Add ReferencePreviews config checks to Cite extension

PHP classes and test are somewhat copies from the Popups codebase.
Some refactoring was applied. More could be done. Not to sure if
this should happen more in follow ups though.

Could also reduce the complexity of checks on the JS side. Most of
these things can only change on page load. The only dynamic part
left is the anon user setting managed by the Popups extension.

Note, that I needed to add a new PHP config for here although the
other still exists and is needed in the Popups extension. This
will change, when the user settings code also moves.

I guess it's okay for now though. Both settings default to true
and are not overridden in the config repos.

Also needed to add the Gadget extension as phan dependency.

Bug: T362771
Depends-On: Ia028c41f8aaa1c522dfc7c372e1ce51e40933a5e
Change-Id: Ie6e8bc706235724494036c7f0d873f5c996c46e6
This commit is contained in:
WMDE-Fisch 2024-04-19 09:42:41 +02:00
parent 646f167a2b
commit 179d402344
12 changed files with 449 additions and 219 deletions

View file

@ -2,6 +2,20 @@
$cfg = require __DIR__ . '/../vendor/mediawiki/mediawiki-phan-config/src/config.php'; $cfg = require __DIR__ . '/../vendor/mediawiki/mediawiki-phan-config/src/config.php';
$cfg['directory_list'] = array_merge(
$cfg['directory_list'],
[
'../../extensions/Gadgets',
]
);
$cfg['exclude_analysis_directory_list'] = array_merge(
$cfg['exclude_analysis_directory_list'],
[
'../../extensions/Gadgets',
]
);
/** /**
* Quick implementation of a recursive directory list. * Quick implementation of a recursive directory list.
* @param string $dir The directory to list * @param string $dir The directory to list

View file

@ -31,6 +31,7 @@
"ParserCloned": "parser", "ParserCloned": "parser",
"ParserFirstCallInit": "parser", "ParserFirstCallInit": "parser",
"EditPage::showEditForm:initial": "main", "EditPage::showEditForm:initial": "main",
"MakeGlobalVariablesScript": "main",
"ResourceLoaderGetConfigVars": "main", "ResourceLoaderGetConfigVars": "main",
"ResourceLoaderRegisterModules": "main" "ResourceLoaderRegisterModules": "main"
}, },
@ -251,8 +252,7 @@
"tests/qunit/ve-cite/ve.ui.MWWikitextStringTransferHandler.test.js", "tests/qunit/ve-cite/ve.ui.MWWikitextStringTransferHandler.test.js",
"tests/qunit/ext.cite.referencePreviews/createReferenceGateway.test.js", "tests/qunit/ext.cite.referencePreviews/createReferenceGateway.test.js",
"tests/qunit/ext.cite.referencePreviews/isReferencePreviewsEnabled.test.js", "tests/qunit/ext.cite.referencePreviews/isReferencePreviewsEnabled.test.js",
"tests/qunit/ext.cite.referencePreviews/renderer.test.js", "tests/qunit/ext.cite.referencePreviews/renderer.test.js"
"tests/qunit/ext.cite.referencePreviews/setUserConfigFlags.test.js"
], ],
"dependencies": [ "dependencies": [
"ext.cite.visualEditor", "ext.cite.visualEditor",
@ -299,6 +299,18 @@
"description": "If long <references /> lists with more than 10 references should behave responsive by default and be displayed in two or more columns. This can also be toggled individually with <references responsive /> to enable and <references responsive=\"0\" /> to disable it.", "description": "If long <references /> lists with more than 10 references should behave responsive by default and be displayed in two or more columns. This can also be toggled individually with <references responsive /> to enable and <references responsive=\"0\" /> to disable it.",
"public": true, "public": true,
"value": true "value": true
},
"CiteReferencePreviews": {
"description": "Feature flag to enable or disable the popups provided by the Popups extension for <ref> tags.",
"value": true
},
"CiteReferencePreviewsConflictingNavPopupsGadgetName": {
"description": "@var string: Name of a gadget that would cause duplicate reference preview popups. Should usually be identical to wgPopupsConflictingNavPopupsGadgetName in the Popups extension.",
"value": "Navigation_popups"
},
"CiteReferencePreviewsConflictingRefTooltipsGadgetName": {
"description": "@var string: Name of a gadget that would cause duplicate reference preview popups. Known conflicting gadgets include \"ReferenceTooltips\", \"CiteTooltip\" alias \"RefTooltip\", \"ReferencePopups\", and \"tooltipRef\" (see T274353).",
"value": "ReferenceTooltips"
} }
}, },
"AutoloadNamespaces": { "AutoloadNamespaces": {

View file

@ -3,9 +3,7 @@ const { initReferencePreviewsInstrumentation, LOGGING_SCHEMA } = require( './ref
const createReferenceGateway = require( './createReferenceGateway.js' ); const createReferenceGateway = require( './createReferenceGateway.js' );
const renderFn = require( './createReferencePreview.js' ); const renderFn = require( './createReferencePreview.js' );
const { TYPE_REFERENCE, FETCH_DELAY_REFERENCE_TYPE } = require( './constants.js' ); const { TYPE_REFERENCE, FETCH_DELAY_REFERENCE_TYPE } = require( './constants.js' );
const setUserConfigFlags = require( './setUserConfigFlags.js' );
setUserConfigFlags( mw.config );
const referencePreviewsState = isReferencePreviewsEnabled( const referencePreviewsState = isReferencePreviewsEnabled(
mw.user, mw.user,
mw.popups.isEnabled, mw.popups.isEnabled,
@ -40,7 +38,6 @@ if ( typeof QUnit !== 'undefined' ) {
module.exports = { private: { module.exports = { private: {
createReferenceGateway: require( './createReferenceGateway.js' ), createReferenceGateway: require( './createReferenceGateway.js' ),
createReferencePreview: require( './createReferencePreview.js' ), createReferencePreview: require( './createReferencePreview.js' ),
isReferencePreviewsEnabled: require( './isReferencePreviewsEnabled.js' ), isReferencePreviewsEnabled: require( './isReferencePreviewsEnabled.js' )
setUserConfigFlags: require( './setUserConfigFlags.js' )
} }; } };
} }

View file

@ -15,27 +15,15 @@ const { TYPE_REFERENCE } = require( './constants.js' );
* @return {boolean|null} Null when there is no way the popup type can be enabled at run-time. * @return {boolean|null} Null when there is no way the popup type can be enabled at run-time.
*/ */
function isReferencePreviewsEnabled( user, isPreviewTypeEnabled, config ) { function isReferencePreviewsEnabled( user, isPreviewTypeEnabled, config ) {
// TODO: This and the final `mw.user.options` check are currently redundant. Only this here
// should be removed when the wgCiteReferencePreviews feature flag is not needed any more.
if ( !config.get( 'wgCiteReferencePreviews' ) ) { if ( !config.get( 'wgCiteReferencePreviews' ) ) {
return null; return null;
} }
// T265872: Unavailable when in conflict with (one of the) reference tooltips gadgets.
if ( config.get( 'wgCiteReferencePreviewsConflictsWithRefTooltipsGadget' ) ||
config.get( 'wgPopupsConflictsWithNavPopupGadget' ) ||
// T243822: Temporarily disabled in the mobile skin
config.get( 'skin' ) === 'minerva'
) {
return null;
}
if ( user.isAnon() ) { if ( user.isAnon() ) {
return isPreviewTypeEnabled( TYPE_REFERENCE ); return isPreviewTypeEnabled( TYPE_REFERENCE );
} }
// Registered users never can enable popup types at run-time. return true;
return user.options.get( 'popups-reference-previews' ) === '1' ? true : null;
} }
module.exports = isReferencePreviewsEnabled; module.exports = isReferencePreviewsEnabled;

