ecc4df7830bdd9bec48e20077edff0010827da50
[lhc/web/wiklou.git] / tests / phpunit / includes / parser / ParserOutputTest.php
1 <?php
2 use Wikimedia\TestingAccessWrapper;
3
4 /**
5 * @group Database
6 * ^--- trigger DB shadowing because we are using Title magic
7 */
8 class ParserOutputTest extends MediaWikiLangTestCase {
9
10 public static function provideIsLinkInternal() {
11 return [
12 // Different domains
13 [ false, 'http://example.org', 'http://mediawiki.org' ],
14 // Same domains
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' ],
32 ];
33 }
34
35 /**
36 * Test to make sure ParserOutput::isLinkInternal behaves properly
37 * @dataProvider provideIsLinkInternal
38 * @covers ParserOutput::isLinkInternal
39 */
40 public function testIsLinkInternal( $shouldMatch, $server, $url ) {
41 $this->assertEquals( $shouldMatch, ParserOutput::isLinkInternal( $server, $url ) );
42 }
43
44 /**
45 * @covers ParserOutput::setExtensionData
46 * @covers ParserOutput::getExtensionData
47 */
48 public function testExtensionData() {
49 $po = new ParserOutput();
50
51 $po->setExtensionData( "one", "Foo" );
52
53 $this->assertEquals( "Foo", $po->getExtensionData( "one" ) );
54 $this->assertNull( $po->getExtensionData( "spam" ) );
55
56 $po->setExtensionData( "two", "Bar" );
57 $this->assertEquals( "Foo", $po->getExtensionData( "one" ) );
58 $this->assertEquals( "Bar", $po->getExtensionData( "two" ) );
59
60 $po->setExtensionData( "one", null );
61 $this->assertNull( $po->getExtensionData( "one" ) );
62 $this->assertEquals( "Bar", $po->getExtensionData( "two" ) );
63 }
64
65 /**
66 * @covers ParserOutput::setProperty
67 * @covers ParserOutput::getProperty
68 * @covers ParserOutput::unsetProperty
69 * @covers ParserOutput::getProperties
70 */
71 public function testProperties() {
72 $po = new ParserOutput();
73
74 $po->setProperty( 'foo', 'val' );
75
76 $properties = $po->getProperties();
77 $this->assertEquals( $po->getProperty( 'foo' ), 'val' );
78 $this->assertEquals( $properties['foo'], 'val' );
79
80 $po->setProperty( 'foo', 'second val' );
81
82 $properties = $po->getProperties();
83 $this->assertEquals( $po->getProperty( 'foo' ), 'second val' );
84 $this->assertEquals( $properties['foo'], 'second val' );
85
86 $po->unsetProperty( 'foo' );
87
88 $properties = $po->getProperties();
89 $this->assertEquals( $po->getProperty( 'foo' ), false );
90 $this->assertArrayNotHasKey( 'foo', $properties );
91 }
92
93 /**
94 * @covers ParserOutput::getWrapperDivClass
95 * @covers ParserOutput::addWrapperDivClass
96 * @covers ParserOutput::clearWrapperDivClass
97 * @covers ParserOutput::getText
98 */
99 public function testWrapperDivClass() {
100 $po = new ParserOutput();
101
102 $po->setText( 'Kittens' );
103 $this->assertContains( 'Kittens', $po->getText() );
104 $this->assertNotContains( '<div', $po->getText() );
105 $this->assertSame( 'Kittens', $po->getRawText() );
106
107 $po->addWrapperDivClass( 'foo' );
108 $text = $po->getText();
109 $this->assertContains( 'Kittens', $text );
110 $this->assertContains( '<div', $text );
111 $this->assertContains( 'class="foo"', $text );
112
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 );
118
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 );
124
125 $text = $po->getText( [ 'wrapperDivClass' => '' ] );
126 $this->assertContains( 'Kittens', $text );
127 $this->assertNotContains( '<div', $text );
128 $this->assertNotContains( 'class="foo bar"', $text );
129
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 );
135
136 $text = $po->getRawText();
137 $this->assertSame( 'Kittens', $text );
138
139 $po->clearWrapperDivClass();
140 $text = $po->getText();
141 $this->assertContains( 'Kittens', $text );
142 $this->assertNotContains( '<div', $text );
143 $this->assertNotContains( 'class="foo bar"', $text );
144 }
145
146 /**
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
152 */
153 public function testGetText( $options, $text, $expect ) {
154 $this->setMwGlobals( [
155 'wgArticlePath' => '/wiki/$1',
156 'wgScriptPath' => '/w',
157 'wgScript' => '/w/index.php',
158 ] );
159
160 $po = new ParserOutput( $text );
161 $actual = $po->getText( $options );
162 $this->assertSame( $expect, $actual );
163 }
164
165 public static function provideGetText() {
166 // phpcs:disable Generic.Files.LineLength
167 $text = <<<EOF
168 <p>Test document.
169 </p>
170 <mw:toc><div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
171 <ul>
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>
174 <ul>
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>
176 </ul>
177 </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>
179 </ul>
180 </div>
181 </mw:toc>
182 <h2><span class="mw-headline" id="Section_1">Section 1</span><mw:editsection page="Test Page" section="1">Section 1</mw:editsection></h2>
183 <p>One
184 </p>
185 <h2><span class="mw-headline" id="Section_2">Section 2</span><mw:editsection page="Test Page" section="2">Section 2</mw:editsection></h2>
186 <p>Two
187 </p>
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>
189 <p>Two point one
190 </p>
191 <h2><span class="mw-headline" id="Section_3">Section 3</span><mw:editsection page="Test Page" section="4">Section 3</mw:editsection></h2>
192 <p>Three
193 </p>
194 EOF;
195
196 $dedupText = <<<EOF
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>
207 EOF;
208
209 return [
210 'No options' => [
211 [], $text, <<<EOF
212 <p>Test document.
213 </p>
214 <div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
215 <ul>
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>
218 <ul>
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>
220 </ul>
221 </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>
223 </ul>
224 </div>
225
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&amp;action=edit&amp;section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
227 <p>One
228 </p>
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&amp;action=edit&amp;section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
230 <p>Two
231 </p>
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&amp;action=edit&amp;section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
233 <p>Two point one
234 </p>
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&amp;action=edit&amp;section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
236 <p>Three
237 </p>
238 EOF
239 ],
240 'Disable section edit links' => [
241 [ 'enableSectionEditLinks' => false ], $text, <<<EOF
242 <p>Test document.
243 </p>
244 <div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
245 <ul>
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>
248 <ul>
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>
250 </ul>
251 </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>
253 </ul>
254 </div>
255
256 <h2><span class="mw-headline" id="Section_1">Section 1</span></h2>
257 <p>One
258 </p>
259 <h2><span class="mw-headline" id="Section_2">Section 2</span></h2>
260 <p>Two
261 </p>
262 <h3><span class="mw-headline" id="Section_2.1">Section 2.1</span></h3>
263 <p>Two point one
264 </p>
265 <h2><span class="mw-headline" id="Section_3">Section 3</span></h2>
266 <p>Three
267 </p>
268 EOF
269 ],
270 'Disable TOC, but wrap' => [
271 [ 'allowTOC' => false, 'wrapperDivClass' => 'mw-parser-output' ], $text, <<<EOF
272 <div class="mw-parser-output"><p>Test document.
273 </p>
274
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&amp;action=edit&amp;section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
276 <p>One
277 </p>
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&amp;action=edit&amp;section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
279 <p>Two
280 </p>
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&amp;action=edit&amp;section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
282 <p>Two point one
283 </p>
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&amp;action=edit&amp;section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
285 <p>Three
286 </p></div>
287 EOF
288 ],
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>
301 EOF
302 ],
303 'Style deduplication disabled' => [
304 [ 'deduplicateStyles' => false ], $dedupText, $dedupText
305 ],
306 ];
307 // phpcs:enable
308 }
309
310 public function provideMergeHtmlMetaDataFrom() {
311 // title text ------------
312 $a = new ParserOutput();
313 $a->setTitleText( 'X' );
314 $b = new ParserOutput();
315 yield 'only left title text' => [ $a, $b, [ 'getTitleText' => 'X' ] ];
316
317 $a = new ParserOutput();
318 $b = new ParserOutput();
319 $b->setTitleText( 'Y' );
320 yield 'only right title text' => [ $a, $b, [ 'getTitleText' => 'Y' ] ];
321
322 $a = new ParserOutput();
323 $a->setTitleText( 'X' );
324 $b = new ParserOutput();
325 $b->setTitleText( 'Y' );
326 yield 'left title text wins' => [ $a, $b, [ 'getTitleText' => 'X' ] ];
327
328 // index policy ------------
329 $a = new ParserOutput();
330 $a->setIndexPolicy( 'index' );
331 $b = new ParserOutput();
332 yield 'only left index policy' => [ $a, $b, [ 'getIndexPolicy' => 'index' ] ];
333
334 $a = new ParserOutput();
335 $b = new ParserOutput();
336 $b->setIndexPolicy( 'index' );
337 yield 'only right index policy' => [ $a, $b, [ 'getIndexPolicy' => 'index' ] ];
338
339 $a = new ParserOutput();
340 $a->setIndexPolicy( 'noindex' );
341 $b = new ParserOutput();
342 $b->setIndexPolicy( 'index' );
343 yield 'left noindex wins' => [ $a, $b, [ 'getIndexPolicy' => 'noindex' ] ];
344
345 $a = new ParserOutput();
346 $a->setIndexPolicy( 'index' );
347 $b = new ParserOutput();
348 $b->setIndexPolicy( 'noindex' );
349 yield 'right noindex wins' => [ $a, $b, [ 'getIndexPolicy' => 'noindex' ] ];
350
351 // head items and friends ------------
352 $a = new ParserOutput();
353 $a->addHeadItem( '<foo1>' );
354 $a->addHeadItem( '<bar1>', 'bar' );
355 $a->addModules( 'test-module-a' );
356 $a->addModuleScripts( 'test-module-script-a' );
357 $a->addModuleStyles( 'test-module-styles-a' );
358 $b->addJsConfigVars( 'test-config-var-a', 'a' );
359
360 $b = new ParserOutput();
361 $b->setIndexPolicy( 'noindex' );
362 $b->addHeadItem( '<foo2>' );
363 $b->addHeadItem( '<bar2>', 'bar' );
364 $b->addModules( 'test-module-b' );
365 $b->addModuleScripts( 'test-module-script-b' );
366 $b->addModuleStyles( 'test-module-styles-b' );
367 $b->addJsConfigVars( 'test-config-var-b', 'b' );
368 $b->addJsConfigVars( 'test-config-var-a', 'X' );
369
370 yield 'head items and friends' => [ $a, $b, [
371 'getHeadItems' => [
372 '<foo1>',
373 '<foo2>',
374 'bar' => '<bar2>', // overwritten
375 ],
376 'getModules' => [
377 'test-module-a',
378 'test-module-b',
379 ],
380 'getModuleScripts' => [
381 'test-module-script-a',
382 'test-module-script-b',
383 ],
384 'getModuleStyles' => [
385 'test-module-styles-a',
386 'test-module-styles-b',
387 ],
388 'getJsConfigVars' => [
389 'test-config-var-a' => 'X', // overwritten
390 'test-config-var-b' => 'b',
391 ],
392 ] ];
393
394 // TOC ------------
395 $a = new ParserOutput();
396 $a->setTOCHTML( '<p>TOC A</p>' );
397 $a->setSections( [ [ 'fromtitle' => 'A1' ], [ 'fromtitle' => 'A2' ] ] );
398
399 $b = new ParserOutput();
400 $b->setTOCHTML( '<p>TOC B</p>' );
401 $b->setSections( [ [ 'fromtitle' => 'B1' ], [ 'fromtitle' => 'B2' ] ] );
402
403 yield 'concat TOC' => [ $a, $b, [
404 'getTOCHTML' => '<p>TOC A</p><p>TOC B</p>',
405 'getSections' => [
406 [ 'fromtitle' => 'A1' ],
407 [ 'fromtitle' => 'A2' ],
408 [ 'fromtitle' => 'B1' ],
409 [ 'fromtitle' => 'B2' ]
410 ],
411 ] ];
412
413 // Skin Control ------------
414 $a = new ParserOutput();
415 $a->setNewSection( true );
416 $a->hideNewSection( true );
417 $a->setNoGallery( true );
418 $a->addWrapperDivClass( 'foo' );
419
420 $a->setIndicator( 'foo', 'Foo!' );
421 $a->setIndicator( 'bar', 'Bar!' );
422
423 $a->setExtensionData( 'foo', 'Foo!' );
424 $a->setExtensionData( 'bar', 'Bar!' );
425
426 $b = new ParserOutput();
427 $b->setNoGallery( true );
428 $b->setEnableOOUI( true );
429 $b->preventClickjacking( true );
430 $a->addWrapperDivClass( 'bar' );
431
432 $b->setIndicator( 'zoo', 'Zoo!' );
433 $b->setIndicator( 'bar', 'Barrr!' );
434
435 $b->setExtensionData( 'zoo', 'Zoo!' );
436 $b->setExtensionData( 'bar', 'Barrr!' );
437
438 yield 'skin control flags' => [ $a, $b, [
439 'getNewSection' => true,
440 'getHideNewSection' => true,
441 'getNoGallery' => true,
442 'getEnableOOUI' => true,
443 'preventClickjacking' => true,
444 'getIndicators' => [
445 'foo' => 'Foo!',
446 'bar' => 'Barrr!',
447 'zoo' => 'Zoo!',
448 ],
449 'getWrapperDivClass' => 'foo bar',
450 '$mExtensionData' => [
451 'foo' => 'Foo!',
452 'bar' => 'Barrr!',
453 'zoo' => 'Zoo!',
454 ],
455 ] ];
456 }
457
458 /**
459 * @dataProvider provideMergeHtmlMetaDataFrom
460 * @covers ParserOutput::mergeHtmlMetaDataFrom
461 *
462 * @param ParserOutput $a
463 * @param ParserOutput $b
464 * @param array $expected
465 */
466 public function testMergeHtmlMetaDataFrom( ParserOutput $a, ParserOutput $b, $expected ) {
467 $a->mergeHtmlMetaDataFrom( $b );
468
469 $this->assertFieldValues( $a, $expected );
470
471 // test twice, to make sure the operation is idempotent (except for the TOC, see below)
472 $a->mergeHtmlMetaDataFrom( $b );
473
474 // XXX: TOC joining should get smarter. Can we make it idempotent as well?
475 unset( $expected['getTOCHTML'] );
476 unset( $expected['getSections'] );
477
478 $this->assertFieldValues( $a, $expected );
479 }
480
481 private function assertFieldValues( ParserOutput $po, $expected ) {
482 $po = TestingAccessWrapper::newFromObject( $po );
483
484 foreach ( $expected as $method => $value ) {
485 if ( $method[0] === '$' ) {
486 $field = substr( $method, 1 );
487 $actual = $po->__get( $field );
488 } else {
489 $actual = $po->__call( $method, [] );
490 }
491
492 $this->assertEquals( $value, $actual, $method );
493 }
494 }
495
496 public function provideMergeTrackingMetaDataFrom() {
497 // links ------------
498 $a = new ParserOutput();
499 $a->addLink( Title::makeTitle( NS_MAIN, 'Kittens' ), 6 );
500 $a->addLink( Title::makeTitle( NS_TALK, 'Kittens' ), 16 );
501 $a->addLink( Title::makeTitle( NS_MAIN, 'Goats' ), 7 );
502
503 $a->addTemplate( Title::makeTitle( NS_TEMPLATE, 'Goats' ), 107, 1107 );
504
505 $a->addLanguageLink( 'de' );
506 $a->addLanguageLink( 'ru' );
507 $a->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Kittens DE', '', 'de' ) );
508 $a->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Kittens RU', '', 'ru' ) );
509 $a->addExternalLink( 'https://kittens.wikimedia.test' );
510 $a->addExternalLink( 'https://goats.wikimedia.test' );
511
512 $a->addCategory( 'Foo', 'X' );
513 $a->addImage( 'Billy.jpg', '20180101000013', 'DEAD' );
514
515 $b = new ParserOutput();
516 $b->addLink( Title::makeTitle( NS_MAIN, 'Goats' ), 7 );
517 $b->addLink( Title::makeTitle( NS_TALK, 'Goats' ), 17 );
518 $b->addLink( Title::makeTitle( NS_MAIN, 'Dragons' ), 8 );
519 $b->addLink( Title::makeTitle( NS_FILE, 'Dragons.jpg' ), 28 );
520
521 $b->addTemplate( Title::makeTitle( NS_TEMPLATE, 'Dragons' ), 108, 1108 );
522 $a->addTemplate( Title::makeTitle( NS_MAIN, 'Dragons' ), 118, 1118 );
523
524 $b->addLanguageLink( 'fr' );
525 $b->addLanguageLink( 'ru' );
526 $b->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Kittens FR', '', 'fr' ) );
527 $b->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Dragons RU', '', 'ru' ) );
528 $b->addExternalLink( 'https://dragons.wikimedia.test' );
529 $b->addExternalLink( 'https://goats.wikimedia.test' );
530
531 $b->addCategory( 'Bar', 'Y' );
532 $b->addImage( 'Puff.jpg', '20180101000017', 'BEEF' );
533
534 yield 'all kinds of links' => [ $a, $b, [
535 'getLinks' => [
536 NS_MAIN => [
537 'Kittens' => 6,
538 'Goats' => 7,
539 'Dragons' => 8,
540 ],
541 NS_TALK => [
542 'Kittens' => 16,
543 'Goats' => 17,
544 ],
545 NS_FILE => [
546 'Dragons.jpg' => 28,
547 ],
548 ],
549 'getTemplates' => [
550 NS_MAIN => [
551 'Dragons' => 118,
552 ],
553 NS_TEMPLATE => [
554 'Dragons' => 108,
555 'Goats' => 107,
556 ],
557 ],
558 'getTemplateIds' => [
559 NS_MAIN => [
560 'Dragons' => 1118,
561 ],
562 NS_TEMPLATE => [
563 'Dragons' => 1108,
564 'Goats' => 1107,
565 ],
566 ],
567 'getLanguageLinks' => [ 'de', 'ru', 'fr' ],
568 'getInterwikiLinks' => [
569 'de' => [ 'Kittens_DE' => 1 ],
570 'ru' => [ 'Kittens_RU' => 1, 'Dragons_RU' => 1, ],
571 'fr' => [ 'Kittens_FR' => 1 ],
572 ],
573 'getCategories' => [ 'Foo' => 'X', 'Bar' => 'Y' ],
574 'getImages' => [ 'Billy.jpg' => 1, 'Puff.jpg' => 1 ],
575 'getFileSearchOptions' => [
576 'Billy.jpg' => [ 'time' => '20180101000013', 'sha1' => 'DEAD' ],
577 'Puff.jpg' => [ 'time' => '20180101000017', 'sha1' => 'BEEF' ],
578 ],
579 'getExternalLinks' => [
580 'https://dragons.wikimedia.test' => 1,
581 'https://kittens.wikimedia.test' => 1,
582 'https://goats.wikimedia.test' => 1,
583 ]
584 ] ];
585
586 // properties ------------
587 $a = new ParserOutput();
588
589 $a->setProperty( 'foo', 'Foo!' );
590 $a->setProperty( 'bar', 'Bar!' );
591
592 $a->setExtensionData( 'foo', 'Foo!' );
593 $a->setExtensionData( 'bar', 'Bar!' );
594
595 $b = new ParserOutput();
596
597 $b->setProperty( 'zoo', 'Zoo!' );
598 $b->setProperty( 'bar', 'Barrr!' );
599
600 $b->setExtensionData( 'zoo', 'Zoo!' );
601 $b->setExtensionData( 'bar', 'Barrr!' );
602
603 yield 'properties' => [ $a, $b, [
604 'getProperties' => [
605 'foo' => 'Foo!',
606 'bar' => 'Barrr!',
607 'zoo' => 'Zoo!',
608 ],
609 '$mExtensionData' => [
610 'foo' => 'Foo!',
611 'bar' => 'Barrr!',
612 'zoo' => 'Zoo!',
613 ],
614 ] ];
615 }
616
617 /**
618 * @dataProvider provideMergeTrackingMetaDataFrom
619 * @covers ParserOutput::mergeTrackingMetaDataFrom
620 *
621 * @param ParserOutput $a
622 * @param ParserOutput $b
623 * @param array $expected
624 */
625 public function testMergeTrackingMetaDataFrom( ParserOutput $a, ParserOutput $b, $expected ) {
626 $a->mergeTrackingMetaDataFrom( $b );
627
628 $this->assertFieldValues( $a, $expected );
629
630 // test twice, to make sure the operation is idempotent
631 $a->mergeTrackingMetaDataFrom( $b );
632
633 $this->assertFieldValues( $a, $expected );
634 }
635
636 public function provideMergeInternalMetaDataFrom() {
637 // hooks
638 $a = new ParserOutput();
639
640 $a->addOutputHook( 'foo', 'X' );
641 $a->addOutputHook( 'bar' );
642
643 $b = new ParserOutput();
644
645 $b->addOutputHook( 'foo', 'Y' );
646 $b->addOutputHook( 'bar' );
647 $b->addOutputHook( 'zoo' );
648
649 yield 'hooks' => [ $a, $b, [
650 'getOutputHooks' => [
651 [ 'foo', 'X' ],
652 [ 'bar', false ],
653 [ 'foo', 'Y' ],
654 [ 'zoo', false ],
655 ],
656 ] ];
657
658 // flags & co
659 $a = new ParserOutput();
660
661 $a->addWarning( 'Oops' );
662 $a->addWarning( 'Whoops' );
663
664 $a->setFlag( 'foo' );
665 $a->setFlag( 'bar' );
666
667 $a->recordOption( 'Foo' );
668 $a->recordOption( 'Bar' );
669
670 $b = new ParserOutput();
671
672 $b->addWarning( 'Yikes' );
673 $b->addWarning( 'Whoops' );
674
675 $b->setFlag( 'zoo' );
676 $b->setFlag( 'bar' );
677
678 $b->recordOption( 'Zoo' );
679 $b->recordOption( 'Bar' );
680
681 yield 'flags' => [ $a, $b, [
682 'getWarnings' => [ 'Oops', 'Whoops', 'Yikes' ],
683 '$mFlags' => [ 'foo' => true, 'bar' => true, 'zoo' => true ],
684 'getUsedOptions' => [ 'Foo', 'Bar', 'Zoo' ],
685 ] ];
686
687 // timestamp ------------
688 $a = new ParserOutput();
689 $a->setTimestamp( '20180101000011' );
690 $b = new ParserOutput();
691 yield 'only left timestamp' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
692
693 $a = new ParserOutput();
694 $b = new ParserOutput();
695 $b->setTimestamp( '20180101000011' );
696 yield 'only right timestamp' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
697
698 $a = new ParserOutput();
699 $a->setTimestamp( '20180101000011' );
700 $b = new ParserOutput();
701 $b->setTimestamp( '20180101000001' );
702 yield 'left timestamp wins' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
703
704 $a = new ParserOutput();
705 $a->setTimestamp( '20180101000001' );
706 $b = new ParserOutput();
707 $b->setTimestamp( '20180101000011' );
708 yield 'right timestamp wins' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
709
710 // speculative rev id ------------
711 $a = new ParserOutput();
712 $a->setSpeculativeRevIdUsed( 9 );
713 $b = new ParserOutput();
714 yield 'only left speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
715
716 $a = new ParserOutput();
717 $b = new ParserOutput();
718 $b->setSpeculativeRevIdUsed( 9 );
719 yield 'only right speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
720
721 $a = new ParserOutput();
722 $a->setSpeculativeRevIdUsed( 9 );
723 $b = new ParserOutput();
724 $b->setSpeculativeRevIdUsed( 9 );
725 yield 'same speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
726
727 // limit report (recursive max) ------------
728 $a = new ParserOutput();
729
730 $a->setLimitReportData( 'naive1', 7 );
731 $a->setLimitReportData( 'naive2', 27 );
732
733 $a->setLimitReportData( 'limitreport-simple1', 7 );
734 $a->setLimitReportData( 'limitreport-simple2', 27 );
735
736 $a->setLimitReportData( 'limitreport-pair1', [ 7, 9 ] );
737 $a->setLimitReportData( 'limitreport-pair2', [ 27, 29 ] );
738
739 $a->setLimitReportData( 'limitreport-more1', [ 7, 9, 1 ] );
740 $a->setLimitReportData( 'limitreport-more2', [ 27, 29, 21 ] );
741
742 $a->setLimitReportData( 'limitreport-only-a', 13 );
743
744 $b = new ParserOutput();
745
746 $b->setLimitReportData( 'naive1', 17 );
747 $b->setLimitReportData( 'naive2', 17 );
748
749 $b->setLimitReportData( 'limitreport-simple1', 17 );
750 $b->setLimitReportData( 'limitreport-simple2', 17 );
751
752 $b->setLimitReportData( 'limitreport-pair1', [ 17, 19 ] );
753 $b->setLimitReportData( 'limitreport-pair2', [ 17, 19 ] );
754
755 $b->setLimitReportData( 'limitreport-more1', [ 17, 19, 11 ] );
756 $b->setLimitReportData( 'limitreport-more2', [ 17, 19, 11 ] );
757
758 $b->setLimitReportData( 'limitreport-only-b', 23 );
759
760 // first write wins
761 yield 'limit report' => [ $a, $b, [
762 'getLimitReportData' => [
763 'naive1' => 7,
764 'naive2' => 27,
765 'limitreport-simple1' => 7,
766 'limitreport-simple2' => 27,
767 'limitreport-pair1' => [ 7, 9 ],
768 'limitreport-pair2' => [ 27, 29 ],
769 'limitreport-more1' => [ 7, 9, 1 ],
770 'limitreport-more2' => [ 27, 29, 21 ],
771 'limitreport-only-a' => 13,
772 ],
773 'getLimitReportJSData' => [
774 'naive1' => 7,
775 'naive2' => 27,
776 'limitreport' => [
777 'simple1' => 7,
778 'simple2' => 27,
779 'pair1' => [ 'value' => 7, 'limit' => 9 ],
780 'pair2' => [ 'value' => 27, 'limit' => 29 ],
781 'more1' => [ 7, 9, 1 ],
782 'more2' => [ 27, 29, 21 ],
783 'only-a' => 13,
784 ],
785 ],
786 ] ];
787 }
788
789 /**
790 * @dataProvider provideMergeInternalMetaDataFrom
791 * @covers ParserOutput::mergeInternalMetaDataFrom
792 *
793 * @param ParserOutput $a
794 * @param ParserOutput $b
795 * @param array $expected
796 */
797 public function testMergeInternalMetaDataFrom( ParserOutput $a, ParserOutput $b, $expected ) {
798 $a->mergeInternalMetaDataFrom( $b );
799
800 $this->assertFieldValues( $a, $expected );
801
802 // test twice, to make sure the operation is idempotent
803 $a->mergeInternalMetaDataFrom( $b );
804
805 $this->assertFieldValues( $a, $expected );
806 }
807
808 public function testMergeInternalMetaDataFrom_parseStartTime() {
809 /** @var object $a */
810 $a = new ParserOutput();
811 $a = TestingAccessWrapper::newFromObject( $a );
812
813 $a->resetParseStartTime();
814 $aClocks = $a->mParseStartTime;
815
816 $b = new ParserOutput();
817
818 $a->mergeInternalMetaDataFrom( $b );
819 $mergedClocks = $a->mParseStartTime;
820
821 foreach ( $mergedClocks as $clock => $timestamp ) {
822 $this->assertSame( $aClocks[$clock], $timestamp, $clock );
823 }
824
825 // try again, with times in $b also set, and later than $a's
826 usleep( 1234 );
827
828 /** @var object $b */
829 $b = new ParserOutput();
830 $b = TestingAccessWrapper::newFromObject( $b );
831
832 $b->resetParseStartTime();
833
834 $bClocks = $b->mParseStartTime;
835
836 $a->mergeInternalMetaDataFrom( $b->object, 'b' );
837 $mergedClocks = $a->mParseStartTime;
838
839 foreach ( $mergedClocks as $clock => $timestamp ) {
840 $this->assertSame( $aClocks[$clock], $timestamp, $clock );
841 $this->assertLessThanOrEqual( $bClocks[$clock], $timestamp, $clock );
842 }
843
844 // try again, with $a's times being later
845 usleep( 1234 );
846 $a->resetParseStartTime();
847 $aClocks = $a->mParseStartTime;
848
849 $a->mergeInternalMetaDataFrom( $b->object, 'b' );
850 $mergedClocks = $a->mParseStartTime;
851
852 foreach ( $mergedClocks as $clock => $timestamp ) {
853 $this->assertSame( $bClocks[$clock], $timestamp, $clock );
854 $this->assertLessThanOrEqual( $aClocks[$clock], $timestamp, $clock );
855 }
856
857 // try again, with no times in $a set
858 $a = new ParserOutput();
859 $a = TestingAccessWrapper::newFromObject( $a );
860
861 $a->mergeInternalMetaDataFrom( $b->object, 'b' );
862 $mergedClocks = $a->mParseStartTime;
863
864 foreach ( $mergedClocks as $clock => $timestamp ) {
865 $this->assertSame( $bClocks[$clock], $timestamp, $clock );
866 }
867 }
868
869 }