Generalize settings code (attempt 2)

This reverts commit a6a65204c6.
to restore custom preview types.

-- Changes since revert
The previous patch accidentally removed the syncUserSettings
changeListener. This has now been restored with several modifications:
* We have a migrate script which rewrites existing localStorage settings
to the new system
* The existing save functions are generalized.

The changes since this patch are captured in
Ia73467799a9b535f7a3cf7268727c9fab7af0d7e

-- More information

A new REGISTER_SETTING action replaces the BOOT action for
registering settings. This allows custom preview types to be
associated with a setting. They do this by adding the enabled
property to the module they provide to mw.popups.register

Every time the new action is called, we refresh the settings
dialog UI with the new settings.

Previously the settings dialog was hardcoded, but now it is generated
from the registered preview types by deriving associated messages
and checking they exist, so by default custom types will not show
up in the settings.

Benefits:
* This change empowers us to add a setting for Math previews to allow
them to be enabled or disabled.
* Allows us to separate references as its own module

Additional notes:
* The syncUserSettings.js changeListener is no longer needed as the logic
for this is handled inside the "userSettings" change listener in response to
the "settings" reducer which is responding to
SETTINGS_CHANGE and REGISTER_SETTING actions.

Upon merging:
* https://www.mediawiki.org/wiki/Extension:Popups#Extensibility will be
updated to detail how a setting can be registered.

Bug: T334261
Bug: T326692

Change-Id: Ie17d622870511ac9730fc9fa525698fc3aa0d5b6
This commit is contained in:
Jon Robson 2023-10-19 14:12:09 -07:00
parent e74d335904
commit 2c09fd1d1c
27 changed files with 270 additions and 163 deletions

View file

@ -16,7 +16,7 @@ module.exports = {
// Set the coverage percentage by category thresholds.
statements: 80,
branches: 70,
branches: 69,
functions: 80,
lines: 90,

View file

@ -70,7 +70,7 @@
"bundlesize": [
{
"path": "resources/dist/index.js",
"maxSize": "14.8kB"
"maxSize": "15.1kB"
}
]
}

Binary file not shown.

Binary file not shown.

View file