View file

@ -1,29 +0,0 @@
/**
* @module setUserConfigFlags
*/
/**
* Same as in includes/PopupsContext.php
*/
const REF_TOOLTIPS_ENABLED = 2,
REFERENCE_PREVIEWS_ENABLED = 4;
/**
* Decodes the bitmask that represents preferences to the related config options.
*
* @param {mw.Map} config
*/
module.exports = function setUserConfigFlags( config ) {
const popupsFlags = parseInt( config.get( 'wgPopupsFlags' ), 10 );
/* eslint-disable no-bitwise */
config.set(
'wgCiteReferencePreviewsConflictsWithRefTooltipsGadget',
!!( popupsFlags & REF_TOOLTIPS_ENABLED )
);
config.set(
'wgCiteReferencePreviews',
!!( popupsFlags & REFERENCE_PREVIEWS_ENABLED )
);
/* eslint-enable no-bitwise */
};

View file

@ -7,11 +7,13 @@
namespace Cite\Hooks; namespace Cite\Hooks;
use ApiQuerySiteinfo; use ApiQuerySiteinfo;
use Cite\ReferencePreviews\ReferencePreviewsContext;
use ExtensionRegistry; use ExtensionRegistry;
use MediaWiki\Api\Hook\APIQuerySiteInfoGeneralInfoHook; use MediaWiki\Api\Hook\APIQuerySiteInfoGeneralInfoHook;
use MediaWiki\Config\Config; use MediaWiki\Config\Config;
use MediaWiki\EditPage\EditPage; use MediaWiki\EditPage\EditPage;
use MediaWiki\Hook\EditPage__showEditForm_initialHook; use MediaWiki\Hook\EditPage__showEditForm_initialHook;
use MediaWiki\Output\Hook\MakeGlobalVariablesScriptHook;
use MediaWiki\Output\OutputPage; use MediaWiki\Output\OutputPage;
use MediaWiki\ResourceLoader\Hook\ResourceLoaderGetConfigVarsHook; use MediaWiki\ResourceLoader\Hook\ResourceLoaderGetConfigVarsHook;
use MediaWiki\ResourceLoader\Hook\ResourceLoaderRegisterModulesHook; use MediaWiki\ResourceLoader\Hook\ResourceLoaderRegisterModulesHook;
@ -26,6 +28,7 @@ use MediaWiki\User\Options\UserOptionsLookup;
*/ */
class CiteHooks implements class CiteHooks implements
ContentHandlerDefaultModelForHook, ContentHandlerDefaultModelForHook,
MakeGlobalVariablesScriptHook,
ResourceLoaderGetConfigVarsHook, ResourceLoaderGetConfigVarsHook,
ResourceLoaderRegisterModulesHook, ResourceLoaderRegisterModulesHook,
APIQuerySiteInfoGeneralInfoHook, APIQuerySiteInfoGeneralInfoHook,
@ -58,6 +61,21 @@ class CiteHooks implements
} }
} }
/**
* @param array &$vars
* @param OutputPage $out
*/
public function onMakeGlobalVariablesScript( &$vars, $out ): void {
$referencePreviewsContext = new ReferencePreviewsContext(
$out->getConfig(),
$this->userOptionsLookup
);
$vars['wgCiteReferencePreviews'] = $referencePreviewsContext->isReferencePreviewsEnabled(
$out->getUser(),
$out->getSkin()
);
}
/** /**
* Adds extra variables to the global config * Adds extra variables to the global config
* @param array &$vars `[ variable name => value ]` * @param array &$vars `[ variable name => value ]`
@ -92,8 +110,7 @@ class CiteHooks implements
'createReferenceGateway.js', 'createReferenceGateway.js',
'createReferencePreview.js', 'createReferencePreview.js',
'isReferencePreviewsEnabled.js', 'isReferencePreviewsEnabled.js',
'referencePreviewsInstrumentation.js', 'referencePreviewsInstrumentation.js'
'setUserConfigFlags.js'
] ]
] ]
] ); ] );

View file

@ -0,0 +1,52 @@
<?php
namespace Cite\ReferencePreviews;
use MediaWiki\Config\Config;
use MediaWiki\User\Options\UserOptionsLookup;
use MediaWiki\User\User;
use Skin;
/**
* @license GPL-2.0-or-later
*/
class ReferencePreviewsContext {
private Config $config;
private ReferencePreviewsGadgetsIntegration $gadgetsIntegration;
private UserOptionsLookup $userOptionsLookup;
public function __construct(
Config $config,
UserOptionsLookup $userOptionsLookup
) {
$this->gadgetsIntegration = new ReferencePreviewsGadgetsIntegration( $config );
$this->userOptionsLookup = $userOptionsLookup;
$this->config = $config;
}
/**
* User preference key to enable/disable Reference Previews. Named
* "mwe-popups-referencePreviews-enabled" in localStorage for anonymous users.
*/
public const REFERENCE_PREVIEWS_PREFERENCE_NAME = 'popups-reference-previews';
public function isReferencePreviewsEnabled( User $user, Skin $skin ): bool {
if (
// T243822: Temporarily disabled in the mobile skin
$skin->getSkinName() === 'minerva' ||
!$this->config->get( 'CiteReferencePreviews' ) ||
$this->gadgetsIntegration->isRefToolTipsGadgetEnabled( $user ) ||
$this->gadgetsIntegration->isNavPopupsGadgetEnabled( $user )
) {
return false;
}
return !$user->isNamed() || $this->userOptionsLookup->getBoolOption(
$user, self::REFERENCE_PREVIEWS_PREFERENCE_NAME
);
}
}

