Merge "Give skins more flexibility over table of contents render"
This commit is contained in:
commit
7766b0206e
9 changed files with 438 additions and 25 deletions
|
|
@ -1644,7 +1644,7 @@ class Linker {
|
|||
'class' => 'toctogglelabel',
|
||||
] )
|
||||
. '</span>'
|
||||
. "</div>\n"
|
||||
. "</div>"
|
||||
. $toc
|
||||
. "</ul>\n</div>\n";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,6 +103,11 @@ class OutputPage extends ContextSource {
|
|||
*/
|
||||
private $mPrintable = false;
|
||||
|
||||
/**
|
||||
* @var array sections from ParserOutput
|
||||
*/
|
||||
private $mSections = [];
|
||||
|
||||
/**
|
||||
* @var array Contains the page subtitle. Special pages usually have some
|
||||
* links here. Don't confuse with site subtitle added by skins.
|
||||
|
|
@ -1880,10 +1885,28 @@ class OutputPage extends ContextSource {
|
|||
] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds sections to OutputPage from ParserOutput
|
||||
* @param array $sections
|
||||
* @internal For use by Article.php
|
||||
*/
|
||||
public function setSections( array $sections ) {
|
||||
$this->mSections = $sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal For usage in Skin::getSectionsData() only.
|
||||
* @return array Array of sections.
|
||||
* Empty if OutputPage::setSections() has not been called.
|
||||
*/
|
||||
public function getSections(): array {
|
||||
return $this->mSections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all metadata associated with a ParserOutput object, but without the actual HTML. This
|
||||
* includes categories, language links, ResourceLoader modules, effects of certain magic words,
|
||||
* and so on.
|
||||
* and so on. It does *not* include section information.
|
||||
*
|
||||
* @since 1.24
|
||||
* @param ParserOutput $parserOutput
|
||||
|
|
@ -1893,13 +1916,18 @@ class OutputPage extends ContextSource {
|
|||
array_merge( $this->mLanguageLinks, $parserOutput->getLanguageLinks() );
|
||||
$this->addCategoryLinks( $parserOutput->getCategories() );
|
||||
$this->setIndicators( $parserOutput->getIndicators() );
|
||||
|
||||
// FIXME: Best practice is for OutputPage to be an accumulator, as
|
||||
// addParserOutputMetadata() may be called multiple times, but the
|
||||
// following lines overwrite any previous data. These should
|
||||
// be migrated to an injection pattern.
|
||||
$this->mNewSectionLink = $parserOutput->getNewSection();
|
||||
$this->mHideNewSectionLink = $parserOutput->getHideNewSection();
|
||||
$this->mNoGallery = $parserOutput->getNoGallery();
|
||||
|
||||
if ( !$parserOutput->isCacheable() ) {
|
||||
$this->enableClientCache( false );
|
||||
}
|
||||
$this->mNoGallery = $parserOutput->getNoGallery();
|
||||
$this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
|
||||
$this->addModules( $parserOutput->getModules() );
|
||||
$this->addModuleStyles( $parserOutput->getModuleStyles() );
|
||||
|
|
@ -1969,6 +1997,9 @@ class OutputPage extends ContextSource {
|
|||
}
|
||||
|
||||
// Include parser limit report
|
||||
// FIXME: This should append, rather than overwrite, or else this
|
||||
// data should be injected into the OutputPage like is done for the
|
||||
// other page-level things (like OutputPage::setSections()).
|
||||
if ( !$this->limitReportJSData ) {
|
||||
$this->limitReportJSData = $parserOutput->getLimitReportJSData();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -491,12 +491,17 @@ class ApiParse extends ApiBase {
|
|||
}
|
||||
|
||||
if ( isset( $prop['text'] ) ) {
|
||||
$skin = $context ? $context->getSkin() : null;
|
||||
$options = $skin ? $skin->getOptions() : [
|
||||
'toc' => true,
|
||||
];
|
||||
$result_array['text'] = $p_result->getText( [
|
||||
'allowTOC' => !$params['disabletoc'],
|
||||
'injectTOC' => $options['toc'],
|
||||
'enableSectionEditLinks' => !$params['disableeditsection'],
|
||||
'wrapperDivClass' => $params['wrapoutputclass'],
|
||||
'deduplicateStyles' => !$params['disablestylededuplication'],
|
||||
'skin' => $context ? $context->getSkin() : null,
|
||||
'skin' => $skin,
|
||||
] );
|
||||
$result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
|
||||
if ( $context ) {
|
||||
|
|
|
|||
|
|
@ -744,8 +744,13 @@ class Article implements Page {
|
|||
# Ensure that UI elements requiring revision ID have
|
||||
# the correct version information.
|
||||
$outputPage->setRevisionId( $pOutput->getCacheRevisionId() ?? $this->getRevIdFetched() );
|
||||
|
||||
$outputPage->addParserOutput( $pOutput, $textOptions );
|
||||
# Ensure that the skin has the necessary ToC information
|
||||
# (and do this before OutputPage::addParserOutput() calls the
|
||||
# OutputPageParserOutput hook)
|
||||
$outputPage->setSections( $pOutput->getSections() );
|
||||
$outputPage->addParserOutput( $pOutput, $textOptions + [
|
||||
'injectTOC' => true,
|
||||
] );
|
||||
# Preload timestamp to avoid a DB hit
|
||||
$cachedTimestamp = $pOutput->getTimestamp();
|
||||
if ( $cachedTimestamp !== null ) {
|
||||
|
|
|
|||
|
|
@ -149,10 +149,46 @@ class Parser {
|
|||
public const MARKER_SUFFIX = "-QINU`\"'\x7f";
|
||||
public const MARKER_PREFIX = "\x7f'\"`UNIQ-";
|
||||
|
||||
# Markers used for wrapping the table of contents
|
||||
/**
|
||||
* Internal Markers used for wrapping the table of contents.
|
||||
*
|
||||
* The use of the `mw:` prefix makes sure that the table of contents is
|
||||
* identified as a block element, and prevents the introduction of `p` tags
|
||||
* wrapping the table of contents; see BlockLevelPass.
|
||||
*
|
||||
* @var string
|
||||
* @deprecated since 1.38. These markers are used in old cached
|
||||
* content but not generated from the current parser (or from Parsoid).
|
||||
* The constants will be removed in a future MediaWiki release.
|
||||
*/
|
||||
public const TOC_START = '<mw:toc>';
|
||||
|
||||
/**
|
||||
* See ::TOC_START
|
||||
* @var string
|
||||
* @deprecated since 1.38. See ::TOC_START
|
||||
*/
|
||||
public const TOC_END = '</mw:toc>';
|
||||
|
||||
/**
|
||||
* Internal marker used by parser to track where the table of
|
||||
* contents should be. Various magic words can change the position
|
||||
* during the parse. The table of contents is generated during
|
||||
* the parse, however skins have the final decision on whether the
|
||||
* table of contents is injected. This placeholder element
|
||||
* identifies where in the page the table of contents should be
|
||||
* injected, if at all.
|
||||
* @var string
|
||||
* @see Keep this in sync with BlockLevelPass::execute() and
|
||||
* RemexCompatMunger::isTableOfContentsMarker()
|
||||
* @internal This will be made private as soon as old content
|
||||
* has expired from the cache (at the moment it is needed in
|
||||
* ParserOutput for a compatibility fallback). Skins should
|
||||
* *not* directly reference TOC_PLACEHOLDER but instead use
|
||||
* Parser::replaceTableOfContentsMarker().
|
||||
*/
|
||||
public const TOC_PLACEHOLDER = '<mw:tocplace></mw:tocplace>';
|
||||
|
||||
# Persistent:
|
||||
private $mTagHooks = [];
|
||||
private $mFunctionHooks = [];
|
||||
|
|
@ -4049,7 +4085,7 @@ class Parser {
|
|||
$this->mForceTocPosition = true;
|
||||
|
||||
# Set a placeholder. At the end we'll fill it in with the TOC.
|
||||
$text = $mw->replace( '<!--MWTOC\'"-->', $text, 1 );
|
||||
$text = $mw->replace( self::TOC_PLACEHOLDER, $text, 1 );
|
||||
|
||||
# Only keep the first one.
|
||||
$text = $mw->replace( '', $text );
|
||||
|
|
@ -4457,7 +4493,6 @@ class Parser {
|
|||
}
|
||||
$toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
|
||||
$this->mOutput->setTOCHTML( $toc );
|
||||
$toc = self::TOC_START . $toc . self::TOC_END;
|
||||
}
|
||||
|
||||
if ( $isMain ) {
|
||||
|
|
@ -4496,16 +4531,12 @@ class Parser {
|
|||
if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
|
||||
// append the TOC at the beginning
|
||||
// Top anchor now in skin
|
||||
$sections[0] .= $toc . "\n";
|
||||
$sections[0] .= self::TOC_PLACEHOLDER . "\n";
|
||||
}
|
||||
|
||||
$full .= implode( '', $sections );
|
||||
|
||||
if ( $this->mForceTocPosition ) {
|
||||
return str_replace( '<!--MWTOC\'"-->', $toc, $full );
|
||||
} else {
|
||||
return $full;
|
||||
}
|
||||
return $full;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -4766,6 +4797,30 @@ class Parser {
|
|||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace table of contents marker in parsed HTML.
|
||||
*
|
||||
* Used to remove or replace the marker. This method should be
|
||||
* used instead of direct access to Parser::TOC_PLACEHOLDER, since
|
||||
* in the future the placeholder might have additional attributes
|
||||
* attached which should be ignored when the replacement is made.
|
||||
*
|
||||
* @since 1.38
|
||||
* @stable
|
||||
*
|
||||
* @param string $text Parsed HTML
|
||||
* @param string $toc HTML table of contents string, or else an empty
|
||||
* string to remove the marker.
|
||||
* @return string Result HTML
|
||||
*/
|
||||
public static function replaceTableOfContentsMarker( $text, $toc ) {
|
||||
return str_replace(
|
||||
self::TOC_PLACEHOLDER,
|
||||
$toc,
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up some variables which are usually set up in parse()
|
||||
* so that an external function can call some class members with confidence
|
||||
|
|
|
|||
|
|
@ -333,6 +333,10 @@ class ParserOutput extends CacheTime {
|
|||
* - allowTOC: (bool) Show the TOC, assuming there were enough headings
|
||||
* to generate one and `__NOTOC__` wasn't used. Default is true,
|
||||
* but might be statefully overridden.
|
||||
* - injectTOC: (bool) Replace the TOC_PLACEHOLDER with TOC contents;
|
||||
* otherwise the marker will be left in the article (and the skin
|
||||
* will be responsible for replacing or removing it). Default is
|
||||
* true.
|
||||
* - enableSectionEditLinks: (bool) Include section edit links, assuming
|
||||
* section edit link tokens are present in the HTML. Default is true,
|
||||
* but might be statefully overridden.
|
||||
|
|
@ -356,6 +360,7 @@ class ParserOutput extends CacheTime {
|
|||
public function getText( $options = [] ) {
|
||||
$options += [
|
||||
'allowTOC' => true,
|
||||
'injectTOC' => true,
|
||||
'enableSectionEditLinks' => true,
|
||||
'skin' => null,
|
||||
'unwrap' => false,
|
||||
|
|
@ -371,7 +376,9 @@ class ParserOutput extends CacheTime {
|
|||
}
|
||||
|
||||
if ( $options['enableSectionEditLinks'] ) {
|
||||
// TODO: Passing the skin should be required
|
||||
// TODO: Skin should not be required.
|
||||
// It would be better to define one or more narrow interfaces to use here,
|
||||
// so this code doesn't have to depend on all of Skin.
|
||||
$skin = $options['skin'] ?: RequestContext::getMain()->getSkin();
|
||||
|
||||
$text = preg_replace_callback(
|
||||
|
|
@ -409,8 +416,32 @@ class ParserOutput extends CacheTime {
|
|||
}
|
||||
|
||||
if ( $options['allowTOC'] ) {
|
||||
$text = str_replace( [ Parser::TOC_START, Parser::TOC_END ], '', $text );
|
||||
if ( $options['injectTOC'] ) {
|
||||
// XXX Use DI to inject this once ::getText() is moved out
|
||||
// of ParserOutput.
|
||||
$tidy = MediaWikiServices::getInstance()->getTidy();
|
||||
$toc = $tidy->tidy(
|
||||
$this->getTOCHTML(),
|
||||
[ Sanitizer::class, 'armorFrenchSpaces' ]
|
||||
);
|
||||
$text = Parser::replaceTableOfContentsMarker( $text, $toc );
|
||||
// The line below can be removed once old content has expired
|
||||
// from the parser cache
|
||||
$text = str_replace( [ Parser::TOC_START, Parser::TOC_END ], '', $text );
|
||||
} else {
|
||||
// The line below can be removed once old content has expired
|
||||
// from the parser cache (and Parser::TOC_PLACEHOLDER should
|
||||
// then be made private)
|
||||
$text = preg_replace(
|
||||
'#' . preg_quote( Parser::TOC_START, '#' ) . '.*?' . preg_quote( Parser::TOC_END, '#' ) . '#s',
|
||||
Parser::TOC_PLACEHOLDER,
|
||||
$text
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$text = Parser::replaceTableOfContentsMarker( $text, '' );
|
||||
// The line below can be removed once old content has expired
|
||||
// from the parser cache
|
||||
$text = preg_replace(
|
||||
'#' . preg_quote( Parser::TOC_START, '#' ) . '.*?' . preg_quote( Parser::TOC_END, '#' ) . '#s',
|
||||
'',
|
||||
|
|
@ -634,6 +665,9 @@ class ParserOutput extends CacheTime {
|
|||
return $this->mTitleText;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getSections() {
|
||||
return $this->mSections;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,40 @@ abstract class Skin extends ContextSource {
|
|||
return self::VERSION_MAJOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enriches section data with has-subsections and is-last-subsection
|
||||
* properties so that the table of contents can be rendered in Mustache.
|
||||
*
|
||||
* Example Mustache template code:
|
||||
* <ul>
|
||||
* {{#array-sections}}
|
||||
* <li>{{number}} {{line}}
|
||||
* {{#has-subsections}}<ul>{{/has-subsections}}
|
||||
* {{^has-subsections}}</li>{{/has-subsections}}
|
||||
* {{#is-last-item}}</ul>{{/is-last-item}}
|
||||
* {{/array-sections}}
|
||||
* </ul>
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getSectionsData(): array {
|
||||
$sections = $this->getOutput()->getSections();
|
||||
$data = [];
|
||||
$parent = null;
|
||||
$lastLevel = 0;
|
||||
foreach ( $sections as $i => $section ) {
|
||||
$nextSection = $sections[$i + 1] ?? null;
|
||||
$level = $section['toclevel'];
|
||||
|
||||
$data[] = $section + [
|
||||
'has-subsections' => $nextSection !== null && $nextSection['toclevel'] > $level,
|
||||
'is-last-item' => $nextSection === null || $nextSection['toclevel'] < $level,
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses may extend this method to add additional
|
||||
* template data.
|
||||
|
|
@ -113,6 +147,9 @@ abstract class Skin extends ContextSource {
|
|||
$out = $this->getOutput();
|
||||
$user = $this->getUser();
|
||||
$data = [
|
||||
// Array values
|
||||
'array-sections' => $this->getSectionsData(),
|
||||
|
||||
// Boolean values
|
||||
'is-anon' => $user->isAnon(),
|
||||
'is-article' => $out->isArticle(),
|
||||
|
|
@ -185,6 +222,10 @@ abstract class Skin extends ContextSource {
|
|||
* tag can be set on the skin. Note, users can disable this feature via user
|
||||
* preference.
|
||||
* `link` an array of link options that will be used in makeLink calls. See Skin::makeLink
|
||||
* `toc` Whether a table of contents is included in the main article
|
||||
* content area. It defaults to `true`, but if your skins wants to
|
||||
* place a table of contents elsewhere (for example, in a sidebar),
|
||||
* set `toc` to `false`.
|
||||
*/
|
||||
public function __construct( $options = null ) {
|
||||
if ( is_string( $options ) ) {
|
||||
|
|
@ -199,6 +240,7 @@ abstract class Skin extends ContextSource {
|
|||
if ( isset( $options['link'] ) ) {
|
||||
$this->defaultLinkOptions = $options['link'];
|
||||
}
|
||||
// Defaults are set in Skin::getOptions()
|
||||
$this->options = $options;
|
||||
$this->skinname = $name;
|
||||
}
|
||||
|
|
@ -2578,8 +2620,12 @@ abstract class Skin extends ContextSource {
|
|||
* @internal
|
||||
* @return array Skin options passed into constructor
|
||||
*/
|
||||
public function getOptions(): array {
|
||||
return $this->options;
|
||||
final public function getOptions(): array {
|
||||
return $this->options + [
|
||||
// Whether the table of contents will be inserted on page views
|
||||
// See ParserOutput::getText() for the implementation logic
|
||||
'toc' => true,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -195,6 +195,7 @@ class ParserOutputTest extends MediaWikiLangTestCase {
|
|||
/**
|
||||
* @covers ParserOutput::getText
|
||||
* @dataProvider provideGetText
|
||||
* @dataProvider provideGetTextBackCompat
|
||||
* @param array $options Options to getText()
|
||||
* @param string $text Parser text
|
||||
* @param string $expect Expected output
|
||||
|
|
@ -207,16 +208,15 @@ class ParserOutputTest extends MediaWikiLangTestCase {
|
|||
] );
|
||||
|
||||
$po = new ParserOutput( $text );
|
||||
$po->setTOCHTML( self::provideGetTextToC() );
|
||||
$actual = $po->getText( $options );
|
||||
$this->assertSame( $expect, $actual );
|
||||
}
|
||||
|
||||
public static function provideGetText() {
|
||||
public static function provideGetTextToC() {
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
$text = <<<EOF
|
||||
<p>Test document.
|
||||
</p>
|
||||
<mw:toc><div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
|
||||
$toc = <<<EOF
|
||||
<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></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>
|
||||
|
|
@ -227,7 +227,124 @@ class ParserOutputTest extends MediaWikiLangTestCase {
|
|||
<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>
|
||||
</mw:toc>
|
||||
|
||||
EOF;
|
||||
return $toc;
|
||||
}
|
||||
|
||||
// REMOVE THIS ONCE Parser::TOC_START IS REMOVED
|
||||
public static function provideGetTextBackCompat() {
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
$toc = self::provideGetTextToc();
|
||||
$text = <<<EOF
|
||||
<p>Test document.
|
||||
</p>
|
||||
<mw:toc>$toc</mw: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;
|
||||
|
||||
return [
|
||||
'No options (mw:toc)' => [
|
||||
[], $text, <<<EOF
|
||||
<p>Test document.
|
||||
</p>
|
||||
<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></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&action=edit&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&action=edit&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&action=edit&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 (mw:toc)' => [
|
||||
[ 'enableSectionEditLinks' => false ], $text, <<<EOF
|
||||
<p>Test document.
|
||||
</p>
|
||||
<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></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 (mw:toc)' => [
|
||||
[ 'allowTOC' => false, 'wrapperDivClass' => 'mw-parser-output' ], $text, <<<EOF
|
||||
<div class="mw-parser-output"><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&action=edit&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&action=edit&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&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
|
||||
<p>Three
|
||||
</p></div>
|
||||
EOF
|
||||
],
|
||||
];
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
public static function provideGetText() {
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
$toc = self::provideGetTextToc();
|
||||
$text = <<<EOF
|
||||
<p>Test document.
|
||||
</p>
|
||||
<mw:tocplace></mw:tocplace>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -205,6 +205,126 @@ class SkinTemplateTest extends MediaWikiIntegrationTestCase {
|
|||
);
|
||||
}
|
||||
|
||||
public function provideGetSectionsData(): array {
|
||||
$END_SUBSECTION = [
|
||||
'is-last-item' => true,
|
||||
];
|
||||
$NOT_END_SUBSECTION = [
|
||||
'is-last-item' => false,
|
||||
];
|
||||
$WITH_SUBSECTIONS = [
|
||||
'has-subsections' => true,
|
||||
];
|
||||
$WITHOUT_SUBSECTIONS = [
|
||||
'has-subsections' => false,
|
||||
];
|
||||
// byteoffset and fromtitle are redacted from this test.
|
||||
$SECTION_1 = [
|
||||
'toclevel' => 1,
|
||||
'line' => 'Section 1',
|
||||
'anchor' => 'section_1',
|
||||
];
|
||||
$SECTION_1_1 = [
|
||||
'toclevel' => 2,
|
||||
'line' => 'Section 1.1',
|
||||
'anchor' => 'section_1_1',
|
||||
];
|
||||
$SECTION_1_2 = [
|
||||
'toclevel' => 2,
|
||||
'line' => 'Section 1.2',
|
||||
'anchor' => 'section_1_2',
|
||||
];
|
||||
$SECTION_1_2_1 = [
|
||||
'toclevel' => 3,
|
||||
'line' => 'Section 1.2.1',
|
||||
'anchor' => 'section_1_2_1',
|
||||
];
|
||||
$SECTION_1_3 = [
|
||||
'toclevel' => 2,
|
||||
'line' => 'Section 1.3',
|
||||
'anchor' => 'section_1_3',
|
||||
];
|
||||
$SECTION_2 = [
|
||||
'toclevel' => 1,
|
||||
'line' => 'Section 2',
|
||||
'anchor' => 'section_2',
|
||||
];
|
||||
|
||||
return [
|
||||
[
|
||||
// sections data
|
||||
[],
|
||||
[]
|
||||
],
|
||||
[
|
||||
[
|
||||
$SECTION_1,
|
||||
$SECTION_2,
|
||||
],
|
||||
[
|
||||
$SECTION_1 + $NOT_END_SUBSECTION + $WITHOUT_SUBSECTIONS,
|
||||
$SECTION_2 + $END_SUBSECTION + $WITHOUT_SUBSECTIONS,
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
$SECTION_1,
|
||||
$SECTION_1_1,
|
||||
$SECTION_2,
|
||||
],
|
||||
[
|
||||
$SECTION_1 + $NOT_END_SUBSECTION + $WITH_SUBSECTIONS,
|
||||
$SECTION_1_1 + $END_SUBSECTION + $WITHOUT_SUBSECTIONS,
|
||||
$SECTION_2 + $END_SUBSECTION + $WITHOUT_SUBSECTIONS,
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
$SECTION_1,
|
||||
$SECTION_1_1,
|
||||
$SECTION_1_2,
|
||||
$SECTION_1_2_1,
|
||||
$SECTION_1_3,
|
||||
$SECTION_2,
|
||||
],
|
||||
[
|
||||
$SECTION_1 + $NOT_END_SUBSECTION + $WITH_SUBSECTIONS,
|
||||
$SECTION_1_1 + $NOT_END_SUBSECTION + $WITHOUT_SUBSECTIONS,
|
||||
$SECTION_1_2 + $NOT_END_SUBSECTION + $WITH_SUBSECTIONS,
|
||||
$SECTION_1_2_1 + $END_SUBSECTION + $WITHOUT_SUBSECTIONS,
|
||||
$SECTION_1_3 + $END_SUBSECTION + $WITHOUT_SUBSECTIONS,
|
||||
$SECTION_2 + $END_SUBSECTION + $WITHOUT_SUBSECTIONS,
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Skin::getSectionsData
|
||||
* @dataProvider provideGetSectionsData
|
||||
*
|
||||
* @param array $sectionsData
|
||||
* @param array $expected
|
||||
*/
|
||||
public function testGetSectionsData( $sectionsData, $expected ) {
|
||||
$skin = new SkinTemplate();
|
||||
$context = new DerivativeContext( $skin->getContext() );
|
||||
$mock = $this->createMock( OutputPage::class );
|
||||
$mock->expects( $this->any() )
|
||||
->method( 'getSections' )
|
||||
->willReturn( $sectionsData );
|
||||
|
||||
$reflectionMethod = new ReflectionMethod( Skin::class, 'getSectionsData' );
|
||||
$reflectionMethod->setAccessible( true );
|
||||
|
||||
$context->setOutput( $mock );
|
||||
$skin->setContext( $context );
|
||||
$data = $reflectionMethod->invoke(
|
||||
$skin
|
||||
);
|
||||
$this->assertEquals( $data, $expected );
|
||||
}
|
||||
|
||||
public function provideContentNavigation(): array {
|
||||
return [
|
||||
'No userpage set' => [
|
||||
|
|
|
|||
Loading…
Reference in a new issue