Merge "Refactor page tools, main menu, and TOC components"

This commit is contained in:
jenkins-bot 2022-12-15 22:44:05 +00:00 committed by Gerrit Code Review
commit 9463861054
15 changed files with 467 additions and 310 deletions

View file

@ -19,55 +19,52 @@ class VectorComponentMainMenu implements VectorComponent {
private $languageData;
/** @var MessageLocalizer */
private $localizer;
/** @var User */
private $user;
/** @var VectorComponentPinnableHeader|null */
private $pinnableHeader;
/** @var string */
public const ID = 'vector-main-menu';
/**
* @param array $sidebarData
* @param Skin $skin
* @param bool $shouldLanguageAlertBeInSidebar
* @param array $languageData
* @param MessageLocalizer $localizer
* @param User $user
* @param Skin $skin
*/
public function __construct(
array $sidebarData,
Skin $skin,
bool $shouldLanguageAlertBeInSidebar,
array $languageData
array $languageData,
MessageLocalizer $localizer,
User $user,
Skin $skin
) {
$this->sidebarData = $sidebarData;
$this->localizer = $skin->getContext();
$this->languageData = $languageData;
$user = $skin->getUser();
$this->user = $user;
$this->localizer = $localizer;
if ( $user->isRegistered() ) {
$this->optOut = new VectorComponentMainMenuActionOptOut( $skin );
$this->pinnableHeader = new VectorComponentPinnableHeader(
$this->localizer,
false,
self::ID,
null
);
}
if ( $shouldLanguageAlertBeInSidebar ) {
$this->alert = new VectorComponentMainMenuActionLanguageSwitchAlert( $skin );
}
}
/**
* @return User
*/
private function getUser(): User {
return $this->user;
}
/**
* @inheritDoc
*/
public function getTemplateData(): array {
$action = $this->optOut;
$alert = $this->alert;
$id = 'vector-main-menu';
$pinnableHeader = new VectorComponentPinnableHeader(
$this->localizer,
false,
$id,
null
);
$pinnableHeader = $this->pinnableHeader;
$portletsRest = [];
foreach ( $this->sidebarData[ 'array-portlets-rest' ] as $data ) {
@ -81,7 +78,7 @@ class VectorComponentMainMenu implements VectorComponent {
'data-main-menu-action' => $action ? $action->getTemplateData() : null,
// T295555 Add language switch alert message temporarily (to be removed).
'data-vector-language-switch-alert' => $alert ? $alert->getTemplateData() : null,
'data-pinnable-header' => $pinnableHeader->getTemplateData(),
'data-pinnable-header' => $pinnableHeader ? $pinnableHeader->getTemplateData() : null,
'data-languages' => $languageMenu->getTemplateData(),
];
}

View file

@ -1,6 +1,8 @@
<?php
namespace MediaWiki\Skins\Vector\Components;
use MediaWiki\Skins\Vector\Constants;
use MediaWiki\Skins\Vector\FeatureManagement\FeatureManager;
use MessageLocalizer;
use User;
@ -12,6 +14,9 @@ class VectorComponentPageTools implements VectorComponent {
/** @var array */
private $menus;
/** @var MessageLocalizer */
private $localizer;
/** @var bool */
private $isPinned;
@ -27,29 +32,26 @@ class VectorComponentPageTools implements VectorComponent {
/** @var string */
private const ACTIONS_ID = 'p-cactions';
/** @var MessageLocalizer */
private $localizer;
/**
* @param array $menus
* @param bool $isPinned
* @param MessageLocalizer $localizer
* @param User $user
* @param FeatureManager $featureManager
*/
public function __construct(
array $menus,
bool $isPinned,
MessageLocalizer $localizer,
User $user
User $user,
FeatureManager $featureManager
) {
$this->menus = $menus;
$this->isPinned = $isPinned;
$this->localizer = $localizer;
$this->isPinned = $featureManager->isFeatureEnabled( Constants::FEATURE_PAGE_TOOLS_PINNED );
$this->pinnableHeader = $user->isRegistered() ? new VectorComponentPinnableHeader(
$localizer,
$isPinned,
$this->isPinned,
// Name
'vector-page-tools',
self::ID,
// Feature name
'page-tools-pinned'
) : null;
@ -64,10 +66,10 @@ class VectorComponentPageTools implements VectorComponent {
return array_map( function ( $menu ) {
switch ( $menu['id'] ?? '' ) {
case self::TOOLBOX_ID:
$menu['label'] = $this->localizer->msg( 'vector-page-tools-general-label' );
$menu['label'] = $this->localizer->msg( 'vector-page-tools-general-label' )->text();
break;
case self::ACTIONS_ID:
$menu['label'] = $this->localizer->msg( 'vector-page-tools-actions-label' );
$menu['label'] = $this->localizer->msg( 'vector-page-tools-actions-label' )->text();
break;
}

View file

@ -1,18 +1,91 @@
<?php
namespace MediaWiki\Skins\Vector\Components;
use Config;
use MessageLocalizer;
/**
* VectorComponentTableOfContents component
*/
class VectorComponentTableOfContents implements VectorComponent {
/** @var array */
private $tocData;
/** @var MessageLocalizer */
private $localizer;
/** @var bool */
private $isPinned;
/** @var Config */
private $config;
/** @var VectorComponentPinnableHeader */
private $pinnableHeader;
/** @var string */
public const ID = 'vector-toc';
/**
* @param array $tocData
* @param MessageLocalizer $localizer
* @param Config $config
*/
public function __construct(
array $tocData,
MessageLocalizer $localizer,
Config $config
) {
$this->tocData = $tocData;
$this->localizer = $localizer;
// ToC is pinned by default, hardcoded for now
$this->isPinned = true;
$this->config = $config;
$this->pinnableHeader = new VectorComponentPinnableHeader(
$this->localizer,
$this->isPinned,
'vector-toc',
null,
false,
'h2'
);
}
/**
* In tableOfContents.js we have tableOfContents::getTableOfContentsSectionsData(),
* that yields the same result as this function, please make sure to keep them in sync.
* @inheritDoc
*/
public function getTemplateData(): array {
$pinnableElementName = 'vector-toc';
$pinnedContainer = new VectorComponentPinnedContainer( $pinnableElementName );
$pinnableElement = new VectorComponentPinnableElement( $pinnableElementName );
return $pinnableElement->getTemplateData() + $pinnedContainer->getTemplateData();
// If the table of contents has no items, we won't output it.
// empty array is interpreted by Mustache as falsey.
if ( empty( $this->tocData ) || empty( $this->tocData[ 'array-sections' ] ) ) {
return [];
}
// Populate button labels for collapsible TOC sections
foreach ( $this->tocData[ 'array-sections' ] as &$section ) {
if ( $section['is-top-level-section'] && $section['is-parent-section'] ) {
$section['vector-button-label'] =
$this->localizer->msg( 'vector-toc-toggle-button-label', $section['line'] )->text();
}
}
$pinnedContainer = new VectorComponentPinnedContainer( self::ID );
$pinnableElement = new VectorComponentPinnableElement( self::ID );
return $pinnableElement->getTemplateData() +
$pinnedContainer->getTemplateData() +
array_merge( $this->tocData, [
'is-vector-toc-beginning-enabled' => $this->config->get(
'VectorTableOfContentsBeginning'
),
'vector-is-collapse-sections-enabled' =>
$this->tocData[ 'number-section-count'] >= $this->config->get(
'VectorTableOfContentsCollapseAtCount'
),
'data-pinnable-header' => $this->pinnableHeader->getTemplateData(),
] );
}
}

View file

@ -35,8 +35,9 @@ use Wikimedia\Assert\Assert;
*
* @package MediaWiki\Skins\Vector\FeatureManagement
* @internal
* @final
*/
final class FeatureManager {
class FeatureManager {
/**
* A map of feature name to the array of requirements (referenced by name). A feature is only

View file

@ -285,7 +285,7 @@ abstract class SkinVector extends SkinMustache {
$returnto = $this->getReturnToParam();
$useCombinedLoginLink = $this->useCombinedLoginLink();
$userMenuOverflowData = Hooks::updateDropdownMenuData( $overflowMenuData );
$userMenu = $this->getUserMenuDropdown( $userMenuData );
$userMenuDropdown = $this->getUserMenuDropdown( $userMenuData );
unset( $userMenuOverflowData[ 'label' ] );
if ( $isAnon || $isTempUser ) {
@ -311,7 +311,7 @@ abstract class SkinVector extends SkinMustache {
'is-temp-user' => $isTempUser,
'is-wide' => $moreItems > 3,
'data-user-menu-overflow' => $userMenuOverflowData,
'data-user-menu' => $userMenu->getTemplateData(),
'data-user-menu-dropdown' => $userMenuDropdown->getTemplateData(),
'html-items' => $userMenuData['html-items'],
];
}
@ -371,7 +371,7 @@ abstract class SkinVector extends SkinMustache {
}
$btns[] = $this->getAddSectionButtonData();
// FIXME: Sync with SkinVector22:getTocData
// FIXME: Sync with VectorComponentTableOfContents:getTemplateData
$tocPortletData = Hooks::updateDropdownMenuData( [
'id' => 'vector-sticky-header-toc',
'class' => 'mw-portlet mw-portlet-sticky-header-toc vector-sticky-header-toc',
@ -530,7 +530,7 @@ abstract class SkinVector extends SkinMustache {
// completion of T319356.
//
// Also, add target class to apply different icon to personal menu dropdown for logged in users.
$portletData['class'] = 'mw-portlet mw-portlet-personal vector-user-menu vector-menu-dropdown';
$portletData['class'] = 'mw-portlet mw-portlet-personal vector-user-menu';
$portletData['class'] .= $this->loggedin ?
' vector-user-menu-logged-in' :
' vector-user-menu-logged-out';

View file

@ -6,7 +6,6 @@ use MediaWiki\MediaWikiServices;
use MediaWiki\Skins\Vector\Components\VectorComponentDropdown;
use MediaWiki\Skins\Vector\Components\VectorComponentMainMenu;
use MediaWiki\Skins\Vector\Components\VectorComponentPageTools;
use MediaWiki\Skins\Vector\Components\VectorComponentPinnableHeader;
use MediaWiki\Skins\Vector\Components\VectorComponentSearchBox;
use MediaWiki\Skins\Vector\Components\VectorComponentStickyHeader;
use MediaWiki\Skins\Vector\Components\VectorComponentTableOfContents;
@ -45,55 +44,6 @@ class SkinVector22 extends SkinVector {
$shouldShowOnMainPage;
}
/**
* Annotates table of contents data with Vector-specific information.
*
* In tableOfContents.js we have tableOfContents::getTableOfContentsSectionsData(),
* that yields the same result as this function, please make sure to keep them in sync.
* FIXME: This code should be moved to VectorComponentTableOfContents.
*
* @param array $tocData
* @return array
*/
private function getTocData( array $tocData ): array {
// If the table of contents has no items, we won't output it.
// empty array is interpreted by Mustache as falsey.
if ( empty( $tocData ) || empty( $tocData[ 'array-sections' ] ) ) {
return [];
}
// Populate button labels for collapsible TOC sections
foreach ( $tocData[ 'array-sections' ] as &$section ) {
if ( $section['is-top-level-section'] && $section['is-parent-section'] ) {
$section['vector-button-label'] =
$this->msg( 'vector-toc-toggle-button-label', $section['line'] )->text();
}
}
// ToC is pinned by default, hardcoded for now
$isTocPinned = true;
$pinnableHeader = new VectorComponentPinnableHeader(
$this->getContext(),
$isTocPinned,
'vector-toc',
null,
false,
'h2'
);
return array_merge( $tocData, [
'is-vector-toc-beginning-enabled' => $this->getConfig()->get(
'VectorTableOfContentsBeginning'
),
'vector-is-collapse-sections-enabled' =>
$tocData[ 'number-section-count'] >= $this->getConfig()->get(
'VectorTableOfContentsCollapseAtCount'
),
'is-pinned' => $isTocPinned,
'data-pinnable-header' => $pinnableHeader->getTemplateData(),
] );
}
/**
* @return array
*/
@ -188,11 +138,6 @@ class SkinVector22 extends SkinVector {
$featureManager = VectorServices::getFeatureManager();
$parentData = parent::getTemplateData();
$stickyHeader = new VectorComponentStickyHeader();
$toc = new VectorComponentTableOfContents();
$tocData = $parentData['data-toc'] ?? [];
$parentData['data-toc'] = !empty( $tocData ) ?
$toc->getTemplateData() + $this->getTocData( $tocData ) : null;
$parentData = $this->mergeViewOverflowIntoActions( $parentData );
// FIXME: Move to component (T322089)
@ -214,8 +159,26 @@ class SkinVector22 extends SkinVector {
);
}
$toc = new VectorComponentTableOfContents(
$parentData['data-toc'],
$this->getContext(),
$this->getConfig()
);
$config = $this->getConfig();
$searchBox = new VectorComponentSearchBox(
$parentData['data-search-box'],
true,
// is primary mode of search
true,
'searchform',
true,
$config,
Constants::SEARCH_BOX_INPUT_LOCATION_MOVED,
$this->getContext()
);
$isPageToolsEnabled = $featureManager->isFeatureEnabled( Constants::FEATURE_PAGE_TOOLS );
$isPageToolsPinned = $featureManager->isFeatureEnabled( Constants::FEATURE_PAGE_TOOLS_PINNED );
$sidebar = $parentData[ 'data-portlets-sidebar' ];
$pageToolsMenus = [];
$restPortlets = $parentData[ 'data-portlets-sidebar' ][ 'array-portlets-rest' ];
@ -236,43 +199,42 @@ class SkinVector22 extends SkinVector {
$sidebar = $parentData[ 'data-portlets-sidebar' ];
}
}
$config = $this->getConfig();
$mainMenuDropdownClass = $this->getUser()->isAnon() ? 'vector-main-menu-btn-dropdown-anon ' : '';
$mainMenuDropdownClass .= 'vector-main-menu-dropdown';
$isRegistered = $this->getUser()->isRegistered();
$mainMenu = new VectorComponentMainMenu(
$sidebar,
$this->shouldLanguageAlertBeInSidebar(),
$parentData['data-portlets']['data-languages'] ?? [],
$this->getContext(),
$this->getUser(),
$this,
);
$mainMenuDropdown = new VectorComponentDropdown(
'vector-main-menu-dropdown',
$this->msg( 'vector-main-menu-label' )->text(),
$mainMenuDropdownClass,
$mainMenu::ID . '-dropdown',
$this->msg( $mainMenu::ID . '-label' )->text(),
$mainMenu::ID . '-dropdown',
'menu'
);
$pageTools = new VectorComponentPageTools(
array_merge( [ $parentData['data-portlets']['data-actions'] ?? [] ], $pageToolsMenus ),
$this->getContext(),
$this->getUser(),
VectorServices::getFeatureManager()
);
$pageToolsDropdown = new VectorComponentDropdown(
$pageTools::ID . '-dropdown',
$this->msg( 'toolbox' )->text(),
$pageTools::ID . '-dropdown',
);
$components = [
'data-search-box' => new VectorComponentSearchBox(
$parentData['data-search-box'],
true,
// is primary mode of search
true,
'searchform',
true,
$config,
Constants::SEARCH_BOX_INPUT_LOCATION_MOVED,
$this->getContext()
),
'data-toc' => $toc,
'data-search-box' => $searchBox,
'data-portlets-main-menu' => $mainMenu,
'data-main-menu-dropdown' => $mainMenuDropdown,
'data-portlets-main-menu' => new VectorComponentMainMenu(
$sidebar,
$this,
$this->shouldLanguageAlertBeInSidebar(),
$parentData['data-portlets']['data-languages'] ?? [],
),
'data-page-tools' => $isPageToolsEnabled ? new VectorComponentPageTools(
array_merge( [ $parentData['data-portlets']['data-actions'] ?? [] ], $pageToolsMenus ),
$isPageToolsPinned,
$this->getContext(),
$this->getUser()
) : null,
'data-page-tools-dropdown' => $isPageToolsEnabled ?
new VectorComponentDropdown( 'vector-page-tools', $this->msg( 'toolbox' )->text() ) : null,
'data-page-tools' => $isPageToolsEnabled ? $pageTools : null,
'data-page-tools-dropdown' => $isPageToolsEnabled ? $pageToolsDropdown : null,
];
foreach ( $components as $key => $component ) {
// Array of components or null values.

View file

@ -5,7 +5,7 @@
<div id="{{id}}" class="vector-menu{{#class}} {{.}}{{/class}}" {{{html-tooltip}}} {{{html-user-language-attributes}}}>
{{#label}}
<div class="vector-menu-heading{{#label-class}} {{.}}{{/label-class}}">
{{{.}}}
{{.}}
</div>
{{/label}}
{{>MenuContents}}

View file

@ -1,7 +1,6 @@
<nav class="vector-user-links{{#is-wide}} vector-user-links-wide{{/is-wide}}" aria-label="{{msg-personaltools}}" role="navigation" >
{{#data-user-menu-overflow}}{{>Menu}}{{/data-user-menu-overflow}}
{{#data-user-menu}}
{{#data-user-menu-dropdown}}
{{>Dropdown/Open}}
{{#is-anon}}
{{#is-temp-user}}
@ -19,5 +18,5 @@
</div>
{{/is-anon}}
{{>Dropdown/Close}}
{{/data-user-menu}}
{{/data-user-menu-dropdown}}
</nav>

View file

@ -478,7 +478,8 @@ module.exports = function tableOfContents( props ) {
/**
* Prepares the data for rendering the table of contents,
* nesting child sections within their parent sections.
* This shoul yield the same result as the php function SkinVector22::getTocData(),
* This should yield the same result as the php function
* VectorComponentTableOfContents::getTemplateData(),
* please make sure to keep them in sync.
*
* @param {Section[]} sections

View file

@ -22,10 +22,3 @@
display: none;
}
}
// For anons the menu is not pinnable.
.vector-main-menu-btn-dropdown-anon {
.vector-pinnable-header {
display: none;
}
}

View file

@ -19,7 +19,6 @@ exports[`UserLinks renders 1`] = `
</div>
</div>
<div id=\\"p-personal\\" class=\\"vector-menu vector-dropdown vector-menu-dropdown mw-portlet mw-portlet-personal vector-user-menu vector-user-menu-logged-in vector-menu-dropdown\\">
<input type=\\"checkbox\\" id=\\"p-personal-checkbox\\" role=\\"button\\" aria-haspopup=\\"true\\" data-event-name=\\"ui.dropdown-p-personal\\" class=\\"vector-menu-checkbox\\">
<label id=\\"p-personal-label\\" for=\\"p-personal-checkbox\\" class=\\"vector-menu-heading\\">

View file

@ -26,7 +26,7 @@ const templateData = {
<li id="pt-watchlist-2" class="user-links-collapsible-item mw-list-item"><a href="/wiki/Special:Watchlist" class="mw-ui-button mw-ui-quiet mw-ui-icon mw-ui-icon-element mw-ui-icon-watchlist mw-ui-icon-wikimedia-watchlist" title="A list of pages you are monitoring for changes [⌃⌥l]" accesskey="l"><span>Watchlist</span></a></li>
`
},
'data-user-menu': {
'data-user-menu-dropdown': {
id: 'p-personal',
class: 'mw-portlet mw-portlet-personal vector-user-menu vector-user-menu-logged-in vector-menu-dropdown',
label: 'Personal tools',

View file

@ -47,163 +47,6 @@ class SkinVectorTest extends MediaWikiIntegrationTestCase {
return in_array( $search, $values );
}
public function provideGetTocData() {
$config = [
'VectorTableOfContentsBeginning' => true,
'VectorTableOfContentsCollapseAtCount' => 1
];
$tocData = [
'number-section-count' => 2,
'array-sections' => [
[
'toclevel' => 1,
'level' => '2',
'line' => 'A',
'number' => '1',
'index' => '1',
'fromtitle' => 'Test',
'byteoffset' => 231,
'anchor' => 'A',
'linkAnchor' => 'A',
'array-sections' => [],
'is-top-level-section' => true,
'is-parent-section' => false,
],
[
'toclevel' => 1,
'level' => '4',
'line' => 'B',
'number' => '2',
'index' => '2',
'fromtitle' => 'Test',
'byteoffset' => 245,
'anchor' => 'B',
'linkAnchor' => 'B',
'array-sections' => [],
'is-top-level-section' => true,
'is-parent-section' => false,
]
]
];
$nestedTocData = [
'number-section-count' => 2,
'array-sections' => [
[
'toclevel' => 1,
'level' => '2',
'line' => 'A',
'number' => '1',
'index' => '1',
'fromtitle' => 'Test',
'byteoffset' => 231,
'anchor' => 'A',
'linkAnchor' => 'A',
'vector-button-label' => '(vector-toc-toggle-button-label: A)',
'array-sections' => [
'toclevel' => 2,
'level' => '4',
'line' => 'A1',
'number' => '1.1',
'index' => '2',
'fromtitle' => 'Test',
'byteoffset' => 245,
'anchor' => 'A1',
'linkAnchor' => 'A1',
'array-sections' => [],
'is-top-level-section' => false,
'is-parent-section' => false,
],
'is-top-level-section' => true,
'is-parent-section' => true,
],
]
];
$expectedConfigData = [
'is-vector-toc-beginning-enabled' => $config[ 'VectorTableOfContentsBeginning' ],
'vector-is-collapse-sections-enabled' =>
$tocData[ 'number-section-count' ] >= $config[ 'VectorTableOfContentsCollapseAtCount' ],
'data-pinnable-header' => [
'is-pinned' => true,
'data-name' => 'vector-toc',
'data-feature-name' => null,
'label' => '(vector-toc-label)',
'unpin-label' => '(vector-unpin-element-label)',
'pin-label' => '(vector-pin-element-label)',
'label-tag-name' => 'h2'
],
'is-pinned' => true,
];
$expectedNestedTocData = array_merge( $nestedTocData, $expectedConfigData );
return [
// When zero sections
[
[],
$config,
// TOC data is empty when given an empty array
[]
],
// When number of multiple sections is lower than configured value
[
$tocData,
array_merge( $config, [ 'VectorTableOfContentsCollapseAtCount' => 3 ] ),
// 'vector-is-collapse-sections-enabled' value is false
array_merge( $tocData, $expectedConfigData, [
'vector-is-collapse-sections-enabled' => false
] )
],
// When number of multiple sections is equal to the configured value
[
$tocData,
array_merge( $config, [ 'VectorTableOfContentsCollapseAtCount' => 2 ] ),
// 'vector-is-collapse-sections-enabled' value is true
array_merge( $tocData, $expectedConfigData )
],
// When number of multiple sections is higher than configured value
[
$tocData,
array_merge( $config, [ 'VectorTableOfContentsCollapseAtCount' => 1 ] ),
// 'vector-is-collapse-sections-enabled' value is true
array_merge( $tocData, $expectedConfigData )
],
// When "Beginning" TOC section is configured to be turned off
[
$tocData,
array_merge( $config, [ 'VectorTableOfContentsBeginning' => false ] ),
// 'is-vector-toc-beginning-enabled' value is false
array_merge( $tocData, $expectedConfigData, [
'is-vector-toc-beginning-enabled' => false
] )
],
// When TOC has sections with top level parent sections
[
$nestedTocData,
$config,
// 'vector-button-label' is provided for top level parent sections
$expectedNestedTocData
],
];
}
/**
* @covers \MediaWiki\Skins\Vector\SkinVector22::getTocData
* @dataProvider provideGetTOCData
*/
public function testGetTocData(
array $tocData,
array $config,
array $expected
) {
$this->overrideConfigValues( $config );
$this->setUserLang( 'qqx' );
$skinVector = new SkinVector22( [ 'name' => 'vector-2022' ] );
$openSkinVector = TestingAccessWrapper::newFromObject( $skinVector );
$data = $openSkinVector->getTocData( $tocData );
$this->assertEquals( $expected, $data );
}
/**
* @covers \MediaWiki\Skins\Vector\SkinVector::getTemplateData
*/

View file

@ -0,0 +1,132 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @since 1.35
*/
namespace MediaWiki\Skins\Vector\Tests\Unit\Components;
use MediaWiki\Skins\Vector\Components\VectorComponentPageTools;
use MediaWiki\Skins\Vector\FeatureManagement\FeatureManager;
use Message;
use MessageLocalizer;
use User;
/**
* @group Vector
* @group Components
* @coversDefaultClass \MediaWiki\Skins\Vector\Components\VectorComponentPageTools
*/
class VectorComponentPageToolsTest extends \MediaWikiUnitTestCase {
public function provideConstructorData() {
$menus = [ [
'id' => 'p-cactions',
'array-items' => [ [
'id' => 'ca-delete',
'html-item' => "<li><a><span>Delete</span></a></li>"
] ]
], [
'id' => 'p-tb',
'array-items' => [ [
'id' => 't-whatlinkshere',
'html-item' => "<li><a><span>What links here</span></a></li>"
] ]
] ];
$expectedMenus = $menus;
$expectedMenus[ 0 ][ 'label' ] = 'vector-page-tools-actions-label';
$expectedMenus[ 1 ][ 'label' ] = 'vector-page-tools-general-label';
return [
[
$menus,
false,
false,
[
'id' => 'vector-page-tools',
'is-pinned' => false,
'data-pinnable-header' => null,
'data-menus' => $expectedMenus
]
], [
$menus,
false,
true,
[
'id' => 'vector-page-tools',
'is-pinned' => true,
'data-pinnable-header' => null,
'data-menus' => $expectedMenus
]
], [
$menus,
true,
false,
[
'id' => 'vector-page-tools',
'is-pinned' => false,
'data-pinnable-header' => [
'is-pinned' => false,
'label' => 'vector-page-tools-label',
'label-tag-name' => 'div',
'pin-label' => 'vector-pin-element-label',
'unpin-label' => 'vector-unpin-element-label',
'data-name' => 'vector-page-tools',
'data-feature-name' => 'page-tools-pinned',
'data-pinnable-element-id' => 'vector-page-tools-pinnable-element',
'data-unpinned-container-id' => 'vector-page-tools-unpinned-container',
'data-pinned-container-id' => 'vector-page-tools-pinned-container',
],
'data-menus' => $expectedMenus
]
]
];
}
/**
* @covers ::getTemplateData
* @dataProvider provideConstructorData
*/
public function testGetTemplateData(
array $menus,
bool $isRegistered,
bool $isPinned,
array $expected
) {
$localizer = $this->createMock( MessageLocalizer::class );
$localizer->method( 'msg' )->willReturnCallback( function ( $key, ...$params ) {
$msg = $this->createMock( Message::class );
$msg->method( '__toString' )->willReturn( $key );
$msg->method( 'text' )->willReturn( $key );
return $msg;
} );
$user = $this->createMock( User::class );
$user->method( 'isRegistered' )->willReturn( $isRegistered );
$featureManager = $this->createMock( FeatureManager::class );
$featureManager->method( 'isFeatureEnabled' )->willReturn( $isPinned );
$pageTools = new VectorComponentPageTools(
$menus,
$localizer,
$user,
$featureManager
);
$this->assertEquals( $expected, $pageTools->getTemplateData() );
}
}

View file

@ -21,7 +21,10 @@
namespace MediaWiki\Skins\Vector\Tests\Unit\Components;
use HashConfig;
use MediaWiki\Skins\Vector\Components\VectorComponentTableOfContents;
use Message;
use MessageLocalizer;
/**
* @group Vector
@ -29,17 +32,169 @@ use MediaWiki\Skins\Vector\Components\VectorComponentTableOfContents;
* @coversDefaultClass \MediaWiki\Skins\Vector\Components\VectorComponentTableOfContents
*/
class VectorComponentTableOfContentsTest extends \MediaWikiUnitTestCase {
public function provideGetTocData() {
$config = [
'VectorTableOfContentsBeginning' => true,
'VectorTableOfContentsCollapseAtCount' => 1
];
$tocData = [
'number-section-count' => 2,
'array-sections' => [
[
'toclevel' => 1,
'level' => '2',
'line' => 'A',
'number' => '1',
'index' => '1',
'fromtitle' => 'Test',
'byteoffset' => 231,
'anchor' => 'A',
'linkAnchor' => 'A',
'array-sections' => [],
'is-top-level-section' => true,
'is-parent-section' => false,
],
[
'toclevel' => 1,
'level' => '4',
'line' => 'B',
'number' => '2',
'index' => '2',
'fromtitle' => 'Test',
'byteoffset' => 245,
'anchor' => 'B',
'linkAnchor' => 'B',
'array-sections' => [],
'is-top-level-section' => true,
'is-parent-section' => false,
]
]
];
$nestedTocData = [
'number-section-count' => 2,
'array-sections' => [
[
'toclevel' => 1,
'level' => '2',
'line' => 'A',
'number' => '1',
'index' => '1',
'fromtitle' => 'Test',
'byteoffset' => 231,
'anchor' => 'A',
'linkAnchor' => 'A',
'vector-button-label' => 'vector-toc-toggle-button-label',
'array-sections' => [
'toclevel' => 2,
'level' => '4',
'line' => 'A1',
'number' => '1.1',
'index' => '2',
'fromtitle' => 'Test',
'byteoffset' => 245,
'anchor' => 'A1',
'linkAnchor' => 'A1',
'array-sections' => [],
'is-top-level-section' => false,
'is-parent-section' => false,
],
'is-top-level-section' => true,
'is-parent-section' => true,
],
]
];
$expectedConfigData = [
'is-vector-toc-beginning-enabled' => $config[ 'VectorTableOfContentsBeginning' ],
'vector-is-collapse-sections-enabled' =>
$tocData[ 'number-section-count' ] >= $config[ 'VectorTableOfContentsCollapseAtCount' ],
'data-pinnable-header' => [
'is-pinned' => true,
'data-name' => 'vector-toc',
'data-feature-name' => null,
'label' => 'vector-toc-label',
'unpin-label' => 'vector-unpin-element-label',
'pin-label' => 'vector-pin-element-label',
'label-tag-name' => 'h2'
],
'is-pinned' => true,
'id' => 'vector-toc'
];
$expectedNestedTocData = array_merge( $nestedTocData, $expectedConfigData );
return [
// When zero sections
[
[],
$config,
// TOC data is empty when given an empty array
[]
],
// When number of multiple sections is lower than configured value
[
$tocData,
array_merge( $config, [ 'VectorTableOfContentsCollapseAtCount' => 3 ] ),
// 'vector-is-collapse-sections-enabled' value is false
array_merge( $tocData, $expectedConfigData, [
'vector-is-collapse-sections-enabled' => false
] )
],
// When number of multiple sections is equal to the configured value
[
$tocData,
array_merge( $config, [ 'VectorTableOfContentsCollapseAtCount' => 2 ] ),
// 'vector-is-collapse-sections-enabled' value is true
array_merge( $tocData, $expectedConfigData )
],
// When number of multiple sections is higher than configured value
[
$tocData,
array_merge( $config, [ 'VectorTableOfContentsCollapseAtCount' => 1 ] ),
// 'vector-is-collapse-sections-enabled' value is true
array_merge( $tocData, $expectedConfigData )
],
// When "Beginning" TOC section is configured to be turned off
[
$tocData,
array_merge( $config, [ 'VectorTableOfContentsBeginning' => false ] ),
// 'is-vector-toc-beginning-enabled' value is false
array_merge( $tocData, $expectedConfigData, [
'is-vector-toc-beginning-enabled' => false
] )
],
// When TOC has sections with top level parent sections
[
$nestedTocData,
$config,
// 'vector-button-label' is provided for top level parent sections
$expectedNestedTocData
],
];
}
/**
* @covers ::getTemplateData
* @dataProvider provideGetTOCData
*/
public function testGetTemplateData() {
$toc = new VectorComponentTableOfContents();
$this->assertEquals(
[
'id' => 'vector-toc',
'is-pinned' => true,
],
$toc->getTemplateData()
public function testGetTemplateData(
array $tocData,
array $config,
array $expected
) {
$localizer = $this->createMock( MessageLocalizer::class );
$localizer->method( 'msg' )->willReturnCallback( function ( $key, ...$params ) {
$msg = $this->createMock( Message::class );
$msg->method( '__toString' )->willReturn( $key );
$msg->method( 'text' )->willReturn( $key );
return $msg;
} );
$toc = new VectorComponentTableOfContents(
$tocData,
$localizer,
new HashConfig( $config )
);
$this->assertEquals( $expected, $toc->getTemplateData() );
}
}