View file

@ -0,0 +1,70 @@
<?php
namespace Cite\ReferencePreviews;
use InvalidArgumentException;
use MediaWiki\Config\Config;
use MediaWiki\Extension\Gadgets\GadgetRepo;
use MediaWiki\MediaWikiServices;
use MediaWiki\User\User;
use MediaWiki\User\UserIdentity;
use Wikimedia\Services\NoSuchServiceException;
/**
* Gadgets integration
*
* @package ReferencePreviews
* @license GPL-2.0-or-later
*/
class ReferencePreviewsGadgetsIntegration {
public const CONFIG_NAVIGATION_POPUPS_NAME = 'CiteReferencePreviewsConflictingNavPopupsGadgetName';
public const CONFIG_REFERENCE_TOOLTIPS_NAME = 'CiteReferencePreviewsConflictingRefTooltipsGadgetName';
private ?GadgetRepo $gadgetRepo;
private string $navPopupsGadgetName;
private string $refTooltipsGadgetName;
public function __construct( Config $config ) {
$this->navPopupsGadgetName = $this->sanitizeGadgetName(
$config->get( self::CONFIG_NAVIGATION_POPUPS_NAME ) );
$this->refTooltipsGadgetName = $this->sanitizeGadgetName(
$config->get( self::CONFIG_REFERENCE_TOOLTIPS_NAME ) );
try {
$this->gadgetRepo = MediaWikiServices::getInstance()->getService( 'GadgetsRepo' );
} catch ( NoSuchServiceException $e ) {
$this->gadgetRepo = null;
}
}
private function sanitizeGadgetName( string $gadgetName ): string {
return str_replace( ' ', '_', trim( $gadgetName ) );
}
private function isGadgetEnabled( UserIdentity $user, string $gadgetName ): bool {
if ( $this->gadgetRepo ) {
if ( in_array( $gadgetName, $this->gadgetRepo->getGadgetIds() ) ) {
try {
return $this->gadgetRepo->getGadget( $gadgetName )
->isEnabled( $user );
} catch ( InvalidArgumentException $e ) {
return false;
}
}
}
return false;
}
public function isNavPopupsGadgetEnabled( User $user ): bool {
return $this->isGadgetEnabled( $user, $this->navPopupsGadgetName );
}
public function isRefTooltipsGadgetEnabled( User $user ): bool {
return $this->isGadgetEnabled( $user, $this->refTooltipsGadgetName );
}
}

