Implement necessary wiring for preferences

Changes:
 - introduce unit tests
 - don't send module for anonymous users
 - renamed Module to Context
 - remove Config dependency from Popups.hooks.php

Bug: T146889
Change-Id: I3cbcdc1303411b28b613afa6dd1a00b410891471
This commit is contained in:
Piotr Miazga 2016-12-16 03:47:52 +01:00
parent 397d3e8bc2
commit 959bf40d9b
7 changed files with 467 additions and 129 deletions

View file

@ -18,20 +18,19 @@
* @file * @file
* @ingroup extensions * @ingroup extensions
*/ */
use MediaWiki\Logger\LoggerFactory; use Popups\PopupsContext;
class PopupsHooks { class PopupsHooks {
const PREVIEWS_ENABLED = 'enabled';
const PREVIEWS_DISABLED = 'disabled';
const PREVIEWS_OPTIN_PREFERENCE_NAME = 'popups-enable';
const PREVIEWS_PREFERENCES_SECTION = 'rendering/reading'; const PREVIEWS_PREFERENCES_SECTION = 'rendering/reading';
static function getPreferences( User $user, array &$prefs ) { private static $context;
static function onGetBetaPreferences( User $user, array &$prefs ) {
global $wgExtensionAssetsPath; global $wgExtensionAssetsPath;
if ( self::getConfig()->get( 'PopupsBetaFeature' ) !== true ) { if ( self::getModuleContext()->getConfig()->get( 'PopupsBetaFeature' ) !== true ) {
return; return;
} }
$prefs['popups'] = [ $prefs[PopupsContext::PREVIEWS_BETA_PREFERENCE_NAME] = [
'label-message' => 'popups-message', 'label-message' => 'popups-message',
'desc-message' => 'popups-desc', 'desc-message' => 'popups-desc',
'screenshot' => [ 'screenshot' => [
@ -53,60 +52,52 @@ class PopupsHooks {
* @param array $prefs * @param array $prefs
*/ */
static function onGetPreferences( User $user, array &$prefs ) { static function onGetPreferences( User $user, array &$prefs ) {
$module = new \Popups\Module( self::getConfig() ); $module = self::getModuleContext();
if ( !$module->showPreviewsOptInOnPreferencesPage() ) { if ( !$module->showPreviewsOptInOnPreferencesPage() ) {
return; return;
} }
$prefs[self::PREVIEWS_OPTIN_PREFERENCE_NAME] = [ $prefs[PopupsContext::PREVIEWS_OPTIN_PREFERENCE_NAME] = [
'type' => 'radio', 'type' => 'radio',
'label-message' => 'popups-prefs-optin-title', 'label-message' => 'popups-prefs-optin-title',
'options' => [ 'options' => [
wfMessage( 'popups-prefs-optin-enabled-label' )->text() => self::PREVIEWS_ENABLED, wfMessage( 'popups-prefs-optin-enabled-label' )->text()
wfMessage( 'popups-prefs-optin-disabled-label' )->text() => self::PREVIEWS_DISABLED => PopupsContext::PREVIEWS_ENABLED,
wfMessage( 'popups-prefs-optin-disabled-label' )->text()
=> PopupsContext::PREVIEWS_DISABLED
], ],
'section' => self::PREVIEWS_PREFERENCES_SECTION 'section' => self::PREVIEWS_PREFERENCES_SECTION
]; ];
} }
/** /**
* @return Config * @return PopupsContext
*/ */
public static function getConfig() { private static function getModuleContext() {
static $config;
if ( !$config ) { if ( !self::$context ) {
$config = ConfigFactory::getDefaultInstance()->makeConfig( 'popups' ); self::$context = new \Popups\PopupsContext();
} }
return $config; return self::$context;
}
private static function areDependenciesMet() {
$registry = ExtensionRegistry::getInstance();
return $registry->isLoaded( 'TextExtracts' ) && class_exists( 'ApiQueryPageImages' );
} }
public static function onBeforePageDisplay( OutputPage &$out, Skin &$skin ) { public static function onBeforePageDisplay( OutputPage &$out, Skin &$skin ) {
// Enable only if the user has turned it on in Beta Preferences, or BetaFeatures is not installed. $module = self::getModuleContext();
// Will only be loaded if PageImages & TextExtracts extensions are installed.
$registry = ExtensionRegistry::getInstance(); if ( !self::areDependenciesMet() ) {
if ( !$registry->isLoaded( 'TextExtracts' ) || !class_exists( 'ApiQueryPageImages' ) ) { $logger = $module->getLogger();
$logger = LoggerFactory::getInstance( 'popups' );
$logger->error( 'Popups requires the PageImages and TextExtracts extensions.' ); $logger->error( 'Popups requires the PageImages and TextExtracts extensions.' );
return true; return true;
} }
$config = self::getConfig(); if ( $module->isEnabledByUser( $skin->getUser() ) ) {
$out->addModules( [ 'ext.popups' ] );
if ( $config->get( 'PopupsBetaFeature' ) === true ) {
if ( !class_exists( 'BetaFeatures' ) ) {
$logger = LoggerFactory::getInstance( 'popups' );
$logger->error( 'PopupsMode cannot be used as a beta feature unless ' .
'the BetaFeatures extension is present.' );
return true;
}
if ( !BetaFeatures::isFeatureEnabled( $skin->getUser(), 'popups' ) ) {
return true;
}
} }
$out->addModules( [ 'ext.popups' ] );
return true; return true;
} }
@ -152,7 +143,8 @@ class PopupsHooks {
* @param array $vars * @param array $vars
*/ */
public static function onResourceLoaderGetConfigVars( array &$vars ) { public static function onResourceLoaderGetConfigVars( array &$vars ) {
$conf = self::getConfig(); $module = self::getModuleContext();
$conf = $module->getConfig();
$vars['wgPopupsSchemaPopupsSamplingRate'] = $conf->get( 'SchemaPopupsSamplingRate' ); $vars['wgPopupsSchemaPopupsSamplingRate'] = $conf->get( 'SchemaPopupsSamplingRate' );
} }
@ -169,7 +161,35 @@ class PopupsHooks {
* or when ConfigRegistry gets populated before calling `callback` ExtensionRegistry hook * or when ConfigRegistry gets populated before calling `callback` ExtensionRegistry hook
*/ */
$config = \MediaWiki\MediaWikiServices::getInstance()->getMainConfig(); $config = \MediaWiki\MediaWikiServices::getInstance()->getMainConfig();
$wgDefaultUserOptions[ self::PREVIEWS_OPTIN_PREFERENCE_NAME ] = $wgDefaultUserOptions[ PopupsContext::PREVIEWS_OPTIN_PREFERENCE_NAME ] =
$config->get( 'PopupsOptInDefaultState' ); $config->get( 'PopupsOptInDefaultState' );
} }
/**
* Inject Mocked context
* As there is no service registration this is used for tests only.
*
* @param PopupsContext $context
* @throws MWException
*/
public static function injectContext( PopupsContext $context ) {
if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
throw new MWException( 'injectContext() must not be used outside unit tests.' );
}
self::$context = $context;
}
/**
* Remove cached context.
* As there is no service registration this is used for tests only.
*
*
* @throws MWException
*/
public static function resetContext() {
if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
throw new MWException( 'injectContext() must not be used outside unit tests.' );
}
self::$context = null;
}
} }

View file

@ -10,14 +10,14 @@
"type": "betafeatures", "type": "betafeatures",
"AutoloadClasses": { "AutoloadClasses": {
"PopupsHooks": "Popups.hooks.php", "PopupsHooks": "Popups.hooks.php",
"Popups\\Module": "includes/Module.php" "Popups\\PopupsContext": "includes/PopupsContext.php"
}, },
"ConfigRegistry": { "ConfigRegistry": {
"popups": "GlobalVarConfig::newInstance" "popups": "GlobalVarConfig::newInstance"
}, },
"Hooks": { "Hooks": {
"GetBetaFeaturePreferences": [ "GetBetaFeaturePreferences": [
"PopupsHooks::getPreferences" "PopupsHooks::onGetBetaPreferences"
], ],
"BeforePageDisplay": [ "BeforePageDisplay": [
"PopupsHooks::onBeforePageDisplay" "PopupsHooks::onBeforePageDisplay"
@ -46,10 +46,10 @@
"PopupsBetaFeature": false, "PopupsBetaFeature": false,
"@SchemaPopupsSamplingRate": "@var number: Sample rate for logging events to Schema:Popups.", "@SchemaPopupsSamplingRate": "@var number: Sample rate for logging events to Schema:Popups.",
"SchemaPopupsSamplingRate": 0, "SchemaPopupsSamplingRate": 0,
"@PopupsHideOptInOnPreferencesPage": "@var bool: Whether the option to enable/disable Page Previews should be hidden on Preferences page. Please note if PopupsBetaFeature is set to true this option will be always hidden. False by default", "@PopupsHideOptInOnPreferencesPage": "@var bool: Whether the option to senable/disable Page Previews should be hidden on Preferences page. Please note if PopupsBetaFeature is set to true this option will be always hidden. False by default",
"PopupsHideOptInOnPreferencesPage": true, "PopupsHideOptInOnPreferencesPage": false,
"@PopupsOptInDefaultState" : "@var string:[enabled|disabled] Default Page Previews visibility", "@PopupsOptInDefaultState" : "@var string:['1'|'0'] Default Page Previews visibility. Has to be a string as a compatibility with beta feature settings",
"PopupsOptInDefaultState" : "disabled" "PopupsOptInDefaultState" : "0"
}, },
"ResourceModules": { "ResourceModules": {
"ext.popups.images": { "ext.popups.images": {

View file

@ -1,35 +0,0 @@
<?php
/**
* Module.php
*/
namespace Popups;
/**
* Popups Module
*
* @package Popups
*/
final class Module {
/**
* @var \Config
*/
private $config;
/**
* Module constructor.
* @param \Config $config
*/
public function __construct( \Config $config ) {
$this->config = $config;
}
/**
* Are Page previews visible on User Preferences Page
*
* return @bool
*/
public function showPreviewsOptInOnPreferencesPage() {
return $this->config->get( 'PopupsBetaFeature' ) === false
&& $this->config->get( 'PopupsHideOptInOnPreferencesPage' ) === false;
}
}

124
includes/PopupsContext.php Normal file
View file

@ -0,0 +1,124 @@
<?php
/*
* This file is part of the MediaWiki extension Popups.
*
* Popups 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.
*
* Popups 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 Popups. If not, see <http://www.gnu.org/licenses/>.
*
* @file
* @ingroup extensions
*/
namespace Popups;
use MediaWiki\Logger\LoggerFactory;
/**
* Popups Module
*
* @package Popups
*/
class PopupsContext {
/**
* Extension name
* @var string
*/
const EXTENSION_NAME = 'popups';
/**
* Logger channel (name)
* @var string
*/
const LOGGER_CHANNEL = 'popups';
/**
* User preference value for enabled Page Previews
* @var string
*/
const PREVIEWS_ENABLED = \HTMLFeatureField::OPTION_ENABLED;
/**
* User preference value for disabled Page Previews
* @var string
*/
const PREVIEWS_DISABLED = \HTMLFeatureField::OPTION_DISABLED;
/**
* User preference to enable/disable Page Previews
* Currently for BETA and regular opt in we use same preference name
*
* @var string
*/
const PREVIEWS_OPTIN_PREFERENCE_NAME = 'popups';
/**
* User preference to enable/disable Page Preivews as a beta feature
* @var string
*/
const PREVIEWS_BETA_PREFERENCE_NAME = 'popups';
/**
* @var \Config
*/
private $config;
/**
* Module constructor.
*/
public function __construct() {
$this->config = \MediaWiki\MediaWikiServices::getInstance()
->getConfigFactory()->makeConfig( PopupsContext::EXTENSION_NAME );
}
/**
* Are Page previews visible on User Preferences Page
*
* return @bool
*/
public function showPreviewsOptInOnPreferencesPage() {
return $this->config->get( 'PopupsBetaFeature' ) === false
&& $this->config->get( 'PopupsHideOptInOnPreferencesPage' ) === false;
}
/**
* @param \User $user
* @return bool
*/
public function isEnabledByUser( \User $user ) {
if ( $user->isAnon() ) {
return false;
}
if ( $this->config->get( 'PopupsBetaFeature' ) ) {
if ( !class_exists( 'BetaFeatures' ) ) {
$this->getLogger()->error( 'PopupsMode cannot be used as a beta feature unless ' .
'the BetaFeatures extension is present.' );
return false;
}
return \BetaFeatures::isFeatureEnabled( $user, self::PREVIEWS_BETA_PREFERENCE_NAME );
};
return $user->getOption( self::PREVIEWS_OPTIN_PREFERENCE_NAME ) === self::PREVIEWS_ENABLED;
}
/**
* Get module logger
*
* @return \Psr\Log\LoggerInterface
*/
public function getLogger() {
return LoggerFactory::getInstance( self::LOGGER_CHANNEL );
}
/**
* Get Module config
*
* @return \Config
*/
public function getConfig() {
return $this->config;
}
}

View file

@ -1,49 +0,0 @@
<?php
use Popups\Module;
/**
* Popups module tests
*
* @group Popups
*/
class ModuleTest extends MediaWikiTestCase {
/**
* @covers Popups\Module::showPreviewsOptInOnPreferencesPage
* @dataProvider provideConfigForShowPreviewsInOptIn
*/
public function testShowPreviewsPreferencesWhenBetaIsOn( $config, $enabled ) {
$options = new HashConfig( $config );
$module = new Module( $options );
$this->assertEquals( $enabled, $module->showPreviewsOptInOnPreferencesPage() );
}
/**
* @return array
*/
public function provideConfigForShowPreviewsInOptIn() {
return [
[
"options" => [
"PopupsBetaFeature" => false,
"PopupsHideOptInOnPreferencesPage" => false
],
"enabled" => true
],
[
"options" => [
"PopupsBetaFeature" => true,
"PopupsHideOptInOnPreferencesPage" => false
],
"enabled" => false
],
[
"options" => [
"PopupsBetaFeature" => false,
"PopupsHideOptInOnPreferencesPage" => true
],
"enabled" => false
]
];
}
}

View file

@ -0,0 +1,161 @@
<?php
use Popups\PopupsContext;
/**
* Popups module tests
*
* @group Popups
*/
class PopupsContextTest extends MediaWikiTestCase {
/**
* @covers Popups\PopupsContext::showPreviewsOptInOnPreferencesPage
* @dataProvider provideConfigForShowPreviewsInOptIn
*/
public function testShowPreviewsPreferencesPage( $config, $expected ) {
$this->setMwGlobals( $config );
$module = new Popups\PopupsContext();
$this->assertEquals( $expected, $module->showPreviewsOptInOnPreferencesPage() );
}
/**
* @covers Popups\PopupsContext::__construct
* @covers Popups\PopupsContext::getConfig
*/
public function testContextAndConfigInitialization() {
$configMock = $this->getMock( Config::class );
$configFactoryMock = $this->getMock( ConfigFactory::class, [ 'makeConfig' ] );
$configFactoryMock->expects( $this->once() )
->method( 'makeConfig' )
->with( PopupsContext::EXTENSION_NAME )
->will( $this->returnValue( $configMock ) );
$mwServices = $this->overrideMwServices();
$mwServices->redefineService( 'ConfigFactory', function() use ( $configFactoryMock ) {
return $configFactoryMock;
} );
$module = new Popups\PopupsContext();
$this->assertSame( $module->getConfig(), $configMock );
}
/**
* @return array
*/
public function provideConfigForShowPreviewsInOptIn() {
return [
[
"options" => [
"wgPopupsBetaFeature" => false,
"wgPopupsHideOptInOnPreferencesPage" => false
],
"expected" => true
],
[
"options" => [
"wgPopupsBetaFeature" => true,
"wgPopupsHideOptInOnPreferencesPage" => false
],
"expected" => false
],
[
"options" => [
"wgPopupsBetaFeature" => false,
"wgPopupsHideOptInOnPreferencesPage" => true
],
"expected" => false
]
];
}
/**
* @covers Popups\PopupsContext::isEnabledByUser
* @dataProvider provideTestDataForIsEnabledByUser
*/
public function testIsEnabledByUser( $optIn, $expected ) {
$this->setMwGlobals( [
"wgPopupsBetaFeature" => false
] );
$module = new PopupsContext();
$user = $this->getMutableTestUser()->getUser();
$user->setOption( PopupsContext::PREVIEWS_OPTIN_PREFERENCE_NAME, $optIn );
$this->assertEquals( $module->isEnabledByUser( $user ), $expected );
}
/**
* @return array/
*/
public function provideTestDataForIsEnabledByUser() {
return [
[
"optin" => PopupsContext::PREVIEWS_ENABLED,
'expected' => true
],
[
"optin" => PopupsContext::PREVIEWS_DISABLED,
'expected' => false
]
];
}
/**
* @covers Popups\PopupsContext::isEnabledByUser
* @dataProvider provideTestDataForIsEnabledByUserWhenBetaEnabled
*/
public function testIsEnabledByUserWhenBetaEnabled( $optIn, $expected ) {
if ( !class_exists( 'BetaFeatures' ) ) {
$this->markTestSkipped( 'Skipped as BetaFeatures is not available' );
}
$this->setMwGlobals( [
"wgPopupsBetaFeature" => true
] );
$module = new PopupsContext();
$user = $this->getMutableTestUser()->getUser();
$user->setOption( PopupsContext::PREVIEWS_BETA_PREFERENCE_NAME, $optIn );
$this->assertEquals( $module->isEnabledByUser( $user ), $expected );
}
/**
* Check that Page Previews are disabled for anonymous user
*/
public function testAnonUserHasDisabledPagePreviews() {
$user = $this->getMutableTestUser()->getUser();
$user->setId( 0 );
$user->setOption( PopupsContext::PREVIEWS_OPTIN_PREFERENCE_NAME,
PopupsContext::PREVIEWS_ENABLED );
$this->setMwGlobals( [
"wgPopupsBetaFeature" => false
] );
$context = new PopupsContext();
$this->assertEquals( false, $context->isEnabledByUser( $user ) );
}
/**
* @return array/
*/
public function provideTestDataForIsEnabledByUserWhenBetaEnabled() {
return [
[
"optin" => PopupsContext::PREVIEWS_ENABLED,
'expected' => true
],
[
"optin" => PopupsContext::PREVIEWS_DISABLED,
'expected' => false
]
];
}
/**
* @covers Popups\PopupsContext::getLogger
*/
public function testGetLogger() {
$loggerMock = $this->getMock( \Psr\Log\LoggerInterface::class );
$this->setLogger( PopupsContext::LOGGER_CHANNEL, $loggerMock );
$context = new PopupsContext();
$this->assertSame( $loggerMock, $context->getLogger() );
}
}

View file

@ -0,0 +1,117 @@
<?php
/**
* Integration tests for Page Preview hooks
*
* @group Popups
*/
class PopupsHooksTest extends MediaWikiTestCase {
protected function tearDown() {
PopupsHooks::resetContext();
parent::tearDown();
}
/**
* @covers PopupsHooks::onGetBetaPreferences
*/
public function testOnGetBetaPreferencesBetaDisabled() {
$prefs = [ 'someNotEmptyValue' => 'notEmpty' ];
$this->setMwGlobals( [ 'wgPopupsBetaFeature' => false ] );
PopupsHooks::onGetBetaPreferences( $this->getTestUser()->getUser(), $prefs );
$this->assertCount( 1, $prefs );
$this->assertEquals( 'notEmpty', $prefs[ 'someNotEmptyValue'] );
}
/**
* @covers PopupsHooks::onGetBetaPreferences
*/
public function testOnGetBetaPreferencesBetaEnabled() {
$prefs = [ 'someNotEmptyValue' => 'notEmpty' ];
$this->setMwGlobals( [ 'wgPopupsBetaFeature' => true ] );
PopupsHooks::onGetBetaPreferences( $this->getTestUser()->getUser(), $prefs );
$this->assertCount( 2, $prefs );
$this->assertArrayHasKey( \Popups\PopupsContext::PREVIEWS_BETA_PREFERENCE_NAME, $prefs );
}
/**
* @covers PopupsHooks::onGetPreferences
* @covers PopupsHooks::injectContext
*/
public function testOnGetPreferencesPreviewsDisabled() {
$contextMock = $this->getMock( \Popups\PopupsContext::class,
[ 'showPreviewsOptInOnPreferencesPage' ] );
$contextMock->expects( $this->once() )
->method( 'showPreviewsOptInOnPreferencesPage' )
->will( $this->returnValue( false ) );
PopupsHooks::injectContext( $contextMock );
$prefs = [ 'someNotEmptyValue' => 'notEmpty' ];
PopupsHooks::onGetPreferences( $this->getTestUser()->getUser(), $prefs );
$this->assertCount( 1, $prefs );
$this->assertEquals( 'notEmpty', $prefs[ 'someNotEmptyValue'] );
}
/**
* @covers PopupsHooks::onGetPreferences
* @covers PopupsHooks::injectContext
*/
public function testOnGetPreferencesPreviewsEnabled() {
$contextMock = $this->getMock( \Popups\PopupsContext::class,
[ 'showPreviewsOptInOnPreferencesPage' ] );
$contextMock->expects( $this->once() )
->method( 'showPreviewsOptInOnPreferencesPage' )
->will( $this->returnValue( true ) );
PopupsHooks::injectContext( $contextMock );
$prefs = [ 'someNotEmptyValue' => 'notEmpty' ];
PopupsHooks::onGetPreferences( $this->getTestUser()->getUser(), $prefs );
$this->assertCount( 2, $prefs );
$this->assertEquals( 'notEmpty', $prefs[ 'someNotEmptyValue'] );
$this->assertArrayHasKey( \Popups\PopupsContext::PREVIEWS_OPTIN_PREFERENCE_NAME, $prefs );
}
/**
* @covers PopupsHooks::onResourceLoaderTestModules
*/
public function testOnResourceLoaderTestModules() {
$testModules = [ 'someNotEmptyValue' => 'notEmpty' ];
$resourceLoaderMock = $this->getMock( ResourceLoader::class );
PopupsHooks::onResourceLoaderTestModules( $testModules, $resourceLoaderMock );
$this->assertCount( 2, $testModules );
$this->assertEquals( 'notEmpty', $testModules[ 'someNotEmptyValue' ] );
$this->assertArrayHasKey( 'qunit', $testModules, 'ResourceLoader expects qunit test modules' );
$this->assertCount( 2, $testModules[ 'qunit' ], 'ResourceLoader expects 2 test modules. ' );
}
/**
* @covers PopupsHooks::onResourceLoaderGetConfigVars
*/
public function testOnResourceLoaderGetConfigVars() {
$vars = [ 'something' => 'notEmpty' ];
$value = 10;
$this->setMwGlobals( [ 'wgSchemaPopupsSamplingRate' => $value ] );
PopupsHooks::onResourceLoaderGetConfigVars( $vars );
$this->assertCount( 2, $vars );
$this->assertEquals( $value, $vars[ 'wgPopupsSchemaPopupsSamplingRate' ] );
}
/**
* @covers PopupsHooks::onExtensionRegistration
*/
public function testOnExtensionRegistration() {
global $wgDefaultUserOptions;
$test = 'testValue';
$this->setMwGlobals( [ 'wgPopupsOptInDefaultState' => $test ] );
PopupsHooks::onExtensionRegistration();
$this->assertEquals( $test,
$wgDefaultUserOptions[ \Popups\PopupsContext::PREVIEWS_OPTIN_PREFERENCE_NAME ] );
}
}