@ -12,8 +12,15 @@ if ( !isTouchDevice && supportNotQueries ) {
mw.loader.using( types.concat( [ 'ext.popups.main' ] ) ).then( function () {
// Load custom popup types
types.forEach( function ( moduleName ) {
var module = require( moduleName );
const module = require( moduleName );
// Check the module exists. A module can export undefined or null if
// it does not want to be registered (for example where registration may
// depend on something that can only be checked at runtime.
// For example the Math module shouldn't register itself if there are no Math
// equations on the page.
if ( module ) {
mw.popups.register( module );
}
} );
// For now this API is limited to extensions/skins as we have not had a chance to
// consider the implications of gadgets having access to this function and dealing with

View file

@ -5,6 +5,7 @@
export default {
BOOT: 'BOOT',
LINK_DWELL: 'LINK_DWELL',
REGISTER_SETTING: 'REGISTER_SETTING',
ABANDON_START: 'ABANDON_START',
ABANDON_END: 'ABANDON_END',
LINK_CLICK: 'LINK_CLICK',

View file

@ -72,6 +72,22 @@ export function boot(
};
}
/**
* Registers a page preview setting for anonymous users.
*
* @param {string} name setting name which is used for storage and deriving associated
* messages.
* @param {boolean} enabled is the feature enabled by default?
* @return {Object}
*/
export function registerSetting( name, enabled ) {
return {
type: types.REGISTER_SETTING,
name,
enabled
};
}
/**
* Represents Page Previews fetching data via the gateway.
*
@ -213,7 +229,6 @@ export function linkDwell( title, el, measures, gateway, generateToken, type ) {
return promise.then( () => {
const previewState = getState().preview;
const enabledValue = previewState.enabled[ type ];
// Note: Only reference previews and default previews can be disabled at this point.
// If there is no UI the enabledValue is always true.
const isEnabled = typeof enabledValue === 'undefined' ? true : enabledValue;

View file

@ -13,12 +13,19 @@ export default function settings( boundActions, render ) {
// Nothing to do on initialization
return;
}
if (
settingsObj &&
Object.keys( oldState.settings.previewTypesEnabled ).length !== Object.keys( newState.settings.previewTypesEnabled ).length
) {
// the number of settings changed so force it to be repainted.
settingsObj.refresh( newState.settings.previewTypesEnabled );
}
// Update global modal visibility
if ( oldState.settings.shouldShow === false && newState.settings.shouldShow ) {
// Lazily instantiate the settings UI
if ( !settingsObj ) {
settingsObj = render( boundActions );
settingsObj = render( boundActions, newState.settings.previewTypesEnabled );
settingsObj.appendTo( document.body );
}

View file

@ -2,8 +2,6 @@
* @module changeListeners/syncUserSettings
*/
import { previewTypes } from '../preview/model';
/**
* Creates an instance of the user settings sync change listener.
*
@ -22,14 +20,14 @@ import { previewTypes } from '../preview/model';
*/
export default function syncUserSettings( userSettings ) {
return ( oldState, newState ) => {
Object.keys( newState.preview.enabled ).forEach( ( key ) => {
syncIfChanged(
oldState, newState, 'preview.enabled.' + previewTypes.TYPE_PAGE,
userSettings.storePagePreviewsEnabled
);
syncIfChanged(
oldState, newState, 'preview.enabled.' + previewTypes.TYPE_REFERENCE,
userSettings.storeReferencePreviewsEnabled
oldState, newState, `preview.enabled.${key}`,
( value ) => {
userSettings.storePreviewTypeEnabled( key, value );
}
);
} );
};
}

View file

@ -191,15 +191,11 @@ function handleDOMEventIfEligible( handler ) {
referenceGateway = createReferenceGateway(),
userSettings = createUserSettings( mw.storage ),
referencePreviewsState = isReferencePreviewsEnabled( mw.user, userSettings, mw.config ),
settingsDialog = createSettingsDialogRenderer( referencePreviewsState !== null ),
settingsDialog = createSettingsDialogRenderer(),
experiments = createExperiments( mw.experiments ),
statsvTracker = getStatsvTracker( mw.user, mw.config, experiments ),
pageviewTracker = getPageviewTracker( mw.config ),
initiallyEnabled = {
[ previewTypes.TYPE_PAGE ]:
createIsPagePreviewsEnabled( mw.user, userSettings, mw.config ),
[ previewTypes.TYPE_REFERENCE ]: referencePreviewsState
};
pagePreviewState = createIsPagePreviewsEnabled( mw.user, userSettings, mw.config );
// If debug mode is enabled, then enable Redux DevTools.
if ( mw.config.get( 'debug' ) ||
@ -224,7 +220,7 @@ function handleDOMEventIfEligible( handler ) {
);
boundActions.boot(
initiallyEnabled,
{},
mw.user,
userSettings,
mw.config,
@ -236,10 +232,15 @@ function handleDOMEventIfEligible( handler ) {
* extensions can query it (T171287)
*/
mw.popups = createMediaWikiPopupsObject(
store, registerModel, registerPreviewUI, registerGatewayForPreviewType
store, registerModel, registerPreviewUI, registerGatewayForPreviewType,
boundActions.registerSetting, userSettings
);
if ( initiallyEnabled[ previewTypes.TYPE_PAGE ] !== null ) {
// Migrate any old preferences to new system.
// FIXME: This can be removed in 4 weeks time.
userSettings.migrateOldPreferences();
if ( pagePreviewState !== null ) {
const excludedLinksSelector = EXCLUDED_LINK_SELECTORS.join( ', ' );
// Register default preview type
mw.popups.register( {
@ -257,7 +258,7 @@ function handleDOMEventIfEligible( handler ) {
]
} );
}
if ( initiallyEnabled[ previewTypes.TYPE_REFERENCE ] !== null ) {
if ( referencePreviewsState !== null ) {
// Register the reference preview type
mw.popups.register( {
type: previewTypes.TYPE_REFERENCE,

View file

@ -4,6 +4,14 @@
import { previewTypes } from '../preview/model';
/**
* @param {string} type
* @return {boolean} whether the preview type supports being disabled/enabled.
*/
function canShowSettingForPreviewType( type ) {
return mw.message( `popups-settings-option-${type}` ).exists();
}
/**
* This function provides a mw.popups object which can be used by 3rd party
* to interact with Popups.
@ -12,9 +20,13 @@ import { previewTypes } from '../preview/model';
* @param {Function} registerModel allows extensions to register custom preview handlers.
* @param {Function} registerPreviewUI allows extensions to register custom preview renderers.
* @param {Function} registerGatewayForPreviewType allows extensions to register gateways for preview types.
* @param {Function} registerSetting
* @param {UserSettings} userSettings
* @return {Object} external Popups interface
*/
export default function createMwPopups( store, registerModel, registerPreviewUI, registerGatewayForPreviewType ) {
export default function createMwPopups( store, registerModel, registerPreviewUI,
registerGatewayForPreviewType, registerSetting, userSettings
) {
return {
/**
* @return {boolean} If Page Previews are currently active
@ -69,6 +81,15 @@ export default function createMwPopups( store, registerModel, registerPreviewUI,
registerModel( type, selector, delay );
registerGatewayForPreviewType( type, gateway );
registerPreviewUI( type, renderFn, doNotRequireSummary );
// Only show if doesn't exist.
if ( canShowSettingForPreviewType( type ) ) {
registerSetting( type, userSettings.isPreviewTypeEnabled( type ) );
} else {
mw.log.warn(
`[Popups] No setting for ${type} registered.
Please create message with key "popups-settings-option-${type}" if this is a mistake.`
);
}
if ( subTypes ) {
subTypes.forEach( function ( subTypePreview ) {
registerPreviewUI( subTypePreview.type, subTypePreview.renderFn, subTypePreview.doNotRequireSummary );

View file

@ -1,6 +1,7 @@
/**
* @module isPagePreviewsEnabled
*/
import { previewTypes } from './preview/model';
const canSaveToUserPreferences = require( './canSaveToUserPreferences.js' );
/**
@ -28,7 +29,7 @@ export default function isPagePreviewsEnabled( user, userSettings, config ) {
// For anonymous users, and for IP masked usersm the code loads always,
// but the feature can be toggled at run-time via local storage.
if ( !canSaveToUserPreferences( user ) ) {
return userSettings.isPagePreviewsEnabled();
return userSettings.isPreviewTypeEnabled( previewTypes.TYPE_PAGE );
}
// Registered users never can enable popup types at run-time.

View file

@ -1,3 +1,5 @@
import { previewTypes } from './preview/model';
/**
* @module isReferencePreviewsEnabled
*/
@ -32,7 +34,7 @@ export default function isReferencePreviewsEnabled( user, userSettings, config )
// For anonymous users, the code loads always, but the feature can be toggled at run-time via
// local storage.
if ( !canSaveToUserPreferences( user ) ) {
return userSettings.isReferencePreviewsEnabled();
return userSettings.isPreviewTypeEnabled( previewTypes.TYPE_REFERENCE );
}
// Registered users never can enable popup types at run-time.

View file

@ -28,7 +28,12 @@ export default function preview( state, action ) {
return nextState( state, {
enabled: action.initiallyEnabled
} );
case actionTypes.REGISTER_SETTING:
return nextState( state, {
enabled: Object.assign( {}, state.enabled, {
[ action.name ]: action.enabled
} )
} );
case actionTypes.SETTINGS_CHANGE: {
return nextState( state, {
enabled: action.newValue

View file

@ -12,6 +12,7 @@ export default function settings( state, action ) {
if ( state === undefined ) {
state = {
shouldShow: false,
previewTypesEnabled: {},
showHelp: false,
shouldShowFooterLink: false
};
@ -57,12 +58,23 @@ export default function settings( state, action ) {
shouldShowFooterLink: anyDisabled
} );
}
case actionTypes.REGISTER_SETTING: {
const previewTypesEnabled = Object.assign( {}, state.previewTypesEnabled, {
[ action.name ]: action.enabled
} );
return nextState( state, {
previewTypesEnabled,
shouldShowFooterLink: state.shouldShowFooterLink || !action.enabled
} );
}
case actionTypes.BOOT: {
// Warning, when the state is null the user can't re-enable this popup type!
const anyDisabled = Object.keys( action.initiallyEnabled )
.some( ( type ) => action.initiallyEnabled[ type ] === false );
const previewTypesEnabled = Object.assign( {}, action.initiallyEnabled );
return nextState( state, {
previewTypesEnabled,
shouldShowFooterLink: action.user.isAnon && anyDisabled
} );
}

View file

@ -3,33 +3,28 @@
*/
import { renderSettingsDialog } from './templates/settingsDialog/settingsDialog';
import { previewTypes } from '../preview/model';
/**
* Create the settings dialog shown to anonymous users.
*
* @param {boolean} referencePreviewsAvaliable
* @param {Object} previewTypesEnabled
* @return {HTMLElement} settings dialog
*/
export function createSettingsDialog( referencePreviewsAvaliable ) {
const choices = [
export function createSettingsDialog( previewTypesEnabled ) {
const choices = Object.keys( previewTypesEnabled ).map( ( id ) => (
{
id: previewTypes.TYPE_PAGE,
name: mw.msg( 'popups-settings-option-page' ),
description: mw.msg( 'popups-settings-option-page-description' )
},
{
id: previewTypes.TYPE_REFERENCE,
name: mw.msg( 'popups-settings-option-reference' ),
description: mw.msg( 'popups-settings-option-reference-description' )
}
];
if ( !referencePreviewsAvaliable ) {
// Anonymous users can't access reference previews when they're disabled
// TODO: Remove when the wgPopupsReferencePreviews feature flag is not needed any more
choices.splice( 1, 1 );
id,
// This can produce:
// * popups-settings-option-preview
// * popups-settings-option-reference
name: mw.msg( `popups-settings-option-${id}` ),
// This can produce:
// * popups-settings-option-preview-description
// * popups-settings-option-reference-description
description: mw.msg( `popups-settings-option-${id}-description` ),
isChecked: previewTypesEnabled[ id ]
}
) );
return renderSettingsDialog( {
heading: mw.msg( 'popups-settings-title' ),

View file

@ -4,14 +4,34 @@
import { createSettingsDialog } from './settingsDialog';
const initDialog = ( boundActions, previewTypesEnabled ) => {
const dialog = createSettingsDialog( previewTypesEnabled );
// Setup event bindings
dialog.querySelector( '.save' ).addEventListener( 'click', () => {
boundActions.saveSettings(
Array.from( dialog.querySelectorAll( 'input' ) ).reduce(
( enabled, el ) => {
enabled[ el.value ] = el.matches( ':checked' );
return enabled;
},
{}
)
);
} );
dialog.querySelector( '.okay' ).addEventListener( 'click', boundActions.hideSettings );
dialog.querySelector( '.close' ).addEventListener( 'click', boundActions.hideSettings );
return dialog;
};
/**
* Creates a render function that will create the settings dialog and return
* a set of methods to operate on it
*
* @param {boolean} referencePreviewsAvaliable
* @return {Function} render function
*/
export default function createSettingsDialogRenderer( referencePreviewsAvaliable ) {
export default function createSettingsDialogRenderer() {
/**
* Cached settings dialog
*
@ -29,28 +49,30 @@ export default function createSettingsDialogRenderer( referencePreviewsAvaliable
* Renders the relevant form and labels in the settings dialog
*
* @param {Object} boundActions
* @param {Object} previewTypesEnabled
* @return {Object} object with methods to affect the rendered UI
*/
return ( boundActions ) => {
return ( boundActions, previewTypesEnabled ) => {
if ( !dialog ) {
dialog = createSettingsDialog( referencePreviewsAvaliable );
overlay = document.createElement( 'div' );
overlay.classList.add( 'mwe-popups-overlay' );
// Setup event bindings
dialog.querySelector( '.save' ).addEventListener( 'click', () => {
const enabled = {};
Array.prototype.forEach.call( dialog.querySelectorAll( 'input' ), ( el ) => {
enabled[ el.value ] = el.matches( ':checked' );
} );
boundActions.saveSettings( enabled );
} );
dialog.querySelector( '.close' ).addEventListener( 'click', boundActions.hideSettings );
dialog.querySelector( '.okay' ).addEventListener( 'click', boundActions.hideSettings );
dialog = initDialog( boundActions, previewTypesEnabled );
}
return {
/**
* Re-initialize the dialog when the available settings have changed.
*
* @param {Object} previewTypesEnabledNew updated key value pairs
*/
refresh( previewTypesEnabledNew ) {
const parent = dialog.parentNode;
dialog.remove();
dialog = initDialog( boundActions, previewTypesEnabledNew );
if ( parent ) {
dialog.appendTo( parent );
}
},
/**
* Append the dialog and overlay to a DOM element
*

View file

@ -1,3 +1,5 @@
import { previewTypes } from './preview/model';
/**
* @module userSettings
*/
@ -22,61 +24,51 @@ const PAGE_PREVIEWS_ENABLED_KEY = 'mwe-popups-enabled',
*/
export default function createUserSettings( storage ) {
return {
/**
* Gets whether the user has previously enabled Page Previews.
*
* N.B. that if the user hasn't previously enabled or disabled Page
* Previews, i.e. userSettings.storePagePreviewsEnabled(true), then they are treated as
* if they have enabled them.
*
* @method
* @name UserSettings#isPagePreviewsEnabled
* @return {boolean}
*/
isPagePreviewsEnabled() {
return storage.get( PAGE_PREVIEWS_ENABLED_KEY ) !== '0';
},
/**
* Permanently persists (typically in localStorage) whether the user has enabled Page
* Previews.
*
* @method
* @name UserSettings#storePagePreviewsEnabled
* @param {boolean} enabled
*/
storePagePreviewsEnabled( enabled ) {
if ( enabled ) {
migrateOldPreferences() {
const isDisabled = !!storage.get( PAGE_PREVIEWS_ENABLED_KEY );
if ( isDisabled ) {
storage.remove( PAGE_PREVIEWS_ENABLED_KEY );
} else {
storage.set( PAGE_PREVIEWS_ENABLED_KEY, '0' );
this.storePreviewTypeEnabled( previewTypes.TYPE_PAGE, false );
}
const isRefsDisabled = !!storage.get( REFERENCE_PREVIEWS_ENABLED_KEY );
if ( isRefsDisabled ) {
storage.remove( REFERENCE_PREVIEWS_ENABLED_KEY );
this.storePreviewTypeEnabled( previewTypes.TYPE_REFERENCE, false );
}
},
/**
* Check whether the preview type is enabled.
*
* @method
* @name UserSettings#isReferencePreviewsEnabled
* @return {boolean}
* @param {string} previewType
*/
isReferencePreviewsEnabled() {
return storage.get( REFERENCE_PREVIEWS_ENABLED_KEY ) !== '0';
isPreviewTypeEnabled( previewType ) {
const storageKey = `mwe-popups-${previewType}-enabled`;
const value = storage.get( storageKey );
return value === null;
},
/**
* Permanently persists (typically in localStorage) whether the user has enabled
* the preview type.
*
* @method
* @name UserSettings#storeReferencePreviewsEnabled
* @name UserSettings#storePreviewTypeEnabled
* @param {string} previewType
* @param {boolean} enabled
*/
storeReferencePreviewsEnabled( enabled ) {
if ( enabled ) {
storage.remove( REFERENCE_PREVIEWS_ENABLED_KEY );
} else {
storage.set( REFERENCE_PREVIEWS_ENABLED_KEY, '0' );
}
storePreviewTypeEnabled( previewType, enabled ) {
if ( previewType === previewTypes.TYPE_REFERENCE ) {
mw.track( REFERENCE_PREVIEWS_LOGGING_SCHEMA, {
action: enabled ? 'anonymousEnabled' : 'anonymousDisabled'
} );
}
const storageKey = `mwe-popups-${previewType}-enabled`;
if ( enabled ) {
storage.remove( storageKey );
} else {
storage.set( storageKey, '0' );
}
}
};
}

View file

@ -60,6 +60,22 @@ QUnit.test( '#boot', ( assert ) => {
);
} );
QUnit.test( '#registerSetting', ( assert ) => {
const action = actions.registerSetting(
'foo',
false
);
assert.deepEqual(
action,
{
type: actionTypes.REGISTER_SETTING,
name: 'foo',
enabled: false
},
'Setting action'
);
} );
/**
* Stubs `wait.js` and adds the deferred and its promise as properties
* of the module.

View file

@ -10,21 +10,24 @@ QUnit.module( 'ext.popups/changeListeners/settings', {
toggleHelp: this.sandbox.spy(),
setEnabled: this.sandbox.spy()
};
const previewTypesEnabled = {};
this.render.withArgs( 'actions' ).returns( this.rendered );
this.defaultState = { settings: { shouldShow: false } };
this.defaultState = { settings: { shouldShow: false, previewTypesEnabled } };
this.showState = {
settings: { shouldShow: true },
settings: { shouldShow: true, previewTypesEnabled },
preview: { enabled: { page: true, reference: true } }
};
this.showHelpState = {
settings: {
previewTypesEnabled,
shouldShow: true,
showHelp: true
}
};
this.hideHelpState = {
settings: {
previewTypesEnabled,
shouldShow: true,
showHelp: false
}

View file

@ -3,8 +3,7 @@ import syncUserSettings from '../../../src/changeListeners/syncUserSettings';
QUnit.module( 'ext.popups/changeListeners/syncUserSettings', {
beforeEach() {
this.userSettings = {
storePagePreviewsEnabled: this.sandbox.spy(),
storeReferencePreviewsEnabled: this.sandbox.spy()
storePreviewTypeEnabled: this.sandbox.spy()
};
this.changeListener = syncUserSettings( this.userSettings );
@ -21,7 +20,7 @@ QUnit.test(
this.changeListener( oldState, newState );
assert.false(
this.userSettings.storePagePreviewsEnabled.called,
this.userSettings.storePreviewTypeEnabled.called,
'The user setting is unchanged.'
);
}
@ -34,7 +33,7 @@ QUnit.test( 'it should update the storage if the enabled flag has changed', func
this.changeListener( oldState, newState );
assert.true(
this.userSettings.storePagePreviewsEnabled.calledWith( false ),
this.userSettings.storePreviewTypeEnabled.calledWith( 'page', false ),
'The user setting is disabled.'
);
} );
@ -49,7 +48,7 @@ QUnit.test(
this.changeListener( oldState, newState );
assert.false(
this.userSettings.storeReferencePreviewsEnabled.called,
this.userSettings.storePreviewTypeEnabled.called,
'Reference previews are unchanged.'
);
}
@ -61,12 +60,8 @@ QUnit.test( 'it should update the storage if the reference preview state has cha
this.changeListener( oldState, newState );
assert.false(
this.userSettings.storePagePreviewsEnabled.called,
'Page previews are unchanged.'
);
assert.true(
this.userSettings.storeReferencePreviewsEnabled.calledWith( false ),
this.userSettings.storePreviewTypeEnabled.calledWith( 'reference', false ),
'Reference previews opt-out is stored.'
);
} );

View file

@ -3,7 +3,7 @@ import isPagePreviewsEnabled from '../../src/isPagePreviewsEnabled';
function createStubUserSettings( expectEnabled ) {
return {
isPagePreviewsEnabled() {
isPreviewTypeEnabled() {
return expectEnabled !== false;
}
};

View file

@ -3,7 +3,7 @@ import isReferencePreviewsEnabled from '../../src/isReferencePreviewsEnabled';
function createStubUserSettings( expectEnabled ) {
return {
isReferencePreviewsEnabled() {
isPreviewTypeEnabled() {
return expectEnabled !== false;
}
};
@ -128,7 +128,7 @@ QUnit.test( 'all relevant combinations of flags', ( assert ) => {
isAnon: () => data.isAnon
},
userSettings = {
isReferencePreviewsEnabled: () => data.isAnon ?
isPreviewTypeEnabled: () => data.isAnon ?
data.enabledByAnon :
assert.true( false, 'not expected to be called' )
},

View file

@ -10,6 +10,7 @@ QUnit.test( '@@INIT', ( assert ) => {
state,
{
shouldShow: false,
previewTypesEnabled: {},
showHelp: false,
shouldShowFooterLink: false
},
@ -24,15 +25,15 @@ QUnit.test( 'BOOT with a single disabled popup type', ( assert ) => {
user: { isAnon: true }
};
assert.deepEqual(
settings( {}, action ),
{ shouldShowFooterLink: true },
settings( {}, action ).shouldShowFooterLink,
true,
'The boot state shows a footer link.'
);
action.user.isAnon = false;
assert.deepEqual(
settings( {}, action ),
{ shouldShowFooterLink: false },
settings( {}, action ).shouldShowFooterLink,
false,
'If the user is logged in, then it doesn\'t signal that the footer link should be shown.'
);
} );
@ -44,26 +45,62 @@ QUnit.test( 'BOOT with multiple popup types', ( assert ) => {
user: { isAnon: true }
};
assert.deepEqual(
settings( {}, action ),
{ shouldShowFooterLink: false },
settings( {}, action ).shouldShowFooterLink,
false,
'Footer link ignores unavailable popup types.'
);
action.initiallyEnabled.reference = true;
assert.deepEqual(
settings( {}, action ),
{ shouldShowFooterLink: false },
settings( {}, action ).shouldShowFooterLink,
false,
'Footer link is pointless when there is nothing to enable.'
);
action.initiallyEnabled.reference = false;
assert.deepEqual(
settings( {}, action ),
{ shouldShowFooterLink: true },
settings( {}, action ).shouldShowFooterLink,
true,
'Footer link appears when at least one popup type is disabled.'
);
} );
QUnit.test( 'REGISTER_SETTING that is disabled by default reveals footer link', ( assert ) => {
const REGISTER_SETTING_FOO_DISABLED = {
type: actionTypes.REGISTER_SETTING,
name: 'foo',
enabled: false
};
const newState = settings( {
previewTypesEnabled: {},
shouldShowFooterLink: false
}, REGISTER_SETTING_FOO_DISABLED );
assert.deepEqual(
newState.shouldShowFooterLink,
true,
'if one setting is registered as disabled, then the footer link is revealed.'
);
} );
QUnit.test( 'REGISTER_SETTING that is enabled by default should not show footer link', ( assert ) => {
const REGISTER_SETTING_FOO_ENABLED = {
type: actionTypes.REGISTER_SETTING,
name: 'foo',
enabled: true
};
const newState = settings( {
previewTypesEnabled: {},
shouldShowFooterLink: false
}, REGISTER_SETTING_FOO_ENABLED );
assert.deepEqual(
newState.previewTypesEnabled.foo,
true,
'previewTypesEnabled is updated'
);
} );
QUnit.test( 'SETTINGS_SHOW', ( assert ) => {
assert.deepEqual(
settings( {}, { type: actionTypes.SETTINGS_SHOW } ),

View file

@ -43,13 +43,14 @@ QUnit.test( '#render', ( assert ) => {
hideSettings() {}
},
expected = {
refresh() {},
appendTo() {},
show() {},
hide() {},
toggleHelp() {},
setEnabled() {}
},
result = createSettingsDialogRenderer( mw.config )( boundActions );
result = createSettingsDialogRenderer( mw.config )( boundActions, {} );
// Specifically NOT a deep equal. Only structure.
assert.propEqual(

View file

@ -9,43 +9,19 @@ QUnit.module( 'ext.popups/userSettings', {
} );
QUnit.test( '#isPagePreviewsEnabled should return false if Page Previews have been disabled', function ( assert ) {
this.userSettings.storePagePreviewsEnabled( false );
this.userSettings.storePreviewTypeEnabled( 'page', false );
assert.false(
this.userSettings.isPagePreviewsEnabled(),
this.userSettings.isPreviewTypeEnabled( 'page' ),
'The user has disabled Page Previews.'
);
// ---
this.userSettings.storePagePreviewsEnabled( true );
this.userSettings.storePreviewTypeEnabled( 'page', true );
assert.true(
this.userSettings.isPagePreviewsEnabled(),
this.userSettings.isPreviewTypeEnabled( 'page' ),
'#isPagePreviewsEnabled should return true if Page Previews have been enabled'
);
} );
QUnit.test( '#isReferencePreviewsEnabled', function ( assert ) {
assert.strictEqual(
this.storage.get( 'mwe-popups-referencePreviews-enabled' ),
null,
'Precondition: storage is empty.'
);
assert.true(
this.userSettings.isReferencePreviewsEnabled(),
'#isReferencePreviewsEnabled should default to true.'
);
this.userSettings.storeReferencePreviewsEnabled( false );
assert.strictEqual(
this.storage.get( 'mwe-popups-referencePreviews-enabled' ),
'0',
'#storeReferencePreviewsEnabled changes the storage.'
);
assert.false(
this.userSettings.isReferencePreviewsEnabled(),
'#isReferencePreviewsEnabled is now false.'
);
} );

View file

@ -119,8 +119,8 @@ module.exports = ( env, argv ) => ( {
// Minified uncompressed size limits for chunks / assets and entrypoints. Keep these numbers
// up-to-date and rounded to the nearest 10th of a kibibyte so that code sizing costs are
// well understood. Related to bundlesize minified, gzipped compressed file size tests.
maxAssetSize: 46.8 * 1024,
maxEntrypointSize: 46.8 * 1024,
maxAssetSize: 47.8 * 1024,
maxEntrypointSize: 47.8 * 1024,
// The default filter excludes map files but we rename ours.
assetFilter: ( filename ) => !filename.endsWith( srcMapExt )