View file

@ -0,0 +1,92 @@
<?php
namespace Cite\Tests\Integration\ReferencePreviews;
use Cite\ReferencePreviews\ReferencePreviewsContext;
use MediaWiki\Config\HashConfig;
use MediaWiki\User\Options\UserOptionsLookup;
use MediaWiki\User\User;
use MediaWikiIntegrationTestCase;
use Skin;
/**
* @group Popups
* @coversDefaultClass \Cite\ReferencePreviews\ReferencePreviewsContext
* @license GPL-2.0-or-later
*/
class ReferencePreviewsContextTest extends MediaWikiIntegrationTestCase {
/**
* Tests #shouldSendModuleToUser when the user is logged in and the reference previews feature
* is disabled.
*
* @covers ::isReferencePreviewsEnabled
* @dataProvider provideIsReferencePreviewsEnabled_requirements
*/
public function testIsReferencePreviewsEnabled_requirements( bool $setting, string $skinName, bool $expected ) {
$config = new HashConfig( [
'CiteReferencePreviews' => $setting,
'CiteReferencePreviewsConflictingNavPopupsGadgetName' => '',
'CiteReferencePreviewsConflictingRefTooltipsGadgetName' => '',
] );
$userOptLookup = $this->createNoOpMock( UserOptionsLookup::class );
$context = new ReferencePreviewsContext( $config, $userOptLookup );
$user = $this->createMock( User::class );
$user->method( 'isNamed' )->willReturn( false );
$skin = $this->createMock( Skin::class );
$skin->method( 'getSkinName' )->willReturn( $skinName );
$this->assertSame( $expected,
$context->isReferencePreviewsEnabled( $user, $skin ),
( $expected ? 'A' : 'No' ) . ' module is sent to the user.' );
}
public static function provideIsReferencePreviewsEnabled_requirements() {
yield [ true, 'minerva', false ];
yield [ false, 'minerva', false ];
yield [ true, 'vector', true ];
yield [ false, 'vector', false ];
}
/**
* Tests #shouldSendModuleToUser when the user is logged in and the reference previews feature
* is disabled.
*
* @covers ::isReferencePreviewsEnabled
* @dataProvider provideIsReferencePreviewsEnabled_userOptions
*/
public function testIsReferencePreviewsEnabled_userOptions( bool $isNamed, bool $option, bool $expected ) {
$user = $this->createMock( User::class );
$user->method( 'isNamed' )->willReturn( $isNamed );
$userOptLookup = $this->createMock( UserOptionsLookup::class );
$userOptLookup->method( 'getBoolOption' )
->with( $user, ReferencePreviewsContext::REFERENCE_PREVIEWS_PREFERENCE_NAME )
->willReturn( $option );
$config = new HashConfig( [
'CiteReferencePreviews' => true,
'CiteReferencePreviewsConflictingNavPopupsGadgetName' => '',
'CiteReferencePreviewsConflictingRefTooltipsGadgetName' => '',
] );
$context = new ReferencePreviewsContext( $config, $userOptLookup );
$skin = $this->createMock( Skin::class );
$this->assertSame( $expected,
$context->isReferencePreviewsEnabled( $user, $skin ),
( $expected ? 'A' : 'No' ) . ' module is sent to the user.' );
}
public static function provideIsReferencePreviewsEnabled_userOptions() {
yield [ true, true, true ];
yield [ true, false, false ];
yield [ false, false, true ];
yield [ false, true, true ];
}
}

