diff --git a/includes/Components/CitizenComponent.php b/includes/Components/CitizenComponent.php
new file mode 100644
index 00000000..cfab9164
--- /dev/null
+++ b/includes/Components/CitizenComponent.php
@@ -0,0 +1,14 @@
+sidebarData = $sidebarData;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getTemplateData(): array {
+ $portletsRest = [];
+ foreach ( $this->sidebarData[ 'array-portlets-rest' ] as $data ) {
+ /**
+ * Remove toolbox from main menu as we moved it to article tools
+ * TODO: Move handling to SkinCitizen.php after we convert pagetools to component
+ */
+ if ( $data['id'] === 'p-tb' ) {
+ continue;
+ }
+ $portletsRest[] = ( new CitizenComponentMenu( $data ) )->getTemplateData();
+ }
+ $firstPortlet = new CitizenComponentMenu( $this->sidebarData['data-portlets-first'] );
+
+ return [
+ 'data-portlets-first' => $firstPortlet->getTemplateData(),
+ 'array-portlets-rest' => $portletsRest
+ ];
+ }
+}
\ No newline at end of file
diff --git a/includes/Components/CitizenComponentMenu.php b/includes/Components/CitizenComponentMenu.php
new file mode 100644
index 00000000..ce4ec035
--- /dev/null
+++ b/includes/Components/CitizenComponentMenu.php
@@ -0,0 +1,49 @@
+data = $data;
+ }
+
+ /**
+ * Counts how many items the menu has.
+ *
+ * @return int
+ */
+ public function count(): int {
+ $items = $this->data['array-list-items'] ?? null;
+ if ( $items ) {
+ return count( $items );
+ }
+ $htmlItems = $this->data['html-items'] ?? '';
+ return substr_count( $htmlItems, '
data + [
+ 'class' => '',
+ 'label' => '',
+ 'html-tooltip' => '',
+ 'label-class' => '',
+ 'html-before-portal' => '',
+ 'html-items' => '',
+ 'html-after-portal' => '',
+ 'array-list-items' => null,
+ ];
+ }
+}
\ No newline at end of file
diff --git a/includes/Partials/Drawer.php b/includes/Partials/Drawer.php
index ea7fbf2c..eb54a344 100644
--- a/includes/Partials/Drawer.php
+++ b/includes/Partials/Drawer.php
@@ -32,31 +32,10 @@ use NumberFormatter;
* Drawer partial of Skin Citizen
* Generates the following partials:
* - Logo
- * - Drawer
* + Special Pages Link
* + Upload Link
*/
final class Drawer extends Partial {
- /**
- * Decorate main menu template data
- *
- * @return array
- */
- public function decorateMainMenuData( $mainMenuData ) {
- for ( $i = 0; $i < count( $mainMenuData['array-portlets-rest'] ); $i++ ) {
- if ( $mainMenuData['array-portlets-rest'][$i]['id'] === 'p-tb' ) {
- // Remove toolbox since it is handled by page tools
- unset( $mainMenuData['array-portlets-rest'][$i] );
- break;
- }
- }
-
- // Reset index after unsetting toolbox
- $mainMenuData['array-portlets-rest'] = array_values( $mainMenuData['array-portlets-rest'] );
-
- return $mainMenuData;
- }
-
/**
* Get messages used for site stats in the drawer
*
diff --git a/includes/SkinCitizen.php b/includes/SkinCitizen.php
index 05a3b0c4..cfbca128 100644
--- a/includes/SkinCitizen.php
+++ b/includes/SkinCitizen.php
@@ -23,6 +23,7 @@
namespace MediaWiki\Skins\Citizen;
+use MediaWiki\Skins\Citizen\Components\CitizenComponentMainMenu;
use MediaWiki\Skins\Citizen\Partials\BodyContent;
use MediaWiki\Skins\Citizen\Partials\Drawer;
use MediaWiki\Skins\Citizen\Partials\Footer;
@@ -116,13 +117,23 @@ class SkinCitizen extends SkinMustache {
'msg-citizen-footer-tagline' => $this->msg( "citizen-footer-tagline" )->inContentLanguage()->parse(),
// Decorate data provided by core
'data-search-box' => $header->decorateSearchBoxData( $parentData['data-search-box'] ),
- 'data-main-menu' => $drawer->decorateMainMenuData( $parentData['data-portlets-sidebar'] ),
'data-footer' => $footer->decorateFooterData( $parentData['data-footer'] ),
];
$data += $sidebar->getSidebarData( $parentData );
$data += $tools->getPageToolsData( $parentData );
+ $components = [
+ 'data-main-menu' => new CitizenComponentMainMenu( $parentData['data-portlets-sidebar'] )
+ ];
+
+ foreach ( $components as $key => $component ) {
+ // Array of components or null values.
+ if ( $component ) {
+ $parentData[$key] = $component->getTemplateData();
+ }
+ }
+
return array_merge( $parentData, $data );
}
diff --git a/templates/Drawer.mustache b/templates/Drawer.mustache
index ad9618c6..29f0568b 100644
--- a/templates/Drawer.mustache
+++ b/templates/Drawer.mustache
@@ -21,10 +21,7 @@
{{#data-main-menu}}
-
+ {{>MainMenu}}
{{/data-main-menu}}
\ No newline at end of file
diff --git a/templates/MainMenu.mustache b/templates/MainMenu.mustache
new file mode 100644
index 00000000..ad8bef32
--- /dev/null
+++ b/templates/MainMenu.mustache
@@ -0,0 +1,4 @@
+
+ {{#data-portlets-first}}{{>Menu}}{{/data-portlets-first}}
+ {{#array-portlets-rest}}{{>Menu}}{{/array-portlets-rest}}
+
\ No newline at end of file
diff --git a/tests/phpunit/Partials/DrawerTest.php b/tests/phpunit/Partials/DrawerTest.php
index 1d637aee..0db6fa87 100644
--- a/tests/phpunit/Partials/DrawerTest.php
+++ b/tests/phpunit/Partials/DrawerTest.php
@@ -11,38 +11,7 @@ use MediaWiki\Skins\Citizen\SkinCitizen;
* @group Citizen
* @group Database
*/
-class DrawerTest extends \MediaWikiIntegrationTestCase {
- /**
- * @covers \MediaWiki\Skins\Citizen\Partials\Drawer::decorateMainMenuData
- * @return void
- */
- public function testDecorateMainMenuDataEmpty() {
- $partial = new Drawer( new SkinCitizen() );
-
- $this->assertEmpty( $partial->decorateMainMenuData( [
- 'array-portlets-rest' => [],
- ] )['array-portlets-rest'] );
- }
-
- /**
- * @covers \MediaWiki\Skins\Citizen\Partials\Drawer::decorateMainMenuData
- * @return void
- */
- public function testDecorateMainMenuRemovePageTools() {
- $partial = new Drawer( new SkinCitizen() );
-
- $mainMenuData = [
- 'array-portlets-rest' => [
- [ 'id' => 'foo' ],
- [ 'id' => 'p-tb' ],
- ],
- ];
-
- $this->assertNotEmpty( $partial->decorateMainMenuData( $mainMenuData ) );
- $this->assertArrayHasKey( 'array-portlets-rest', $partial->decorateMainMenuData( $mainMenuData ) );
- $this->assertNotContains( [ 'id' => 'pt-tb' ], $partial->decorateMainMenuData( $mainMenuData )['array-portlets-rest'] );
- }
-
+class DrawerTest extends MediaWikiIntegrationTestCase {
/**
* @covers \MediaWiki\Skins\Citizen\Partials\Drawer::getSiteStatsData
* @return void
diff --git a/tests/phpunit/Unit/Components/CitizenComponentMainMenuTest.php b/tests/phpunit/Unit/Components/CitizenComponentMainMenuTest.php
new file mode 100644
index 00000000..e3680f0a
--- /dev/null
+++ b/tests/phpunit/Unit/Components/CitizenComponentMainMenuTest.php
@@ -0,0 +1,58 @@
+assertInstanceOf( CitizenComponent::class, $mainMenu );
+ }
+
+ /**
+ * @covers ::getTemplateData
+ */
+ public function testGetTemplateData( array $sidebarData, array $languageData, bool $isPinned ) {
+ // Create a new CitizenComponentMainMenu object
+ $mainMenu = new CitizenComponentMainMenu(
+ $sidebarData
+ );
+
+ // Call the getTemplateData method
+ $templateData = $mainMenu->getTemplateData();
+
+ // Assert main menu id
+ $this->assertSame( 'citizen-main-menu', $templateData['id'] );
+
+ // Assert the structure and types of expected keys
+ $this->assertIsArray( $templateData['data-portlets-first'] );
+ $this->assertIsArray( $templateData['array-portlets-rest'] );
+
+ // Assert the structure and types of expected keys
+ $this->assertArrayHasKey( 'data-portlets-first', $templateData );
+ $this->assertArrayHasKey( 'array-portlets-rest', $templateData );
+ }
+}
\ No newline at end of file
diff --git a/tests/phpunit/Unit/Components/CitizenComponentMenuTest.php b/tests/phpunit/Unit/Components/CitizenComponentMenuTest.php
new file mode 100644
index 00000000..cb88f985
--- /dev/null
+++ b/tests/phpunit/Unit/Components/CitizenComponentMenuTest.php
@@ -0,0 +1,98 @@
+ 'some-class',
+ 'label' => 'Some label',
+ 'html-tooltip' => 'Some tooltip',
+ 'label-class' => 'some-label-class',
+ 'html-before-portal' => 'Some before portal',
+ 'html-items' => 'Some items',
+ 'html-after-portal' => 'Some after portal',
+ 'array-list-items' => [ 'some-item-one', 'some-item-2', 'some-item-3' ]
+ ]
+ ]
+ ];
+ }
+
+ /**
+ * @return array[]
+ */
+ public function provideCountData(): array {
+ return [
+ [
+ [
+ 'array-list-items' => [ 'some-item-one', 'some-item-2', 'some-item-3' ]
+ ],
+ 3
+ ],
+ [
+ [
+ 'html-items' => 'Some itemSome itemSome item'
+ ],
+ 3
+ ]
+ ];
+ }
+
+ /**
+ * This test checks if the CitizenComponentMenu class can be instantiated
+ * @covers ::__construct
+ */
+ public function testConstruct() {
+ // Create a new CitizenComponentMenu object
+ $menu = new CitizenComponentMenu( [] );
+
+ // Check if the object is an instance of CitizenComponent
+ $this->assertInstanceOf( CitizenComponent::class, $menu );
+ }
+
+ /**
+ * This test checks if the count method returns the correct number of items
+ * @covers ::count
+ * @dataProvider provideCountData
+ */
+ public function testCount( array $data, int $expected ) {
+ // Create a new CitizenComponentMenu object
+ $menu = new CitizenComponentMenu( $data );
+
+ // Check if the count method returns the correct number of items
+ $this->assertSame( $expected, $menu->count() );
+ }
+
+ /**
+ * This test checks if the getTemplateData method returns the correct data
+ * @covers ::getTemplateData
+ * @dataProvider provideMenuData
+ */
+ public function testGetTemplateData( array $data ) {
+ // Create a new CitizenComponentMenu object
+ $menu = new CitizenComponentMenu( $data );
+
+ // Call the getTemplateData method
+ $actualData = $menu->getTemplateData();
+
+ // Check if the getTemplateData method returns the correct data
+ $this->assertSame( $data, $actualData );
+ }
+}
\ No newline at end of file