%PDF- %PDF-
Direktori : /www/varak.net/wiki.varak.net/extensions/MobileFrontend/tests/phpunit/ |
Current File : /www/varak.net/wiki.varak.net/extensions/MobileFrontend/tests/phpunit/MobileFormatterTest.php |
<?php /** * @group MobileFrontend */ class MobileFormatterTest extends MediaWikiTestCase { const TOC = '<div id="toc" class="toc-mobile"><h2>Contents</h2></div>'; const SECTION_INDICATOR = '<div class="mw-ui-icon mw-ui-icon-element indicator"></div>'; const HATNOTE_CLASSNAME = 'hatnote'; const INFOBOX_CLASSNAME = 'infobox'; /** * Helper function that creates section headings from a heading and title * * @param string $heading * @param string $innerHtml of the heading element * @param integer $sectionNumber heading corresponds to * @return string */ private function makeSectionHeading( $heading, $innerHtml, $sectionNumber = 1 ) { return "<$heading class=\"section-heading\"" . " onclick=\"javascript:mfTempOpenSection($sectionNumber)\">" . self::SECTION_INDICATOR . "$innerHtml</$heading>"; } /** * Helper function that creates sections from section number and content HTML. * * @param string $sectionNumber * @param string $contentHtml * @param boolean $isReferenceSection whether the section contains references * @return string */ private function makeSectionHtml( $sectionNumber, $contentHtml = '', $isReferenceSection = false ) { $attrs = $isReferenceSection ? ' data-is-reference-section="1"' : ''; $className = "mf-section-$sectionNumber"; if ( $sectionNumber > 0 ) { $className = $className . ' ' . MobileFormatter::STYLE_COLLAPSIBLE_SECTION_CLASS; } return "<div class=\"$className\" id=\"mf-section-$sectionNumber\"" . "$attrs>$contentHtml</div>"; } /** * @dataProvider provideHtmlTransform * * @param string $input * @param string $expected * @param callable|bool $callback * @param bool $removeDefaults * @param bool $lazyLoadReferences * @param bool $lazyLoadImages * @param bool $showFirstParagraphBeforeInfobox * @covers MobileFormatter::filterContent * @covers MobileFormatter::doRemoveImages */ public function testHtmlTransform( $input, $expected, $callback = false, $removeDefaults = false, $lazyLoadReferences = false, $lazyLoadImages = false, $showFirstParagraphBeforeInfobox = false ) { $t = Title::newFromText( 'Mobile' ); // "yay" to Windows! $input = str_replace( "\r", '', $input ); $mf = new MobileFormatter( MobileFormatter::wrapHTML( $input ), $t ); if ( $callback ) { $callback( $mf ); } $mf->topHeadingTags = [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ]; $mf->filterContent( $removeDefaults, $lazyLoadReferences, $lazyLoadImages, $showFirstParagraphBeforeInfobox ); $html = $mf->getText(); $this->assertEquals( str_replace( "\n", '', $expected ), str_replace( "\n", '', $html ) ); } /** * @covers MobileFormatter::enableExpandableSections * @covers MobileFormatter::filterContent */ public function testHtmlTransformWhenSkippingLazyLoadingSmallImages() { $smallPic = '<img src="smallPicture.jpg" style="width: 4.4ex; height:3.34ex;">'; $enableSections = function ( MobileFormatter $mf ) { $mf->enableExpandableSections(); }; $this->setMwGlobals( [ 'wgMFLazyLoadSkipSmallImages' => true ] ); $this->testHtmlTransform( '<p>text</p><h2>heading 1</h2>' . $smallPic, $this->makeSectionHtml( 0, '<p>text</p>' ) . $this->makeSectionHeading( 'h2', 'heading 1' ) . $this->makeSectionHtml( 1, $smallPic ), $enableSections, false, false, true ); } public function provideHtmlTransform() { $enableSections = function ( MobileFormatter $mf ) { $mf->enableExpandableSections(); }; $longLine = "\n" . str_repeat( 'A', 5000 ); $removeImages = function ( MobileFormatter $f ) { $f->setRemoveMedia(); }; $mainPage = function ( MobileFormatter $f ) { $f->setIsMainPage( true ); }; $citeUrl = SpecialPage::getTitleFor( 'MobileCite', '0' )->getLocalUrl(); $originalImage = '<img alt="foo" src="foo.jpg" width="100" ' . 'height="100" srcset="foo-1.5x.jpg 1.5x, foo-2x.jpg 2x">'; $placeholder = '<span class="lazy-image-placeholder" ' . 'style="width: 100px;height: 100px;" ' . 'data-src="foo.jpg" data-alt="foo" data-width="100" data-height="100" ' . 'data-srcset="foo-1.5x.jpg 1.5x, foo-2x.jpg 2x">' . ' ' . '</span>'; $noscript = '<noscript><img alt="foo" src="foo.jpg" width="100" height="100"></noscript>'; $refText = '<p>They saved the world with one single unit test' . '<sup class="reference"><a href="#cite-note-1">[1]</a></sup></p>'; $expectedReftext = '<p>They saved the world with one single unit test' . '<sup class="reference"><a href="' . $citeUrl . '#cite-note-1">[1]</a></sup></p>'; $refhtml = '<ol class="references"><li>link 1</li><li>link 2</li></ol>'; $refplaceholder = Html::element( 'a', [ 'class' => 'mf-lazy-references-placeholder', 'href' => $citeUrl, ], wfMessage( 'mobile-frontend-references-list' )->text() ); $refSectionHtml = $this->makeSectionHeading( 'h2', 'references' ) . $this->makeSectionHtml( 1, $refplaceholder, true ); return [ // Nested headings are not wrapped [ '<div class="wrapper"><p>Text goes here i think 2testestestestest</p>' . '<h2>Heading</h2>I am awesome</div>' . 'Text<h2>test</h2><p>more text</p>', $this->makeSectionHtml( 0, '<div class="wrapper"><p>Text goes here i think 2testestestestest</p>' . '<h2>Heading</h2>I am awesome</div>Text' ) . $this->makeSectionHeading( 'h2', 'test' ) . $this->makeSectionHtml( 1, '<p>more text</p>' ), $enableSections, false, false, false ], // # Lazy loading images // Main page not impacted [ '<div>a</div><h2>Today</h2>' . $originalImage . '<h2>Tomorrow</h2>Test.', '<div>a</div><h2>Today</h2>' . $originalImage . '<h2>Tomorrow</h2>Test.', $mainPage, false, false, true, ], // Lead section images not impacted [ '<p>' . $originalImage . '</p><h2>heading 1</h2><p>text</p>' . '<h2>heading 2</h2>abc', $this->makeSectionHtml( 0, '<p>' . $originalImage . '</p>' ) . $this->makeSectionHeading( 'h2', 'heading 1' ) . $this->makeSectionHtml( 1, '<p>text</p>' ) . $this->makeSectionHeading( 'h2', 'heading 2', 2 ) . $this->makeSectionHtml( 2, 'abc' ), $enableSections, false, false, true, ], // Test lazy loading of images outside the lead section [ '<p>text</p><h2>heading 1</h2><p>text</p>' . $originalImage . '<h2>heading 2</h2>abc', $this->makeSectionHtml( 0, '<p>text</p>' ) . $this->makeSectionHeading( 'h2', 'heading 1' ) . $this->makeSectionHtml( 1, '<p>text</p>' . $noscript . $placeholder ) . $this->makeSectionHeading( 'h2', 'heading 2', 2 ) . $this->makeSectionHtml( 2, 'abc' ), $enableSections, false, false, true, ], // https://phabricator.wikimedia.org/T130025, last section filtered [ '<p>text</p><h2>heading 1</h2><p>text</p>' . $originalImage . '<h2>heading 2</h2>' . $originalImage, $this->makeSectionHtml( 0, '<p>text</p>' ) . $this->makeSectionHeading( 'h2', 'heading 1' ) . $this->makeSectionHtml( 1, '<p>text</p>' . $noscript . $placeholder ) . $this->makeSectionHeading( 'h2', 'heading 2', 2 ) . $this->makeSectionHtml( 2, $noscript . $placeholder ), $enableSections, false, false, true, ], // # Lazy loading references [ $refText . '<h2>references</h2>' . $refhtml, $this->makeSectionHtml( 0, $expectedReftext ) . $refSectionHtml, $enableSections, false, true, false ], // T135923: Note the whitespace immediately inside the `sup` element. [ '<p>T135923 <sup class="reference"> <a href="#cite-note-1">[1]</a></sup></p>' . '<h2>references</h2>' . $refhtml, $this->makeSectionHtml( 0, '<p>T135923 <sup class="reference"> ' . '<a href="' . $citeUrl . '#cite-note-1">[1]</a></sup></p>' ) . $refSectionHtml, $enableSections, false, true, false ], // Empty reference class [ '<p>T135923 <sup class="reference"></sup></p>' . '<h2>references</h2>' . $refhtml, $this->makeSectionHtml( 0, '<p>T135923 <sup class="reference"></sup></p>' ) . $refSectionHtml, $enableSections, false, true, false ], // # Removal of images [ '<img src="/foo/bar.jpg" alt="Blah"/>', '<span class="mw-mf-image-replacement">[Blah]</span>', $removeImages, ], [ '<img alt="picture of kitty" src="kitty.jpg">', '<span class="mw-mf-image-replacement">' . '[picture of kitty]</span>', $removeImages, ], [ '<img src="kitty.jpg">', '<span class="mw-mf-image-replacement">[' . wfMessage( 'mobile-frontend-missing-image' ) . ']</span>', $removeImages, ], [ '<img alt src="kitty.jpg">', '<span class="mw-mf-image-replacement">[' . wfMessage( 'mobile-frontend-missing-image' ) . ']</span>', $removeImages, ], [ '<img alt src="kitty.jpg">look at the cute kitty!' . '<img alt="picture of angry dog" src="dog.jpg">', '<span class="mw-mf-image-replacement">[' . wfMessage( 'mobile-frontend-missing-image' ) . ']</span>look at the cute kitty!' . '<span class="mw-mf-image-replacement">[picture of angry dog]</span>', $removeImages, ], // # Section wrapping // \n</h2> in headers [ '<h2><span class="mw-headline" id="Forty-niners">Forty-niners</span>' . '<a class="edit-page" href="#editor/2">Edit</a></h2>' . $longLine, $this->makeSectionHtml( 0, '' ) . $this->makeSectionHeading( 'h2', '<span class="mw-headline" id="Forty-niners">Forty-niners</span>' . '<a class="edit-page" href="#editor/2">Edit</a>' ) . $this->makeSectionHtml( 1, $longLine ), $enableSections ], // \n</h3> in headers [ '<h3><span>h3</span></h3>' . $longLine . '<h4><span>h4</span></h4>' . 'h4 text.', $this->makeSectionHtml( 0, '' ) . $this->makeSectionHeading( 'h3', '<span>h3</span>' ) . $this->makeSectionHtml( 1, $longLine . '<h4 class="in-block"><span>h4</span></h4>' . 'h4 text.' ), $enableSections ], // \n</h6> in headers [ '<h6><span>h6</span></h6>' . $longLine, $this->makeSectionHtml( 0, '' ) . $this->makeSectionHeading( 'h6', '<span>h6</span>' ) . $this->makeSectionHtml( 1, $longLine ), $enableSections ], // Bug 36670 [ '<h2><span class="mw-headline" id="History"><span id="Overview"></span>' . 'History</span><a class="edit-page" href="#editor/2">Edit</a></h2>' . $longLine, $this->makeSectionHtml( 0, '' ) . $this->makeSectionHeading( 'h2', '<span class="mw-headline" id="History"><span id="Overview"></span>' . 'History</span><a class="edit-page" href="#editor/2">Edit</a>' ) . $this->makeSectionHtml( 1, $longLine ), $enableSections ], // Infobox and the first paragraph in lead section transformations [ // no lead section, no infobox, a section '<h2>Heading 1</h2>' . '<p>paragraph 3</p>', $this->makeSectionHtml( 0, '' ) . $this->makeSectionHeading( 'h2', 'Heading 1' ) . $this->makeSectionHtml( 1, '<p>paragraph 3</p>' ), $enableSections, false, false, false, true, ], [ // hat-note, lead section, no infobox, another section '<div class="' . self::HATNOTE_CLASSNAME . '">hatnote</div>' . '<p>paragraph 1</p>' . '<p>paragraph 2</p>' . '<h2>Heading 1</h2>' . '<p>paragraph 3</p>', $this->makeSectionHtml( 0, '<div class="' . self::HATNOTE_CLASSNAME . '">hatnote</div>' . '<p>paragraph 1</p>' . '<p>paragraph 2</p>' ) . $this->makeSectionHeading( 'h2', 'Heading 1' ) . $this->makeSectionHtml( 1, '<p>paragraph 3</p>' ), $enableSections, false, false, false, true, ], [ // hat-note, lead section, infobox, another section '<div class="' . self::HATNOTE_CLASSNAME . '">hatnote</div>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>' . '<p>paragraph 1</p>' . '<p>paragraph 2</p>' . '<h2>Heading 1</h2>' . '<p>paragraph 3</p>', $this->makeSectionHtml( 0, '<div class="' . self::HATNOTE_CLASSNAME . '">hatnote</div>' . '<p>paragraph 1</p>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>' . '<p>paragraph 2</p>' ) . $this->makeSectionHeading( 'h2', 'Heading 1' ) . $this->makeSectionHtml( 1, '<p>paragraph 3</p>' ), $enableSections, false, false, false, true, ], [ // first paragraph is already before the lead section '<p>paragraph 1</p>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>' . '<p>paragraph 2</p>' . '<h2>Heading 1</h2>' . '<p>paragraph 3</p>', $this->makeSectionHtml( 0, '<p>paragraph 1</p>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>' . '<p>paragraph 2</p>' ) . $this->makeSectionHeading( 'h2', 'Heading 1' ) . $this->makeSectionHtml( 1, '<p>paragraph 3</p>' ), $enableSections, false, false, false, true, ], [ // infobox, but no paragraphs in the lead section '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>' . '<h2>Heading 1</h2>' . '<p>paragraph 1</p>', $this->makeSectionHtml( 0, '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>' ) . $this->makeSectionHeading( 'h2', 'Heading 1' ) . $this->makeSectionHtml( 1, '<p>paragraph 1</p>' ), $enableSections, false, false, false, true, ], [ // no lead section, infobox after the first section '<h2>Heading 1</h2>' . '<p>paragraph 1</p>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>', $this->makeSectionHtml( 0, '' ) . $this->makeSectionHeading( 'h2', 'Heading 1' ) . $this->makeSectionHtml( 1, '<p>paragraph 1</p>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>' ), $enableSections, false, false, false, true, ], [ // two infoboxes, lead section, another section '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox 1</td></tr></table>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox 2</td></tr></table>' . '<p>paragraph 1</p>' . '<h2>Heading 1</h2>' . '<p>paragraph 1</p>', $this->makeSectionHtml( 0, '<p>paragraph 1</p>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox 1</td></tr></table>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox 2</td></tr></table>' ) . $this->makeSectionHeading( 'h2', 'Heading 1' ) . $this->makeSectionHtml( 1, '<p>paragraph 1</p>' ), $enableSections, false, false, false, true, ], [ // first paragraph (which has coordinates and is hidden on mobile), // infobox, lead section '<p><span><span id="coordinates">Coordinates</span></span></p>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>' . '<p>paragraph 2</p>', $this->makeSectionHtml( 0, '<p><span><span id="coordinates">Coordinates</span></span></p>' . '<p>paragraph 2</p>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>' ), $enableSections, false, false, false, true, ], [ // hatnote, infobox, thumbnail, lead section, another section '<div class="' . self::HATNOTE_CLASSNAME . '">hatnote</div>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>' . '<div class="thumb">Thumbnail</div>' . '<p>paragraph 1</p>' . '<p>paragraph 2</p>' . '<h2>Heading 1</h2>' . '<p>paragraph 3</p>', $this->makeSectionHtml( 0, '<div class="' . self::HATNOTE_CLASSNAME . '">hatnote</div>' . '<p>paragraph 1</p>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>' . '<div class="thumb">Thumbnail</div>' . '<p>paragraph 2</p>' ) . $this->makeSectionHeading( 'h2', 'Heading 1' ) . $this->makeSectionHtml( 1, '<p>paragraph 3</p>' ), $enableSections, false, false, false, true, ], [ // empty first paragraph, infobox, second paragraph, another section '<p></p>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>' . '<p>paragraph 2</p>' . '<h2>Heading 1</h2>' . '<p>paragraph 3</p>', $this->makeSectionHtml( 0, '<p></p>' . '<p>paragraph 2</p>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>' ) . $this->makeSectionHeading( 'h2', 'Heading 1' ) . $this->makeSectionHtml( 1, '<p>paragraph 3</p>' ), $enableSections, false, false, false, true, ], [ // infobox, empty first paragraph, second paragraph, another section '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>' . '<p></p>' . '<p>paragraph 2</p>' . '<h2>Heading 1</h2>' . '<p>paragraph 3</p>', $this->makeSectionHtml( 0, '<p>paragraph 2</p>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>' . '<p></p>' ) . $this->makeSectionHeading( 'h2', 'Heading 1' ) . $this->makeSectionHtml( 1, '<p>paragraph 3</p>' ), $enableSections, false, false, false, true, ], [ // infobox, a paragraph, list element // @see https://phabricator.wikimedia.org/T149852 '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>' . '<p>paragraph</p>' . '<ol><li>item 1</li><li>item 2</li></ol>', $this->makeSectionHtml( 0, '<p>paragraph</p><ol><li>item 1</li><li>item 2</li></ol>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table>' ), $enableSections, false, false, false, true, ], [ // 2 hat-notes, ambox, 2 infoboxes, 2 paragraphs, another section '<div class="' . self::HATNOTE_CLASSNAME . '">hatnote</div>' . '<div class="' . self::HATNOTE_CLASSNAME . '">hatnote</div>' . '<table class="ambox"><tr><td>ambox</td></tr></table>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox 1</td></tr></table>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox 2</td></tr></table>' . '<p>paragraph 1</p>' . '<p>paragraph 2</p>' . '<ul><li>item</li></ul>' . '<h2>Heading 1</h2>' . '<p>paragraph 3</p>', $this->makeSectionHtml( 0, '<div class="' . self::HATNOTE_CLASSNAME . '">hatnote</div>' . '<div class="' . self::HATNOTE_CLASSNAME . '">hatnote</div>' . '<table class="ambox"><tr><td>ambox</td></tr></table>' . '<p>paragraph 1</p>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox 1</td></tr></table>' . '<table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox 2</td></tr></table>' . '<p>paragraph 2</p><ul><li>item</li></ul>' ) . $this->makeSectionHeading( 'h2', 'Heading 1' ) . $this->makeSectionHtml( 1, '<p>paragraph 3</p>' ), $enableSections, false, false, false, true, ], [ // Minimal test case for T149561: `p` elements should be immediate // descendants of the section container element (`div`, currently). '<table class="' . self::INFOBOX_CLASSNAME . '">' . '<tr><td><p>SURPRISE PARAGRAPH</p></td></tr></table>' . '<p>paragraph 1</p>', $this->makeSectionHtml( 0, '<p>paragraph 1</p>' . '<table class="' . self::INFOBOX_CLASSNAME . '">' . '<tr><td><p>SURPRISE PARAGRAPH</p></td></tr></table>' ), $enableSections, false, false, false, true, ], [ // T149389: If the infobox is inside one or more containers, i.e. not an // immediate child of the section container element, then // MobileFormatter#moveFirstParagraphBeforeInfobox will trigger a "Not // Found Error" warning. // Do not touch infoboxes that are not immediate children of the lead section // unless... (see next test T170006) '<div><table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table></div>' . '<p>paragraph 1</p>', $this->makeSectionHtml( 0, '<div><table class="' . self::INFOBOX_CLASSNAME . '"><tr><td>infobox</td></tr></table></div>' . '<p>paragraph 1</p>' ), $enableSections, false, false, false, true, ], ]; } /** * @dataProvider provideHeadingTransform * @covers MobileFormatter::makeSections * @covers MobileFormatter::enableExpandableSections * @covers MobileFormatter::filterContent */ public function testHeadingTransform( array $topHeadingTags, $input, $expectedOutput ) { $t = Title::newFromText( 'Mobile' ); $formatter = new MobileFormatter( $input, $t ); // If MobileFormatter#enableExpandableSections isn't called, then headings // won't be transformed. $formatter->enableExpandableSections( true ); $formatter->topHeadingTags = $topHeadingTags; $formatter->filterContent(); $this->assertEquals( $expectedOutput, $formatter->getText() ); } public function provideHeadingTransform() { return [ // The "in-block" class is added to a subheading. [ [ 'h1', 'h2' ], '<h1>Foo</h1><h2>Bar</h2>', $this->makeSectionHtml( 0, '' ) . $this->makeSectionHeading( 'h1', 'Foo' ) . $this->makeSectionHtml( 1, '<h2 class="in-block">Bar</h2>' ) ], // The "in-block" class is added to a subheading // without overwriting the existing attribute. [ [ 'h1', 'h2' ], '<h1>Foo</h1><h2 class="baz">Bar</h2>', $this->makeSectionHtml( 0, '' ) . $this->makeSectionHeading( 'h1', 'Foo' ) . $this->makeSectionHtml( 1, '<h2 class="baz in-block">Bar</h2>' ), ], // The "in-block" class is added to all subheadings. [ [ 'h1', 'h2', 'h3' ], '<h1>Foo</h1><h2>Bar</h2><h3>Qux</h3>', $this->makeSectionHtml( 0, '' ) . $this->makeSectionHeading( 'h1', 'Foo' ) . $this->makeSectionHtml( 1, '<h2 class="in-block">Bar</h2><h3 class="in-block">Qux</h3>' ) ], // The first heading found is the highest ranked // subheading. [ [ 'h1', 'h2', 'h3' ], '<h2>Bar</h2><h3>Qux</h3>', $this->makeSectionHtml( 0, '' ) . $this->makeSectionHeading( 'h2', 'Bar' ) . $this->makeSectionHtml( 1, '<h3 class="in-block">Qux</h3>' ), ], // Unenclosed text is appended to the expandable container. [ [ 'h1', 'h2' ], '<h1>Foo</h1><h2>Bar</h2>A', $this->makeSectionHtml( 0, '' ) . $this->makeSectionHeading( 'h1', 'Foo' ) . $this->makeSectionHtml( 1, '<h2 class="in-block">Bar</h2>A' ) ], // Unencloded text that appears before the first // heading is appended to a container. // FIXME: This behaviour was included for backwards // compatibility but mightn't be necessary. [ [ 'h1', 'h2' ], 'A<h1>Foo</h1><h2>Bar</h2>', $this->makeSectionHtml( 0, '<p>A</p>' ) . $this->makeSectionHeading( 'h1', 'Foo' ) . $this->makeSectionHtml( 1, '<h2 class="in-block">Bar</h2>' ), ], // Multiple headings are handled identically. [ [ 'h1', 'h2' ], '<h1>Foo</h1><h2>Bar</h2>Baz<h1>Qux</h1>Quux', $this->makeSectionHtml( 0, '' ) . $this->makeSectionHeading( 'h1', 'Foo' ) . $this->makeSectionHtml( 1, '<h2 class="in-block">Bar</h2>Baz' ) . $this->makeSectionHeading( 'h1', 'Qux', 2 ) . $this->makeSectionHtml( 2, 'Quux' ), ], ]; } /** * @see https://phabricator.wikimedia.org/T137375 * @covers MobileFormatter::filterContent */ public function testT137375() { $input = '<p>Hello, world!</p><h2>Section heading</h2><ol class="references"></ol>'; $formatter = new MobileFormatter( $input, Title::newFromText( 'Special:Foo' ) ); $formatter->filterContent( false, true, false ); // Success is not crashing when the input is not a DOMElement. $this->assertTrue( true ); } /** * @see https://phabricator.wikimedia.org/T149884 * @dataProvider provideLoggingOfInfoboxesBeingWrappedInContainersWhenWrapped * @covers MobileFormatter::filterContent * @param string $input */ public function testLoggingOfInfoboxesBeingWrappedInContainersWhenWrapped( $input ) { $this->setMwGlobals( [ 'wgMFLogWrappedInfoboxes' => true ] ); $title = 'T149884'; $formatter = new MobileFormatter( MobileFormatter::wrapHTML( $input ), Title::newFromText( $title, NS_MAIN ) ); $formatter->enableExpandableSections(); $loggerMock = $this->getMock( \Psr\Log\LoggerInterface::class ); $loggerMock->expects( $this->once() ) ->method( 'info' ) ->will( $this->returnCallback( function ( $message ) use ( $title ) { // Debug message contains Page title $this->assertContains( $title, $message ); // and contains revision id which is 0 by default $this->assertContains( '0', $message ); } ) ); $this->setLogger( 'mobile', $loggerMock ); $formatter->filterContent( false, false, false, true ); } public function provideLoggingOfInfoboxesBeingWrappedInContainersWhenWrapped() { $box = $this->buildInfoboxHTML( 'infobox' ); return [ // wrapped once [ "<div>$box</div>" ], // wrapped twice [ "<div><p>$box</p></div>" ], // wrapped multiple times [ "<div><div><p><span><div><p>Test</p>$box</div></span></p></div></div>" ] ]; } /** * @see https://phabricator.wikimedia.org/T149884 * @covers MobileFormatter::filterContent * @covers MobileFrontend\Transforms\MoveLeadParagraphTransform::logInfoboxesWrappedInContainers * @dataProvider provideLoggingOfInfoboxesBeingWrappedInContainersWhenNotWrapped */ public function testLoggingOfInfoboxesBeingWrappedInContainersWhenNotWrapped( $input ) { $this->setMwGlobals( [ 'wgMFLogWrappedInfoboxes' => true ] ); $title = 'T149884'; $formatter = new MobileFormatter( MobileFormatter::wrapHTML( $input ), Title::newFromText( $title ) ); $formatter->enableExpandableSections(); $loggerMock = $this->getMock( \Psr\Log\LoggerInterface::class ); $loggerMock->expects( $this->never() ) ->method( 'info' ); $this->setLogger( 'mobile', $loggerMock ); $formatter->filterContent( false, false, false, true ); } public function provideLoggingOfInfoboxesBeingWrappedInContainersWhenNotWrapped() { $box = $this->buildInfoboxHTML( 'infobox' ); return [ // no wrapping [ $box ], // Although the box is wrapped, it comes over the first paragraph so isn't a problem (T196767) [ "<p>First para</p><div>$box</div>" ], // Although the box is wrapped, it comes over the first paragraph so isn't a problem (T196767) [ "<p>First para</p><div><div><div>$box</div></div></div>" ], // if wrapped inside mw-stack no logging occurs [ "<div class=\"mw-stack\">$box</div>" ], ]; } /** * @see https://phabricator.wikimedia.org/T163805 * @covers MobileFormatter::filterContent */ public function testLoggingOfInfoboxesSkipsInfoBoxInsideInfobox() { $this->setMwGlobals( [ 'wgMFLogWrappedInfoboxes' => true ] ); // wrapped inside different infobox $input = $this->buildInfoboxHTML( $this->buildInfoboxHTML( 'test' ) ); $title = 'T163805'; $formatter = new MobileFormatter( MobileFormatter::wrapHTML( $input ), Title::newFromText( $title, NS_MAIN ) ); $formatter->enableExpandableSections(); $loggerMock = $this->getMock( \Psr\Log\LoggerInterface::class ); $loggerMock->expects( $this->never() ) ->method( 'info' ); $this->setLogger( 'mobile', $loggerMock ); $formatter->filterContent( false, false, false, true ); } /** * Helper function to create an infobox with given content * * @param string $content * @return string built HTML */ private function buildInfoboxHTML( $content ) { return "<table class=\"" . self::INFOBOX_CLASSNAME . "\"><tr><td>" . $content . "</td></tr></table>"; } }