View file

@ -0,0 +1,183 @@
<?php
namespace Cite\Tests\Integration\ReferencePreviews;
use Cite\ReferencePreviews\ReferencePreviewsGadgetsIntegration;
use InvalidArgumentException;
use MediaWiki\Config\Config;
use MediaWiki\Extension\Gadgets\Gadget;
use MediaWiki\Extension\Gadgets\GadgetRepo;
use MediaWiki\User\User;
use MediaWikiIntegrationTestCase;
use PHPUnit\Framework\MockObject\MockObject;
/**
* @coversDefaultClass \Cite\ReferencePreviews\ReferencePreviewsGadgetsIntegration
* @license GPL-2.0-or-later
*/
class ReferencePreviewsGadgetsIntegrationTest extends MediaWikiIntegrationTestCase {
/**
* Gadget name for testing
*/
private const NAV_POPUPS_GADGET_NAME = 'navigation-test';
/**
* Helper constants for easier reading
*/
private const GADGET_ENABLED = true;
/**
* Helper constants for easier reading
*/
private const GADGET_DISABLED = false;
/**
* @return MockObject|Config
*/
private function getConfigMock() {
$mock = $this->createMock( Config::class );
$mock->expects( $this->atLeastOnce() )
->method( 'get' )
->willReturnMap( [
[ ReferencePreviewsGadgetsIntegration::CONFIG_NAVIGATION_POPUPS_NAME, self::NAV_POPUPS_GADGET_NAME ],
[ ReferencePreviewsGadgetsIntegration::CONFIG_REFERENCE_TOOLTIPS_NAME, self::NAV_POPUPS_GADGET_NAME ],
] );
return $mock;
}
/**
* @covers ::isNavPopupsGadgetEnabled
* @covers ::__construct
* @covers ::sanitizeGadgetName
*/
public function testConflictsWithNavPopupsGadgetIfGadgetsExtensionIsNotLoaded() {
$user = $this->createMock( User::class );
$integration = new ReferencePreviewsGadgetsIntegration( $this->getConfigMock() );
$this->assertFalse(
$integration->isNavPopupsGadgetEnabled( $user ),
'No conflict is identified.' );
}
/**
* @covers ::isNavPopupsGadgetEnabled
*/
public function testConflictsWithNavPopupsGadgetIfGadgetNotExists() {
$user = $this->createMock( User::class );
$gadgetRepoMock = $this->createMock( GadgetRepo::class );
$gadgetRepoMock->expects( $this->once() )
->method( 'getGadgetIds' )
->willReturn( [] );
$this->executeConflictsWithNavPopupsGadgetSafeCheck( $user, $this->getConfigMock(),
$gadgetRepoMock, self::GADGET_DISABLED );
}
/**
* @covers ::isNavPopupsGadgetEnabled
*/
public function testConflictsWithNavPopupsGadgetIfGadgetExists() {
$user = $this->createMock( User::class );
$gadgetMock = $this->createMock( Gadget::class );
$gadgetMock->expects( $this->once() )
->method( 'isEnabled' )
->with( $user )
->willReturn( self::GADGET_ENABLED );
$gadgetRepoMock = $this->createMock( GadgetRepo::class );
$gadgetRepoMock->expects( $this->once() )
->method( 'getGadgetIds' )
->willReturn( [ self::NAV_POPUPS_GADGET_NAME ] );
$gadgetRepoMock->expects( $this->once() )
->method( 'getGadget' )
->with( self::NAV_POPUPS_GADGET_NAME )
->willReturn( $gadgetMock );
$this->executeConflictsWithNavPopupsGadgetSafeCheck( $user, $this->getConfigMock(),
$gadgetRepoMock, self::GADGET_ENABLED );
}
/**
* Test the edge case when GadgetsRepo::getGadget throws an exception
* @covers ::isNavPopupsGadgetEnabled
*/
public function testConflictsWithNavPopupsGadgetWhenGadgetNotExists() {
$user = $this->createMock( User::class );
$gadgetRepoMock = $this->createMock( GadgetRepo::class );
$gadgetRepoMock->expects( $this->once() )
->method( 'getGadgetIds' )
->willReturn( [ self::NAV_POPUPS_GADGET_NAME ] );
$gadgetRepoMock->expects( $this->once() )
->method( 'getGadget' )
->with( self::NAV_POPUPS_GADGET_NAME )
->willThrowException( new InvalidArgumentException() );
$this->executeConflictsWithNavPopupsGadgetSafeCheck( $user, $this->getConfigMock(),
$gadgetRepoMock, self::GADGET_DISABLED );
}
/**
* @covers ::sanitizeGadgetName
* @dataProvider provideGadgetNamesWithSanitizedVersion
*/
public function testConflictsWithNavPopupsGadgetNameSanitization( $name, $sanitized ) {
$user = $this->createMock( User::class );
$configMock = $this->createMock( Config::class );
$configMock->expects( $this->atLeastOnce() )
->method( 'get' )
->willReturnMap( [
[ ReferencePreviewsGadgetsIntegration::CONFIG_NAVIGATION_POPUPS_NAME, $name ],
[ ReferencePreviewsGadgetsIntegration::CONFIG_REFERENCE_TOOLTIPS_NAME, $name ]
] );
$gadgetMock = $this->createMock( Gadget::class );
$gadgetMock->expects( $this->once() )
->method( 'isEnabled' )
->willReturn( self::GADGET_ENABLED );
$gadgetRepoMock = $this->createMock( GadgetRepo::class );
$gadgetRepoMock->expects( $this->once() )
->method( 'getGadgetIds' )
->willReturn( [ $sanitized ] );
$gadgetRepoMock->expects( $this->once() )
->method( 'getGadget' )
->with( $sanitized )
->willReturn( $gadgetMock );
$this->executeConflictsWithNavPopupsGadgetSafeCheck( $user, $configMock, $gadgetRepoMock,
self::GADGET_ENABLED );
}
public static function provideGadgetNamesWithSanitizedVersion() {
return [
[ ' Popups ', 'Popups' ],
[ 'Navigation_popups-API', 'Navigation_popups-API' ],
[ 'Navigation popups ', 'Navigation_popups' ]
];
}
/**
* Execute test and restore GadgetRepo
*
* @param User $user
* @param Config $config
* @param GadgetRepo $repoMock
* @param bool $expected
*/
private function executeConflictsWithNavPopupsGadgetSafeCheck(
User $user,
Config $config,
GadgetRepo $repoMock,
$expected
) {
$this->setService( 'GadgetsRepo', $repoMock );
$integration = new ReferencePreviewsGadgetsIntegration( $config );
$this->assertSame( $expected,
$integration->isNavPopupsGadgetEnabled( $user ),
( $expected ? 'A' : 'No' ) . ' conflict is identified.' );
}
}

