Vector-Local/tests/phpunit/integration/SkinVectorTest.php

470 lines
13 KiB
PHP
Raw Normal View History

<?php
namespace MediaWiki\Skins\Vector\Tests\Integration;
use Exception;
use MediaWiki\MediaWikiServices;
use MediaWiki\Skins\Vector\SkinVector22;
use MediaWiki\Skins\Vector\SkinVectorLegacy;
Add opt-out link to Sidebar for Vector/Logged-in Users Without Abstractions This commit is singularly focused on adding a link to the sidebar for Vector, logged-in users. It does the bare minimum to fulfill the requirements of T243281. Additionally, it will help to answer the question "Do we need to use abstractions (other than maybe different templates) to separate Legacy Vector from Vector" by intentionally leaving out any abstractions in order to make it easier to compare with a follow-up patch (Ib2ef15180df73360cc1de25b893e49d415d23e1a) which does use abstractions. It is a good thing to question whether or not we need addtional abstractions in VectorTemplate and if they will help us as unnecessary abstractions can have the opposite effect and just lead to further frustrations down the road. Therefore, I urge you, the reviewer, to let me know your thoughts! If abstractions are viewed as not making our lives any easier, the follow-up patches may be completely discarded and that's totally okay with me. :) I think it's a good think to talk about now though. Important changes: * The VectorTemplate constructor was changed to allow injecting the config, templateParser, and isLegacy boolean (only the config was allowed before this commit). According to MediaWiki's Stable Interface Policy, "Constructor signatures are generally considered unstable unless explicitly declared stable for calling" [3]. Given that VecorTemplate's constructor is not marked as stable, it is justified to do this without warning according to the policy. * Due to the above, the 'setTemplate' method is no longer needed and was marked as deprecated. * VectorTemplateTest was made to adapt to the new VectorTemplate constructor. Additionally, it now extends from MediaWikiIntegrationTestCase which my intelliphense server can pick up. I *think* MediaWikiTestCase is just an alias to MediaWikiIntegrationTestCase [1] and MediaWikiTestCase file was renamed to MediaWikiIntegrationTestCase in [2], but I'm willing to change it back if there is pushback to this. Open questions: * What are VectorTemplate's responsibilities? To me, it acts right now as a controller (because it echos the full HTML string from the template), a model (because SkinTemplate::prepareQuickTemplate sets data on it which it later retrieves through `$this->get()`), a presenter (because it adds data tailored for a web-centric view), and a view (because it renders HTML strings instead of letting the view/template be solely responsible for that). Arguably, some business logic might be mixed in there as well (because it checks to see if a User is logged in/has necessary permissions to show x which my changes here add to). This might not be a problem if we keep VectorTemplate relatively small, but will it remain this way as we progress further in Desktop Improvements? * How do we write tests for VectorTemplate without exposing unnecessary public methods? For example, if I want to test the `getSkinData()` method to see what state will be sent to the template, how should I do this? One option might be to use `TestingAccessWrapper` to expose these private methods which is what `VectorTemplateTest::testbuildViewsProps()` does. Another option is to accept this method as public. Is there a better way? Keep in mind that even with access to this method, there might be many things to mock. [1] https://github.com/wikimedia/mediawiki/blob/0030cb525be6cabc1d63de80586b2017d4bbe354/tests/common/TestsAutoLoader.php#L64 [2] Ie717b0ecf4fcfd089d46248f14853c80b7ef4a76 [3] https://www.mediawiki.org/wiki/Stable_interface_policy Bug: T243281 Change-Id: I0571b041bcd7f19bec9f103fa7bccdd093f6394d
2020-03-17 20:21:33 +00:00
use MediaWikiIntegrationTestCase;
use ReflectionMethod;
use RequestContext;
use Title;
use Wikimedia\TestingAccessWrapper;
/**
* Class VectorTemplateTest
* @package MediaWiki\Skins\Vector\Tests\Unit
* @group Vector
* @group Skins
*/
class SkinVectorTest extends MediaWikiIntegrationTestCase {
/**
* @return SkinVectorLegacy
*/
private function provideVectorTemplateObject() {
$skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
$template = $skinFactory->makeSkin( 'vector' );
return $template;
}
/**
* @param string $nodeString an HTML of the node we want to verify
* @param string $tag Tag of the element we want to check
* @param string $attribute Attribute of the element we want to check
* @param string $search Value of the attribute we want to verify
* @return bool
*/
private function expectNodeAttribute( $nodeString, $tag, $attribute, $search ) {
$node = new \DOMDocument();
$node->loadHTML( $nodeString );
$element = $node->getElementsByTagName( $tag )->item( 0 );
if ( !$element ) {
return false;
}
$values = explode( ' ', $element->getAttribute( $attribute ) );
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',
'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',
'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',
'array-sections' => [
'toclevel' => 2,
'level' => '4',
'line' => 'A1',
'number' => '1.1',
'index' => '2',
'fromtitle' => 'Test',
'byteoffset' => 245,
'anchor' => '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' ]
];
$expectedNestedTocData = array_merge( $nestedTocData, $expectedConfigData );
// qqx output
$buttonLabel = '(vector-toc-toggle-button-label: A)';
$expectedNestedTocData[ 'array-sections' ][ 0 ][ 'vector-button-label' ] = $buttonLabel;
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
*/
public function testGetTemplateData() {
$title = Title::newFromText( 'SkinVector' );
$context = RequestContext::newExtraneousContext( $title );
$context->setLanguage( 'fr' );
Add opt-out link to Sidebar for Vector/Logged-in Users Without Abstractions This commit is singularly focused on adding a link to the sidebar for Vector, logged-in users. It does the bare minimum to fulfill the requirements of T243281. Additionally, it will help to answer the question "Do we need to use abstractions (other than maybe different templates) to separate Legacy Vector from Vector" by intentionally leaving out any abstractions in order to make it easier to compare with a follow-up patch (Ib2ef15180df73360cc1de25b893e49d415d23e1a) which does use abstractions. It is a good thing to question whether or not we need addtional abstractions in VectorTemplate and if they will help us as unnecessary abstractions can have the opposite effect and just lead to further frustrations down the road. Therefore, I urge you, the reviewer, to let me know your thoughts! If abstractions are viewed as not making our lives any easier, the follow-up patches may be completely discarded and that's totally okay with me. :) I think it's a good think to talk about now though. Important changes: * The VectorTemplate constructor was changed to allow injecting the config, templateParser, and isLegacy boolean (only the config was allowed before this commit). According to MediaWiki's Stable Interface Policy, "Constructor signatures are generally considered unstable unless explicitly declared stable for calling" [3]. Given that VecorTemplate's constructor is not marked as stable, it is justified to do this without warning according to the policy. * Due to the above, the 'setTemplate' method is no longer needed and was marked as deprecated. * VectorTemplateTest was made to adapt to the new VectorTemplate constructor. Additionally, it now extends from MediaWikiIntegrationTestCase which my intelliphense server can pick up. I *think* MediaWikiTestCase is just an alias to MediaWikiIntegrationTestCase [1] and MediaWikiTestCase file was renamed to MediaWikiIntegrationTestCase in [2], but I'm willing to change it back if there is pushback to this. Open questions: * What are VectorTemplate's responsibilities? To me, it acts right now as a controller (because it echos the full HTML string from the template), a model (because SkinTemplate::prepareQuickTemplate sets data on it which it later retrieves through `$this->get()`), a presenter (because it adds data tailored for a web-centric view), and a view (because it renders HTML strings instead of letting the view/template be solely responsible for that). Arguably, some business logic might be mixed in there as well (because it checks to see if a User is logged in/has necessary permissions to show x which my changes here add to). This might not be a problem if we keep VectorTemplate relatively small, but will it remain this way as we progress further in Desktop Improvements? * How do we write tests for VectorTemplate without exposing unnecessary public methods? For example, if I want to test the `getSkinData()` method to see what state will be sent to the template, how should I do this? One option might be to use `TestingAccessWrapper` to expose these private methods which is what `VectorTemplateTest::testbuildViewsProps()` does. Another option is to accept this method as public. Is there a better way? Keep in mind that even with access to this method, there might be many things to mock. [1] https://github.com/wikimedia/mediawiki/blob/0030cb525be6cabc1d63de80586b2017d4bbe354/tests/common/TestsAutoLoader.php#L64 [2] Ie717b0ecf4fcfd089d46248f14853c80b7ef4a76 [3] https://www.mediawiki.org/wiki/Stable_interface_policy Bug: T243281 Change-Id: I0571b041bcd7f19bec9f103fa7bccdd093f6394d
2020-03-17 20:21:33 +00:00
$vectorTemplate = $this->provideVectorTemplateObject();
$vectorTemplate->setContext( $context );
$this->setTemporaryHook( 'PersonalUrls', [
static function ( &$personal_urls, &$title, $skin ) {
$personal_urls = [
'pt-1' => [ 'text' => 'pt1' ],
];
}
] );
$this->setTemporaryHook( 'SkinTemplateNavigation::Universal', [
static function ( &$skinTemplate, &$content_navigation ) {
$content_navigation['actions'] = [
'action-1' => []
];
$content_navigation['namespaces'] = [
'ns-1' => []
];
$content_navigation['variants'] = [
[
'class' => 'selected',
'text' => 'Language variant',
'href' => '/url/to/variant',
'lang' => 'zh-hant',
'hreflang' => 'zh-hant',
]
];
$content_navigation['views'] = [];
}
] );
$openVectorTemplate = TestingAccessWrapper::newFromObject( $vectorTemplate );
$props = $openVectorTemplate->getTemplateData()['data-portlets'];
$views = $props['data-views'];
$namespaces = $props['data-namespaces'];
// The mediawiki core specification might change at any time
// so let's limit the values we test to those we are aware of.
$keysToTest = [
'id', 'class', 'html-tooltip', 'html-items',
'html-after-portal', 'html-before-portal',
'label', 'heading-class', 'is-dropdown'
];
foreach ( $views as $key => $value ) {
if ( !in_array( $key, $keysToTest ) ) {
unset( $views[ $key] );
}
}
$this->assertSame(
[
// Provided by core
'id' => 'p-views',
'class' => 'mw-portlet mw-portlet-views emptyPortlet ' .
'vector-menu-tabs vector-menu-tabs-legacy',
'html-tooltip' => '',
'html-items' => '',
'html-after-portal' => '',
'html-before-portal' => '',
'label' => $context->msg( 'views' )->text(),
'heading-class' => '',
'is-dropdown' => false,
],
$views
);
$variants = $props['data-variants'];
$actions = $props['data-actions'];
$this->assertSame(
'mw-portlet mw-portlet-namespaces vector-menu-tabs vector-menu-tabs-legacy',
$namespaces['class']
);
$this->assertSame(
'mw-portlet mw-portlet-variants vector-menu-dropdown-noicon vector-menu-dropdown',
$variants['class']
);
$this->assertSame(
'mw-portlet mw-portlet-cactions vector-menu-dropdown-noicon vector-menu-dropdown',
$actions['class']
);
$this->assertSame(
'mw-portlet mw-portlet-personal vector-user-menu-legacy',
$props['data-personal']['class']
);
}
/**
* Standard config for Language Alert in Sidebar
* @return array
*/
private function enableLanguageAlertFeatureConfig(): array {
return [
'VectorLanguageInHeader' => [
'logged_in' => true,
'logged_out' => true
],
'VectorLanguageInMainPageHeader' => [
'logged_in' => false,
'logged_out' => false
],
'VectorLanguageAlertInSidebar' => [
'logged_in' => true,
'logged_out' => true
],
];
}
public function providerLanguageAlertRequirements() {
$testTitle = Title::makeTitle( NS_MAIN, 'Test' );
$testTitleMainPage = Title::makeTitle( NS_MAIN, 'MAIN_PAGE' );
return [
'When none of the requirements are present, do not show alert' => [
// Configuration requirements for language in header and alert in sidebar
[],
// Title instance
$testTitle,
// Cached languages
[],
// Is the language selector at the top of the content?
false,
// Should the language button be hidden?
false,
// Expected
false
],
'When the feature is enabled and languages should be hidden, do not show alert' => [
$this->enableLanguageAlertFeatureConfig(),
$testTitle,
[], true, true, false
],
'When the language alert feature is disabled, do not show alert' => [
[
'VectorLanguageInHeader' => [
'logged_in' => true,
'logged_out' => true
],
'VectorLanguageAlertInSidebar' => [
'logged_in' => false,
'logged_out' => false
]
],
$testTitle,
[ 'fr', 'en', 'ko' ], true, false, false
],
'When the language in header feature is disabled, do not show alert' => [
[
'VectorLanguageInHeader' => [
'logged_in' => false,
'logged_out' => false
],
'VectorLanguageAlertInSidebar' => [
'logged_in' => true,
'logged_out' => true
]
],
$testTitle,
[ 'fr', 'en', 'ko' ], true, false, false
],
'When it is a main page, feature is enabled, and there are no languages, do not show alert' => [
$this->enableLanguageAlertFeatureConfig(),
$testTitleMainPage,
[], true, true, false
],
'When it is a non-main page, feature is enabled, and there are no languages, do not show alert' => [
$this->enableLanguageAlertFeatureConfig(),
$testTitle,
[], true, true, false
],
'When it is a main page, header feature is disabled, and there are languages, do not show alert' => [
[
'VectorLanguageInHeader' => [
'logged_in' => false,
'logged_out' => false
],
'VectorLanguageAlertInSidebar' => [
'logged_in' => true,
'logged_out' => true
]
],
$testTitleMainPage,
[ 'fr', 'en', 'ko' ], true, true, false
],
'When it is a non-main page, alert feature is disabled, there are languages, do not show alert' => [
[
'VectorLanguageInHeader' => [
'logged_in' => true,
'logged_out' => true
],
'VectorLanguageAlertInSidebar' => [
'logged_in' => false,
'logged_out' => false
]
],
$testTitle,
[ 'fr', 'en', 'ko' ], true, true, false
],
'When most requirements are present but languages are not at the top, do not show alert' => [
$this->enableLanguageAlertFeatureConfig(),
$testTitle,
[ 'fr', 'en', 'ko' ], false, false, false
],
'When most requirements are present but languages should be hidden, do not show alert' => [
$this->enableLanguageAlertFeatureConfig(),
$testTitle,
[ 'fr', 'en', 'ko' ], true, true, false
],
'When it is a main page, features are enabled, and there are languages, show alert' => [
$this->enableLanguageAlertFeatureConfig(),
$testTitleMainPage,
[ 'fr', 'en', 'ko' ], true, false, true
],
'When all the requirements are present on a non-main page, show alert' => [
$this->enableLanguageAlertFeatureConfig(),
$testTitle,
[ 'fr', 'en', 'ko' ], true, false, true
],
];
}
/**
* @dataProvider providerLanguageAlertRequirements
* @covers \MediaWiki\Skins\Vector\SkinVector::shouldLanguageAlertBeInSidebar
* @param array $requirements
* @param Title $title
* @param array $getLanguagesCached
* @param bool $isLanguagesInContentAt
* @param bool $shouldHideLanguages
* @param bool $expected
* @throws Exception
*/
public function testShouldLanguageAlertBeInSidebar(
array $requirements,
Title $title,
array $getLanguagesCached,
bool $isLanguagesInContentAt,
bool $shouldHideLanguages,
bool $expected
) {
$this->overrideConfigValues( array_merge( $requirements, [
'DefaultSkin' => 'vector-2022',
'VectorDefaultSkinVersion' => '2',
'VectorSkinMigrationMode' => true,
] ) );
$mockSkinVector = $this->getMockBuilder( SkinVector22::class )
->disableOriginalConstructor()
->onlyMethods( [ 'getTitle', 'getLanguagesCached','isLanguagesInContentAt', 'shouldHideLanguages' ] )
->getMock();
$mockSkinVector->method( 'getTitle' )
->willReturn( $title );
$mockSkinVector->method( 'getLanguagesCached' )
->willReturn( $getLanguagesCached );
$mockSkinVector->method( 'isLanguagesInContentAt' )->with( 'top' )
->willReturn( $isLanguagesInContentAt );
$mockSkinVector->method( 'shouldHideLanguages' )
->willReturn( $shouldHideLanguages );
$shouldLanguageAlertBeInSidebarMethod = new ReflectionMethod(
SkinVector22::class,
'shouldLanguageAlertBeInSidebar'
);
$shouldLanguageAlertBeInSidebarMethod->setAccessible( true );
$this->assertSame(
$expected,
$shouldLanguageAlertBeInSidebarMethod->invoke( $mockSkinVector )
);
}
}