2 use Wikimedia\TestingAccessWrapper
;
6 * ^--- trigger DB shadowing because we are using Title magic
8 class ParserOutputTest
extends MediaWikiLangTestCase
{
10 public static function provideIsLinkInternal() {
13 [ false, 'http://example.org', 'http://mediawiki.org' ],
15 [ true, 'http://example.org', 'http://example.org' ],
16 [ true, 'https://example.org', 'https://example.org' ],
17 [ true, '//example.org', '//example.org' ],
18 // Same domain different cases
19 [ true, 'http://example.org', 'http://EXAMPLE.ORG' ],
20 // Paths, queries, and fragments are not relevant
21 [ true, 'http://example.org', 'http://example.org/wiki/Main_Page' ],
22 [ true, 'http://example.org', 'http://example.org?my=query' ],
23 [ true, 'http://example.org', 'http://example.org#its-a-fragment' ],
24 // Different protocols
25 [ false, 'http://example.org', 'https://example.org' ],
26 [ false, 'https://example.org', 'http://example.org' ],
27 // Protocol relative servers always match http and https links
28 [ true, '//example.org', 'http://example.org' ],
29 [ true, '//example.org', 'https://example.org' ],
30 // But they don't match strange things like this
31 [ false, '//example.org', 'irc://example.org' ],
36 * Test to make sure ParserOutput::isLinkInternal behaves properly
37 * @dataProvider provideIsLinkInternal
38 * @covers ParserOutput::isLinkInternal
40 public function testIsLinkInternal( $shouldMatch, $server, $url ) {
41 $this->assertEquals( $shouldMatch, ParserOutput
::isLinkInternal( $server, $url ) );
45 * @covers ParserOutput::setExtensionData
46 * @covers ParserOutput::getExtensionData
48 public function testExtensionData() {
49 $po = new ParserOutput();
51 $po->setExtensionData( "one", "Foo" );
53 $this->assertEquals( "Foo", $po->getExtensionData( "one" ) );
54 $this->assertNull( $po->getExtensionData( "spam" ) );
56 $po->setExtensionData( "two", "Bar" );
57 $this->assertEquals( "Foo", $po->getExtensionData( "one" ) );
58 $this->assertEquals( "Bar", $po->getExtensionData( "two" ) );
60 $po->setExtensionData( "one", null );
61 $this->assertNull( $po->getExtensionData( "one" ) );
62 $this->assertEquals( "Bar", $po->getExtensionData( "two" ) );
66 * @covers ParserOutput::setProperty
67 * @covers ParserOutput::getProperty
68 * @covers ParserOutput::unsetProperty
69 * @covers ParserOutput::getProperties
71 public function testProperties() {
72 $po = new ParserOutput();
74 $po->setProperty( 'foo', 'val' );
76 $properties = $po->getProperties();
77 $this->assertEquals( $po->getProperty( 'foo' ), 'val' );
78 $this->assertEquals( $properties['foo'], 'val' );
80 $po->setProperty( 'foo', 'second val' );
82 $properties = $po->getProperties();
83 $this->assertEquals( $po->getProperty( 'foo' ), 'second val' );
84 $this->assertEquals( $properties['foo'], 'second val' );
86 $po->unsetProperty( 'foo' );
88 $properties = $po->getProperties();
89 $this->assertEquals( $po->getProperty( 'foo' ), false );
90 $this->assertArrayNotHasKey( 'foo', $properties );
94 * @covers ParserOutput::getWrapperDivClass
95 * @covers ParserOutput::addWrapperDivClass
96 * @covers ParserOutput::clearWrapperDivClass
97 * @covers ParserOutput::getText
99 public function testWrapperDivClass() {
100 $po = new ParserOutput();
102 $po->setText( 'Kittens' );
103 $this->assertContains( 'Kittens', $po->getText() );
104 $this->assertNotContains( '<div', $po->getText() );
105 $this->assertSame( 'Kittens', $po->getRawText() );
107 $po->addWrapperDivClass( 'foo' );
108 $text = $po->getText();
109 $this->assertContains( 'Kittens', $text );
110 $this->assertContains( '<div', $text );
111 $this->assertContains( 'class="foo"', $text );
113 $po->addWrapperDivClass( 'bar' );
114 $text = $po->getText();
115 $this->assertContains( 'Kittens', $text );
116 $this->assertContains( '<div', $text );
117 $this->assertContains( 'class="foo bar"', $text );
119 $po->addWrapperDivClass( 'bar' ); // second time does nothing, no "foo bar bar".
120 $text = $po->getText( [ 'unwrap' => true ] );
121 $this->assertContains( 'Kittens', $text );
122 $this->assertNotContains( '<div', $text );
123 $this->assertNotContains( 'class="foo bar"', $text );
125 $text = $po->getText( [ 'wrapperDivClass' => '' ] );
126 $this->assertContains( 'Kittens', $text );
127 $this->assertNotContains( '<div', $text );
128 $this->assertNotContains( 'class="foo bar"', $text );
130 $text = $po->getText( [ 'wrapperDivClass' => 'xyzzy' ] );
131 $this->assertContains( 'Kittens', $text );
132 $this->assertContains( '<div', $text );
133 $this->assertContains( 'class="xyzzy"', $text );
134 $this->assertNotContains( 'class="foo bar"', $text );
136 $text = $po->getRawText();
137 $this->assertSame( 'Kittens', $text );
139 $po->clearWrapperDivClass();
140 $text = $po->getText();
141 $this->assertContains( 'Kittens', $text );
142 $this->assertNotContains( '<div', $text );
143 $this->assertNotContains( 'class="foo bar"', $text );
147 * @covers ParserOutput::getText
148 * @dataProvider provideGetText
149 * @param array $options Options to getText()
150 * @param string $text Parser text
151 * @param string $expect Expected output
153 public function testGetText( $options, $text, $expect ) {
154 $this->setMwGlobals( [
155 'wgArticlePath' => '/wiki/$1',
156 'wgScriptPath' => '/w',
157 'wgScript' => '/w/index.php',
160 $po = new ParserOutput( $text );
161 $actual = $po->getText( $options );
162 $this->assertSame( $expect, $actual );
165 public static function provideGetText() {
166 // phpcs:disable Generic.Files.LineLength
170 <mw:toc><div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
172 <li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
173 <li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
175 <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>
178 <li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
182 <h2><span class="mw-headline" id="Section_1">Section 1</span><mw:editsection page="Test Page" section="1">Section 1</mw:editsection></h2>
185 <h2><span class="mw-headline" id="Section_2">Section 2</span><mw:editsection page="Test Page" section="2">Section 2</mw:editsection></h2>
188 <h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><mw:editsection page="Test Page" section="3">Section 2.1</mw:editsection></h3>
191 <h2><span class="mw-headline" id="Section_3">Section 3</span><mw:editsection page="Test Page" section="4">Section 3</mw:editsection></h2>
197 <p>This is a test document.</p>
198 <style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
199 <style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
200 <style data-mw-deduplicate="duplicate2">.Duplicate2 {}</style>
201 <style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
202 <style data-mw-deduplicate="duplicate2">.Duplicate2 {}</style>
203 <style data-mw-not-deduplicate="duplicate1">.Duplicate1 {}</style>
204 <style data-mw-deduplicate="duplicate1">.Same-attribute-different-content {}</style>
205 <style data-mw-deduplicate="duplicate3">.Duplicate1 {}</style>
206 <style>.Duplicate1 {}</style>
214 <div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
216 <li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
217 <li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
219 <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>
222 <li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
226 <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&action=edit&section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
229 <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&action=edit&section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
232 <h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
235 <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&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
240 'Disable section edit links' => [
241 [ 'enableSectionEditLinks' => false ], $text, <<<EOF
244 <div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
246 <li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
247 <li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
249 <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>
252 <li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
256 <h2><span class="mw-headline" id="Section_1">Section 1</span></h2>
259 <h2><span class="mw-headline" id="Section_2">Section 2</span></h2>
262 <h3><span class="mw-headline" id="Section_2.1">Section 2.1</span></h3>
265 <h2><span class="mw-headline" id="Section_3">Section 3</span></h2>
270 'Disable TOC, but wrap' => [
271 [ 'allowTOC' => false, 'wrapperDivClass' => 'mw-parser-output' ], $text, <<<EOF
272 <div class="mw-parser-output"><p>Test document.
275 <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&action=edit&section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
278 <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&action=edit&section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
281 <h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
284 <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&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
289 'Style deduplication' => [
290 [], $dedupText, <<<EOF
291 <p>This is a test document.</p>
292 <style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
293 <link rel="mw-deduplicated-inline-style" href="mw-data:duplicate1"/>
294 <style data-mw-deduplicate="duplicate2">.Duplicate2 {}</style>
295 <link rel="mw-deduplicated-inline-style" href="mw-data:duplicate1"/>
296 <link rel="mw-deduplicated-inline-style" href="mw-data:duplicate2"/>
297 <style data-mw-not-deduplicate="duplicate1">.Duplicate1 {}</style>
298 <link rel="mw-deduplicated-inline-style" href="mw-data:duplicate1"/>
299 <style data-mw-deduplicate="duplicate3">.Duplicate1 {}</style>
300 <style>.Duplicate1 {}</style>
303 'Style deduplication disabled' => [
304 [ 'deduplicateStyles' => false ], $dedupText, $dedupText
311 * @covers ParserOutput::hasText
313 public function testHasText() {
314 $po = new ParserOutput();
315 $this->assertTrue( $po->hasText() );
317 $po = new ParserOutput( null );
318 $this->assertFalse( $po->hasText() );
320 $po = new ParserOutput( '' );
321 $this->assertTrue( $po->hasText() );
323 $po = new ParserOutput( null );
325 $this->assertTrue( $po->hasText() );
329 * @covers ParserOutput::getText
331 public function testGetText_failsIfNoText() {
332 $po = new ParserOutput( null );
334 $this->setExpectedException( LogicException
::class );
339 * @covers ParserOutput::getRawText
341 public function testGetRawText_failsIfNoText() {
342 $po = new ParserOutput( null );
344 $this->setExpectedException( LogicException
::class );
348 public function provideMergeHtmlMetaDataFrom() {
349 // title text ------------
350 $a = new ParserOutput();
351 $a->setTitleText( 'X' );
352 $b = new ParserOutput();
353 yield
'only left title text' => [ $a, $b, [ 'getTitleText' => 'X' ] ];
355 $a = new ParserOutput();
356 $b = new ParserOutput();
357 $b->setTitleText( 'Y' );
358 yield
'only right title text' => [ $a, $b, [ 'getTitleText' => 'Y' ] ];
360 $a = new ParserOutput();
361 $a->setTitleText( 'X' );
362 $b = new ParserOutput();
363 $b->setTitleText( 'Y' );
364 yield
'left title text wins' => [ $a, $b, [ 'getTitleText' => 'X' ] ];
366 // index policy ------------
367 $a = new ParserOutput();
368 $a->setIndexPolicy( 'index' );
369 $b = new ParserOutput();
370 yield
'only left index policy' => [ $a, $b, [ 'getIndexPolicy' => 'index' ] ];
372 $a = new ParserOutput();
373 $b = new ParserOutput();
374 $b->setIndexPolicy( 'index' );
375 yield
'only right index policy' => [ $a, $b, [ 'getIndexPolicy' => 'index' ] ];
377 $a = new ParserOutput();
378 $a->setIndexPolicy( 'noindex' );
379 $b = new ParserOutput();
380 $b->setIndexPolicy( 'index' );
381 yield
'left noindex wins' => [ $a, $b, [ 'getIndexPolicy' => 'noindex' ] ];
383 $a = new ParserOutput();
384 $a->setIndexPolicy( 'index' );
385 $b = new ParserOutput();
386 $b->setIndexPolicy( 'noindex' );
387 yield
'right noindex wins' => [ $a, $b, [ 'getIndexPolicy' => 'noindex' ] ];
389 // head items and friends ------------
390 $a = new ParserOutput();
391 $a->addHeadItem( '<foo1>' );
392 $a->addHeadItem( '<bar1>', 'bar' );
393 $a->addModules( 'test-module-a' );
394 $a->addModuleScripts( 'test-module-script-a' );
395 $a->addModuleStyles( 'test-module-styles-a' );
396 $b->addJsConfigVars( 'test-config-var-a', 'a' );
398 $b = new ParserOutput();
399 $b->setIndexPolicy( 'noindex' );
400 $b->addHeadItem( '<foo2>' );
401 $b->addHeadItem( '<bar2>', 'bar' );
402 $b->addModules( 'test-module-b' );
403 $b->addModuleScripts( 'test-module-script-b' );
404 $b->addModuleStyles( 'test-module-styles-b' );
405 $b->addJsConfigVars( 'test-config-var-b', 'b' );
406 $b->addJsConfigVars( 'test-config-var-a', 'X' );
408 yield
'head items and friends' => [ $a, $b, [
412 'bar' => '<bar2>', // overwritten
418 'getModuleScripts' => [
419 'test-module-script-a',
420 'test-module-script-b',
422 'getModuleStyles' => [
423 'test-module-styles-a',
424 'test-module-styles-b',
426 'getJsConfigVars' => [
427 'test-config-var-a' => 'X', // overwritten
428 'test-config-var-b' => 'b',
433 $a = new ParserOutput();
434 $a->setTOCHTML( '<p>TOC A</p>' );
435 $a->setSections( [ [ 'fromtitle' => 'A1' ], [ 'fromtitle' => 'A2' ] ] );
437 $b = new ParserOutput();
438 $b->setTOCHTML( '<p>TOC B</p>' );
439 $b->setSections( [ [ 'fromtitle' => 'B1' ], [ 'fromtitle' => 'B2' ] ] );
441 yield
'concat TOC' => [ $a, $b, [
442 'getTOCHTML' => '<p>TOC A</p><p>TOC B</p>',
444 [ 'fromtitle' => 'A1' ],
445 [ 'fromtitle' => 'A2' ],
446 [ 'fromtitle' => 'B1' ],
447 [ 'fromtitle' => 'B2' ]
451 // Skin Control ------------
452 $a = new ParserOutput();
453 $a->setNewSection( true );
454 $a->hideNewSection( true );
455 $a->setNoGallery( true );
456 $a->addWrapperDivClass( 'foo' );
458 $a->setIndicator( 'foo', 'Foo!' );
459 $a->setIndicator( 'bar', 'Bar!' );
461 $a->setExtensionData( 'foo', 'Foo!' );
462 $a->setExtensionData( 'bar', 'Bar!' );
464 $b = new ParserOutput();
465 $b->setNoGallery( true );
466 $b->setEnableOOUI( true );
467 $b->preventClickjacking( true );
468 $a->addWrapperDivClass( 'bar' );
470 $b->setIndicator( 'zoo', 'Zoo!' );
471 $b->setIndicator( 'bar', 'Barrr!' );
473 $b->setExtensionData( 'zoo', 'Zoo!' );
474 $b->setExtensionData( 'bar', 'Barrr!' );
476 yield
'skin control flags' => [ $a, $b, [
477 'getNewSection' => true,
478 'getHideNewSection' => true,
479 'getNoGallery' => true,
480 'getEnableOOUI' => true,
481 'preventClickjacking' => true,
487 'getWrapperDivClass' => 'foo bar',
488 '$mExtensionData' => [
497 * @dataProvider provideMergeHtmlMetaDataFrom
498 * @covers ParserOutput::mergeHtmlMetaDataFrom
500 * @param ParserOutput $a
501 * @param ParserOutput $b
502 * @param array $expected
504 public function testMergeHtmlMetaDataFrom( ParserOutput
$a, ParserOutput
$b, $expected ) {
505 $a->mergeHtmlMetaDataFrom( $b );
507 $this->assertFieldValues( $a, $expected );
509 // test twice, to make sure the operation is idempotent (except for the TOC, see below)
510 $a->mergeHtmlMetaDataFrom( $b );
512 // XXX: TOC joining should get smarter. Can we make it idempotent as well?
513 unset( $expected['getTOCHTML'] );
514 unset( $expected['getSections'] );
516 $this->assertFieldValues( $a, $expected );
519 private function assertFieldValues( ParserOutput
$po, $expected ) {
520 $po = TestingAccessWrapper
::newFromObject( $po );
522 foreach ( $expected as $method => $value ) {
523 if ( $method[0] === '$' ) {
524 $field = substr( $method, 1 );
525 $actual = $po->__get( $field );
527 $actual = $po->__call( $method, [] );
530 $this->assertEquals( $value, $actual, $method );
534 public function provideMergeTrackingMetaDataFrom() {
535 // links ------------
536 $a = new ParserOutput();
537 $a->addLink( Title
::makeTitle( NS_MAIN
, 'Kittens' ), 6 );
538 $a->addLink( Title
::makeTitle( NS_TALK
, 'Kittens' ), 16 );
539 $a->addLink( Title
::makeTitle( NS_MAIN
, 'Goats' ), 7 );
541 $a->addTemplate( Title
::makeTitle( NS_TEMPLATE
, 'Goats' ), 107, 1107 );
543 $a->addLanguageLink( 'de' );
544 $a->addLanguageLink( 'ru' );
545 $a->addInterwikiLink( Title
::makeTitle( NS_MAIN
, 'Kittens DE', '', 'de' ) );
546 $a->addInterwikiLink( Title
::makeTitle( NS_MAIN
, 'Kittens RU', '', 'ru' ) );
547 $a->addExternalLink( 'https://kittens.wikimedia.test' );
548 $a->addExternalLink( 'https://goats.wikimedia.test' );
550 $a->addCategory( 'Foo', 'X' );
551 $a->addImage( 'Billy.jpg', '20180101000013', 'DEAD' );
553 $b = new ParserOutput();
554 $b->addLink( Title
::makeTitle( NS_MAIN
, 'Goats' ), 7 );
555 $b->addLink( Title
::makeTitle( NS_TALK
, 'Goats' ), 17 );
556 $b->addLink( Title
::makeTitle( NS_MAIN
, 'Dragons' ), 8 );
557 $b->addLink( Title
::makeTitle( NS_FILE
, 'Dragons.jpg' ), 28 );
559 $b->addTemplate( Title
::makeTitle( NS_TEMPLATE
, 'Dragons' ), 108, 1108 );
560 $a->addTemplate( Title
::makeTitle( NS_MAIN
, 'Dragons' ), 118, 1118 );
562 $b->addLanguageLink( 'fr' );
563 $b->addLanguageLink( 'ru' );
564 $b->addInterwikiLink( Title
::makeTitle( NS_MAIN
, 'Kittens FR', '', 'fr' ) );
565 $b->addInterwikiLink( Title
::makeTitle( NS_MAIN
, 'Dragons RU', '', 'ru' ) );
566 $b->addExternalLink( 'https://dragons.wikimedia.test' );
567 $b->addExternalLink( 'https://goats.wikimedia.test' );
569 $b->addCategory( 'Bar', 'Y' );
570 $b->addImage( 'Puff.jpg', '20180101000017', 'BEEF' );
572 yield
'all kinds of links' => [ $a, $b, [
596 'getTemplateIds' => [
605 'getLanguageLinks' => [ 'de', 'ru', 'fr' ],
606 'getInterwikiLinks' => [
607 'de' => [ 'Kittens_DE' => 1 ],
608 'ru' => [ 'Kittens_RU' => 1, 'Dragons_RU' => 1, ],
609 'fr' => [ 'Kittens_FR' => 1 ],
611 'getCategories' => [ 'Foo' => 'X', 'Bar' => 'Y' ],
612 'getImages' => [ 'Billy.jpg' => 1, 'Puff.jpg' => 1 ],
613 'getFileSearchOptions' => [
614 'Billy.jpg' => [ 'time' => '20180101000013', 'sha1' => 'DEAD' ],
615 'Puff.jpg' => [ 'time' => '20180101000017', 'sha1' => 'BEEF' ],
617 'getExternalLinks' => [
618 'https://dragons.wikimedia.test' => 1,
619 'https://kittens.wikimedia.test' => 1,
620 'https://goats.wikimedia.test' => 1,
624 // properties ------------
625 $a = new ParserOutput();
627 $a->setProperty( 'foo', 'Foo!' );
628 $a->setProperty( 'bar', 'Bar!' );
630 $a->setExtensionData( 'foo', 'Foo!' );
631 $a->setExtensionData( 'bar', 'Bar!' );
633 $b = new ParserOutput();
635 $b->setProperty( 'zoo', 'Zoo!' );
636 $b->setProperty( 'bar', 'Barrr!' );
638 $b->setExtensionData( 'zoo', 'Zoo!' );
639 $b->setExtensionData( 'bar', 'Barrr!' );
641 yield
'properties' => [ $a, $b, [
647 '$mExtensionData' => [
656 * @dataProvider provideMergeTrackingMetaDataFrom
657 * @covers ParserOutput::mergeTrackingMetaDataFrom
659 * @param ParserOutput $a
660 * @param ParserOutput $b
661 * @param array $expected
663 public function testMergeTrackingMetaDataFrom( ParserOutput
$a, ParserOutput
$b, $expected ) {
664 $a->mergeTrackingMetaDataFrom( $b );
666 $this->assertFieldValues( $a, $expected );
668 // test twice, to make sure the operation is idempotent
669 $a->mergeTrackingMetaDataFrom( $b );
671 $this->assertFieldValues( $a, $expected );
674 public function provideMergeInternalMetaDataFrom() {
676 $a = new ParserOutput();
678 $a->addOutputHook( 'foo', 'X' );
679 $a->addOutputHook( 'bar' );
681 $b = new ParserOutput();
683 $b->addOutputHook( 'foo', 'Y' );
684 $b->addOutputHook( 'bar' );
685 $b->addOutputHook( 'zoo' );
687 yield
'hooks' => [ $a, $b, [
688 'getOutputHooks' => [
697 $a = new ParserOutput();
699 $a->addWarning( 'Oops' );
700 $a->addWarning( 'Whoops' );
702 $a->setFlag( 'foo' );
703 $a->setFlag( 'bar' );
705 $a->recordOption( 'Foo' );
706 $a->recordOption( 'Bar' );
708 $b = new ParserOutput();
710 $b->addWarning( 'Yikes' );
711 $b->addWarning( 'Whoops' );
713 $b->setFlag( 'zoo' );
714 $b->setFlag( 'bar' );
716 $b->recordOption( 'Zoo' );
717 $b->recordOption( 'Bar' );
719 yield
'flags' => [ $a, $b, [
720 'getWarnings' => [ 'Oops', 'Whoops', 'Yikes' ],
721 '$mFlags' => [ 'foo' => true, 'bar' => true, 'zoo' => true ],
722 'getUsedOptions' => [ 'Foo', 'Bar', 'Zoo' ],
725 // timestamp ------------
726 $a = new ParserOutput();
727 $a->setTimestamp( '20180101000011' );
728 $b = new ParserOutput();
729 yield
'only left timestamp' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
731 $a = new ParserOutput();
732 $b = new ParserOutput();
733 $b->setTimestamp( '20180101000011' );
734 yield
'only right timestamp' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
736 $a = new ParserOutput();
737 $a->setTimestamp( '20180101000011' );
738 $b = new ParserOutput();
739 $b->setTimestamp( '20180101000001' );
740 yield
'left timestamp wins' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
742 $a = new ParserOutput();
743 $a->setTimestamp( '20180101000001' );
744 $b = new ParserOutput();
745 $b->setTimestamp( '20180101000011' );
746 yield
'right timestamp wins' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
748 // speculative rev id ------------
749 $a = new ParserOutput();
750 $a->setSpeculativeRevIdUsed( 9 );
751 $b = new ParserOutput();
752 yield
'only left speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
754 $a = new ParserOutput();
755 $b = new ParserOutput();
756 $b->setSpeculativeRevIdUsed( 9 );
757 yield
'only right speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
759 $a = new ParserOutput();
760 $a->setSpeculativeRevIdUsed( 9 );
761 $b = new ParserOutput();
762 $b->setSpeculativeRevIdUsed( 9 );
763 yield
'same speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
765 // limit report (recursive max) ------------
766 $a = new ParserOutput();
768 $a->setLimitReportData( 'naive1', 7 );
769 $a->setLimitReportData( 'naive2', 27 );
771 $a->setLimitReportData( 'limitreport-simple1', 7 );
772 $a->setLimitReportData( 'limitreport-simple2', 27 );
774 $a->setLimitReportData( 'limitreport-pair1', [ 7, 9 ] );
775 $a->setLimitReportData( 'limitreport-pair2', [ 27, 29 ] );
777 $a->setLimitReportData( 'limitreport-more1', [ 7, 9, 1 ] );
778 $a->setLimitReportData( 'limitreport-more2', [ 27, 29, 21 ] );
780 $a->setLimitReportData( 'limitreport-only-a', 13 );
782 $b = new ParserOutput();
784 $b->setLimitReportData( 'naive1', 17 );
785 $b->setLimitReportData( 'naive2', 17 );
787 $b->setLimitReportData( 'limitreport-simple1', 17 );
788 $b->setLimitReportData( 'limitreport-simple2', 17 );
790 $b->setLimitReportData( 'limitreport-pair1', [ 17, 19 ] );
791 $b->setLimitReportData( 'limitreport-pair2', [ 17, 19 ] );
793 $b->setLimitReportData( 'limitreport-more1', [ 17, 19, 11 ] );
794 $b->setLimitReportData( 'limitreport-more2', [ 17, 19, 11 ] );
796 $b->setLimitReportData( 'limitreport-only-b', 23 );
799 yield
'limit report' => [ $a, $b, [
800 'getLimitReportData' => [
803 'limitreport-simple1' => 7,
804 'limitreport-simple2' => 27,
805 'limitreport-pair1' => [ 7, 9 ],
806 'limitreport-pair2' => [ 27, 29 ],
807 'limitreport-more1' => [ 7, 9, 1 ],
808 'limitreport-more2' => [ 27, 29, 21 ],
809 'limitreport-only-a' => 13,
811 'getLimitReportJSData' => [
817 'pair1' => [ 'value' => 7, 'limit' => 9 ],
818 'pair2' => [ 'value' => 27, 'limit' => 29 ],
819 'more1' => [ 7, 9, 1 ],
820 'more2' => [ 27, 29, 21 ],
828 * @dataProvider provideMergeInternalMetaDataFrom
829 * @covers ParserOutput::mergeInternalMetaDataFrom
831 * @param ParserOutput $a
832 * @param ParserOutput $b
833 * @param array $expected
835 public function testMergeInternalMetaDataFrom( ParserOutput
$a, ParserOutput
$b, $expected ) {
836 $a->mergeInternalMetaDataFrom( $b );
838 $this->assertFieldValues( $a, $expected );
840 // test twice, to make sure the operation is idempotent
841 $a->mergeInternalMetaDataFrom( $b );
843 $this->assertFieldValues( $a, $expected );
846 public function testMergeInternalMetaDataFrom_parseStartTime() {
847 /** @var object $a */
848 $a = new ParserOutput();
849 $a = TestingAccessWrapper
::newFromObject( $a );
851 $a->resetParseStartTime();
852 $aClocks = $a->mParseStartTime
;
854 $b = new ParserOutput();
856 $a->mergeInternalMetaDataFrom( $b );
857 $mergedClocks = $a->mParseStartTime
;
859 foreach ( $mergedClocks as $clock => $timestamp ) {
860 $this->assertSame( $aClocks[$clock], $timestamp, $clock );
863 // try again, with times in $b also set, and later than $a's
866 /** @var object $b */
867 $b = new ParserOutput();
868 $b = TestingAccessWrapper
::newFromObject( $b );
870 $b->resetParseStartTime();
872 $bClocks = $b->mParseStartTime
;
874 $a->mergeInternalMetaDataFrom( $b->object, 'b' );
875 $mergedClocks = $a->mParseStartTime
;
877 foreach ( $mergedClocks as $clock => $timestamp ) {
878 $this->assertSame( $aClocks[$clock], $timestamp, $clock );
879 $this->assertLessThanOrEqual( $bClocks[$clock], $timestamp, $clock );
882 // try again, with $a's times being later
884 $a->resetParseStartTime();
885 $aClocks = $a->mParseStartTime
;
887 $a->mergeInternalMetaDataFrom( $b->object, 'b' );
888 $mergedClocks = $a->mParseStartTime
;
890 foreach ( $mergedClocks as $clock => $timestamp ) {
891 $this->assertSame( $bClocks[$clock], $timestamp, $clock );
892 $this->assertLessThanOrEqual( $aClocks[$clock], $timestamp, $clock );
895 // try again, with no times in $a set
896 $a = new ParserOutput();
897 $a = TestingAccessWrapper
::newFromObject( $a );
899 $a->mergeInternalMetaDataFrom( $b->object, 'b' );
900 $mergedClocks = $a->mParseStartTime
;
902 foreach ( $mergedClocks as $clock => $timestamp ) {
903 $this->assertSame( $bClocks[$clock], $timestamp, $clock );