View file

@ -24,109 +24,28 @@ const options = { get: () => '1' };
QUnit.module : QUnit.module :
QUnit.module.skip )( 'ext.cite.referencePreviews#isReferencePreviewsEnabled' ); QUnit.module.skip )( 'ext.cite.referencePreviews#isReferencePreviewsEnabled' );
QUnit.test( 'all relevant combinations of flags', ( assert ) => { QUnit.test( 'relevant combinations of anonymous flags', ( assert ) => {
[ [
{ {
testCase: 'enabled for an anonymous user', testCase: 'enabled for an anonymous user',
wgCiteReferencePreviews: true, wgCiteReferencePreviews: true,
wgCiteReferencePreviewsConflictsWithRefTooltipsGadget: false,
isMobile: false,
isAnon: true, isAnon: true,
enabledByAnon: true, enabledByAnon: true,
enabledByRegistered: false,
expected: true expected: true
}, },
{ {
testCase: 'turned off via the feature flag (anonymous user)', testCase: 'turned off via the feature flag (anonymous user)',
wgCiteReferencePreviews: false, wgCiteReferencePreviews: false,
wgCiteReferencePreviewsConflictsWithRefTooltipsGadget: false,
isMobile: false,
isAnon: true, isAnon: true,
enabledByAnon: true, enabledByAnon: true,
enabledByRegistered: true,
expected: null
},
{
testCase: 'not available because of a conflicting gadget (anonymous user)',
wgCiteReferencePreviews: true,
wgCiteReferencePreviewsConflictsWithRefTooltipsGadget: true,
isMobile: false,
isAnon: true,
enabledByAnon: true,
enabledByRegistered: true,
expected: null
},
{
testCase: 'not available in the mobile skin (anonymous user)',
wgCiteReferencePreviews: true,
wgCiteReferencePreviewsConflictsWithRefTooltipsGadget: false,
isMobile: true,
isAnon: true,
enabledByAnon: true,
enabledByRegistered: true,
expected: null expected: null
}, },
{ {
testCase: 'manually disabled by the anonymous user', testCase: 'manually disabled by the anonymous user',
wgCiteReferencePreviews: true, wgCiteReferencePreviews: true,
wgCiteReferencePreviewsConflictsWithRefTooltipsGadget: false,
isMobile: false,
isAnon: true, isAnon: true,
enabledByAnon: false, enabledByAnon: false,
enabledByRegistered: true,
expected: false expected: false
},
{
testCase: 'enabled for a registered user',
wgCiteReferencePreviews: true,
wgCiteReferencePreviewsConflictsWithRefTooltipsGadget: false,
isMobile: false,
isAnon: false,
enabledByAnon: false,
enabledByRegistered: true,
expected: true
},
{
testCase: 'turned off via the feature flag (registered user)',
wgCiteReferencePreviews: false,
wgCiteReferencePreviewsConflictsWithRefTooltipsGadget: false,
isMobile: false,
isAnon: false,
enabledByAnon: true,
enabledByRegistered: true,
expected: null
},
{
testCase: 'not available because of a conflicting gadget (registered user)',
wgCiteReferencePreviews: true,
wgCiteReferencePreviewsConflictsWithRefTooltipsGadget: true,
isMobile: false,
isAnon: false,
enabledByAnon: true,
enabledByRegistered: true,
expected: null
},
{
testCase: 'not available in the mobile skin (registered user)',
wgCiteReferencePreviews: true,
wgCiteReferencePreviewsConflictsWithRefTooltipsGadget: false,
isMobile: true,
isAnon: false,
enabledByAnon: true,
enabledByRegistered: true,
expected: null
},
{
// TODO: This combination will make much more sense when the server-side
// wgCiteReferencePreviews flag doesn't include the user's setting any more
testCase: 'manually disabled by the registered user',
wgCiteReferencePreviews: true,
wgCiteReferencePreviewsConflictsWithRefTooltipsGadget: false,
isMobile: false,
isAnon: false,
enabledByAnon: true,
enabledByRegistered: false,
expected: null
} }
].forEach( ( data ) => { ].forEach( ( data ) => {
const user = { const user = {
@ -142,9 +61,8 @@ QUnit.test( 'all relevant combinations of flags', ( assert ) => {
} }
return data.enabledByAnon; return data.enabledByAnon;
}, },
config = { config = new Map();
get: ( key ) => key === 'skin' && data.isMobile ? 'minerva' : data[ key ] config.set( 'wgCiteReferencePreviews', data.wgCiteReferencePreviews );
};
if ( data.isAnon ) { if ( data.isAnon ) {
user.options.get = () => assert.true( false, 'not expected to be called 2' ); user.options.get = () => assert.true( false, 'not expected to be called 2' );
@ -166,7 +84,6 @@ QUnit.test( 'it should display reference previews when conditions are fulfilled'
config = new Map(); config = new Map();
config.set( 'wgCiteReferencePreviews', true ); config.set( 'wgCiteReferencePreviews', true );
config.set( 'wgCiteReferencePreviewsConflictsWithRefTooltipsGadget', false );
assert.true( assert.true(
require( 'ext.cite.referencePreviews' ).private.isReferencePreviewsEnabled( user, userSettings, config ), require( 'ext.cite.referencePreviews' ).private.isReferencePreviewsEnabled( user, userSettings, config ),
@ -174,44 +91,12 @@ QUnit.test( 'it should display reference previews when conditions are fulfilled'
); );
} ); } );
QUnit.test( 'it should handle the conflict with the Reference Tooltips Gadget', ( assert ) => {
const user = createStubUser( false ),
userSettings = createStubUserSettings( false ),
config = new Map();
config.set( 'wgCiteReferencePreviews', true );
config.set( 'wgCiteReferencePreviewsConflictsWithRefTooltipsGadget', true );
assert.strictEqual(
require( 'ext.cite.referencePreviews' ).private.isReferencePreviewsEnabled( user, userSettings, config ),
null,
'Reference Previews is disabled.'
);
} );
QUnit.test( 'it should not be enabled when the global is disabling it', ( assert ) => { QUnit.test( 'it should not be enabled when the global is disabling it', ( assert ) => {
const user = createStubUser( false ), const user = createStubUser( false ),
userSettings = createStubUserSettings( false ), userSettings = createStubUserSettings( false ),
config = new Map(); config = new Map();
config.set( 'wgCiteReferencePreviews', false ); config.set( 'wgCiteReferencePreviews', false );
config.set( 'wgCiteReferencePreviewsConflictsWithRefTooltipsGadget', false );
assert.strictEqual(
require( 'ext.cite.referencePreviews' ).private.isReferencePreviewsEnabled( user, userSettings, config ),
null,
'Reference Previews is disabled.'
);
} );
QUnit.test( 'it should not be enabled when minerva skin used', ( assert ) => {
const user = createStubUser( false ),
userSettings = createStubUserSettings( false ),
config = new Map();
config.set( 'wgCiteReferencePreviews', true );
config.set( 'wgCiteReferencePreviewsConflictsWithRefTooltipsGadget', false );
config.set( 'skin', 'minerva' );
assert.strictEqual( assert.strictEqual(
require( 'ext.cite.referencePreviews' ).private.isReferencePreviewsEnabled( user, userSettings, config ), require( 'ext.cite.referencePreviews' ).private.isReferencePreviewsEnabled( user, userSettings, config ),

View file

@ -1,51 +0,0 @@
( mw.loader.getModuleNames().indexOf( 'ext.popups.main' ) !== -1 ?
QUnit.module :
QUnit.module.skip )( 'ext.cite.referencePreviews#setUserConfigFlags' );
QUnit.test( 'reference preview config settings are successfully set from bitmask', ( assert ) => {
const config = new Map();
config.set( 'wgPopupsFlags', '7' );
require( 'ext.cite.referencePreviews' ).private.setUserConfigFlags( config );
assert.deepEqual(
[
config.get( 'wgCiteReferencePreviewsConflictsWithRefTooltipsGadget' ),
config.get( 'wgCiteReferencePreviews' )
],
[ true, true ]
);
config.set( 'wgPopupsFlags', '2' );
require( 'ext.cite.referencePreviews' ).private.setUserConfigFlags( config );
assert.deepEqual(
[
config.get( 'wgCiteReferencePreviewsConflictsWithRefTooltipsGadget' ),
config.get( 'wgCiteReferencePreviews' )
],
[ true, false ]
);
config.set( 'wgPopupsFlags', '5' );
require( 'ext.cite.referencePreviews' ).private.setUserConfigFlags( config );
assert.deepEqual(
[
config.get( 'wgCiteReferencePreviewsConflictsWithRefTooltipsGadget' ),
config.get( 'wgCiteReferencePreviews' )
],
[ false, true ]
);
config.set( 'wgPopupsFlags', '0' );
require( 'ext.cite.referencePreviews' ).private.setUserConfigFlags( config );
assert.deepEqual(
[
config.get( 'wgCiteReferencePreviewsConflictsWithRefTooltipsGadget' ),
config.get( 'wgCiteReferencePreviews' )
],
[ false, false ]
);
} );