Reference previews should use Popups registration

* Adds a new webpack entry point for references previews
* Reference related code in src/index.js is moved to new
file resources/ext.popups.referencePreviews/index.js

The changes:
* References previews now in its own module ext.popups.referencePreviews
* Loaded via getCustomPopupTypes
* OWNERS.md files make clear which team owns which part of the code.

Bug: T326692
Change-Id: Iea8a5b9221c0b1fd41e40bff2cbe01e42124d53f
This commit is contained in:
Jon Robson 2024-01-02 17:00:34 -08:00 committed by WMDE-Fisch
parent 31ef381e90
commit 59c6b8e88f
35 changed files with 346 additions and 228 deletions

View file

@ -152,6 +152,15 @@
"resources/ext.popups/index.js"
]
},
"ext.popups.referencePreviews": {
"styles": [
"resources/ext.popups.referencePreviews/referencePreview.less"
],
"scripts": [
"resources/dist/referencePreviews.js",
"resources/ext.popups.referencePreviews/index.js"
]
},
"ext.popups.main": {
"scripts": [
"resources/dist/index.js"

View file

@ -67,9 +67,11 @@ class PopupsHooks implements
* @return array
*/
public static function getCustomPopupTypes(): array {
return ExtensionRegistry::getInstance()->getAttribute(
return array_merge( ExtensionRegistry::getInstance()->getAttribute(
'PopupsPluginModules'
);
), [
'ext.popups.referencePreviews'
] );
}
/**

View file

@ -71,7 +71,11 @@
"bundlesize": [
{
"path": "resources/dist/index.js",
"maxSize": "15.1kB"
"maxSize": "13.5kB"
},
{
"path": "resources/dist/referencePreviews.js",
"maxSize": "2.7kB"
}
]
}

Binary file not shown.

Binary file not shown.

BIN
resources/dist/referencePreviews.js vendored Normal file

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1 @@
module.exports = window.refPreviews;

View file

@ -1,11 +1,36 @@
@import 'mediawiki.skin.variables.less';
@import '../../src/ui/variables.less';
// Don't do any event bubbling on childs like <a><span>[</span>2]</a>, see T214693
.popups-icon--reference-generic {
.cdx-mixin-css-icon( @cdx-icon-reference );
}
.popups-icon--reference-book {
.cdx-mixin-css-icon( @cdx-icon-book );
}
.popups-icon--reference-journal {
.cdx-mixin-css-icon( @cdx-icon-journal );
}
.popups-icon--reference-news {
.cdx-mixin-css-icon( @cdx-icon-newspaper );
}
.popups-icon--reference-web {
.cdx-mixin-css-icon( @cdx-icon-browser );
}
.popups-icon--preview-disambiguation {
.cdx-mixin-css-icon( @cdx-icon-articles );
}
// Don't do any event bubbling on childs like <a><span>[</span>2]</a>
// see https://phabricator.wikimedia.org/T214693
#mw-content-text .reference a[ href*='#' ] * {
pointer-events: none;
}
.mwe-popups.mwe-popups-type-reference {
.mwe-popups.mwe-popups-type-reference .mwe-popups-container {
.mwe-popups-title .popups-icon--reference-note {
// There is currently no "reference-note" icon specified in extension.json
display: none;
@ -14,13 +39,12 @@
.mwe-popups-extract {
margin-right: 0;
max-height: inherit;
.mwe-popups-scroll {
// This is how the `margin-bottom: 47px` in popup.less is calculated
@marginBottom: 2 * @lineHeight + 7px;
// Same as the --pointer-height in popup.less
// This is how the @previewFooterHeight in popup.less is calculated
@marginBottom: @popupPadding + 34px;
// Same as @previewPointerHeight in popup.less
@pointerHeight: 8px;
max-height: 403px - @popupPadding - @marginBottom + @pointerHeight;
max-height: 401px - @popupPadding - @marginBottom + @pointerHeight;
overflow: auto;
padding-right: @popupPadding;
}
@ -49,7 +73,6 @@
opacity: 1;
}
}
.mwe-collapsible-placeholder {
font-weight: bold;
margin: 1em 0;

1
src/OWNERS.md Normal file
View file

@ -0,0 +1 @@
Code in this folder and subfolders is maintained by the Web Team unless stated.

View file

@ -20,11 +20,6 @@ export const FETCH_COMPLETE_TARGET_DELAY = 350 + FETCH_START_DELAY; // ms.
export const PREVIEW_SEEN_DURATION = 1000; // ms
export const ABANDON_END_DELAY = 300;
//
// Reference previews specific config
//
export const FETCH_DELAY_REFERENCE_TYPE = 150; // ms.
export default {
BRACKETED_DEVICE_PIXEL_RATIO: bpr,
// See https://phabricator.wikimedia.org/T272169: requesting a larger thumbnail to avoid bluriness

View file

@ -0,0 +1 @@
Code in this folder and subfolders is maintained by WMDE.

View file

@ -0,0 +1,2 @@
export const TYPE_REFERENCE = 'reference';
export const FETCH_DELAY_REFERENCE_TYPE = 150; // ms.

View file

@ -2,8 +2,8 @@
* @module gateway/reference
*/
import { previewTypes } from '../preview/model';
import { abortablePromise } from './index.js';
import { TYPE_REFERENCE } from './constants.js';
import { abortablePromise } from '../gateway/index.js';
/**
* @return {Gateway}
@ -77,7 +77,7 @@ export default function createReferenceGateway() {
const model = {
url: `#${ id }`,
extract: referenceNode.innerHTML,
type: previewTypes.TYPE_REFERENCE,
type: TYPE_REFERENCE,
referenceType: scrapeReferenceType( referenceNode ),
// Note: Even the top-most HTMLHtmlElement is guaranteed to have a parent.
sourceElementId: el.parentNode.id

View file

@ -1,9 +1,9 @@
/**
* @module referencePreview
*/
import { isTrackingEnabled, LOGGING_SCHEMA } from '../../../instrumentation/referencePreviews';
import { renderPopup } from '../popup/popup';
import { createNodeFromTemplate, escapeHTML } from '../templateUtil';
import { isTrackingEnabled, LOGGING_SCHEMA } from './referencePreviews';
import { renderPopup } from '../ui/templates/popup/popup';
import { createNodeFromTemplate, escapeHTML } from '../ui/templates/templateUtil';
const templateHTML = `
<div class="mwe-popups-container">
@ -39,7 +39,7 @@ const replaceWith = ( node, htmlOrOtherNode ) => {
* @param {ext.popups.ReferencePreviewModel} model
* @return {jQuery}
*/
export function renderReferencePreview(
function renderReferencePreview(
model
) {
const type = model.referenceType || 'generic';
@ -170,3 +170,15 @@ export function renderReferencePreview(
return el;
}
/**
* @param {ext.popups.ReferencePreviewModel} model
* @return {ext.popups.Preview}
*/
export default function createReferencePreview( model ) {
return {
el: renderReferencePreview( model ),
hasThumbnail: false,
isTall: false
};
}

View file

@ -0,0 +1,23 @@
import isReferencePreviewsEnabled from './isReferencePreviewsEnabled';
import { initReferencePreviewsInstrumentation } from './referencePreviews';
import createReferenceGateway from './createReferenceGateway';
import renderFn from './createReferencePreview';
import { TYPE_REFERENCE, FETCH_DELAY_REFERENCE_TYPE } from './constants';
import createUserSettings from '../userSettings';
import setUserConfigFlags from './setUserConfigFlags';
setUserConfigFlags( mw.config );
const userSettings = createUserSettings( mw.storage );
const referencePreviewsState = isReferencePreviewsEnabled( mw.user, userSettings, mw.config );
const gateway = createReferenceGateway();
window.refPreviews = referencePreviewsState !== null ? {
type: TYPE_REFERENCE,
selector: '#mw-content-text .reference a[ href*="#" ]',
delay: FETCH_DELAY_REFERENCE_TYPE,
gateway,
renderFn,
init: () => {
initReferencePreviewsInstrumentation();
}
} : null;

View file

@ -1,9 +1,9 @@
import { previewTypes } from './preview/model';
import { TYPE_REFERENCE } from './constants.js';
/**
* @module isReferencePreviewsEnabled
*/
const canSaveToUserPreferences = require( './canSaveToUserPreferences.js' );
const canSaveToUserPreferences = require( '../canSaveToUserPreferences.js' );
/**
* Given the global state of the application, creates a function that gets
@ -34,9 +34,9 @@ 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.isPreviewTypeEnabled( previewTypes.TYPE_REFERENCE );
return userSettings.isPreviewTypeEnabled( TYPE_REFERENCE );
}
// Registered users never can enable popup types at run-time.
return mw.user.options.get( 'popups-reference-previews' ) === '1' ? true : null;
return user.options.get( 'popups-reference-previews' ) === '1' ? true : null;
}

View file

@ -0,0 +1,29 @@
/**
* @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
*/
export default function setUserConfigFlags( config ) {
const popupsFlags = parseInt( config.get( 'wgPopupsFlags' ), 10 );
/* eslint-disable no-bitwise */
config.set(
'wgPopupsConflictsWithRefTooltipsGadget',
!!( popupsFlags & REF_TOOLTIPS_ENABLED )
);
config.set(
'wgPopupsReferencePreviews',
!!( popupsFlags & REFERENCE_PREVIEWS_ENABLED )
);
/* eslint-enable no-bitwise */
}

View file

@ -6,7 +6,6 @@ import * as Redux from 'redux';
import * as ReduxThunk from 'redux-thunk';
import createPagePreviewGateway from './gateway/page';
import createReferenceGateway from './gateway/reference';
import createUserSettings from './userSettings';
import createPreviewBehavior from './previewBehavior';
import createSettingsDialogRenderer from './ui/settingsDialogRenderer';
@ -14,8 +13,7 @@ import registerChangeListener from './changeListener';
import createIsPagePreviewsEnabled from './isPagePreviewsEnabled';
import { fromElement as titleFromElement } from './title';
import { init as rendererInit, registerPreviewUI, createPagePreview,
createDisambiguationPreview,
createReferencePreview
createDisambiguationPreview
} from './ui/renderer';
import createExperiments from './experiments';
import { isEnabled as isStatsvEnabled } from './instrumentation/statsv';
@ -26,11 +24,9 @@ import createMediaWikiPopupsObject from './integrations/mwpopups';
import { previewTypes, getPreviewType,
registerModel,
isAnythingEligible, findNearestEligibleTarget } from './preview/model';
import isReferencePreviewsEnabled from './isReferencePreviewsEnabled';
import setUserConfigFlags from './setUserConfigFlags';
import { registerGatewayForPreviewType, getGatewayForPreviewType } from './gateway';
import { initReferencePreviewsInstrumentation } from './instrumentation/referencePreviews';
import { FETCH_START_DELAY, FETCH_COMPLETE_TARGET_DELAY, FETCH_DELAY_REFERENCE_TYPE } from './constants';
import { FETCH_START_DELAY, FETCH_COMPLETE_TARGET_DELAY } from './constants';
const EXCLUDED_LINK_SELECTORS = [
'.extiw',
@ -188,9 +184,7 @@ function handleDOMEventIfEligible( handler ) {
// So-called "services".
generateToken = mw.user.generateRandomSessionId,
pagePreviewGateway = createPagePreviewGateway( mw.config ),
referenceGateway = createReferenceGateway(),
userSettings = createUserSettings( mw.storage ),
referencePreviewsState = isReferencePreviewsEnabled( mw.user, userSettings, mw.config ),
settingsDialog = createSettingsDialogRenderer(),
experiments = createExperiments( mw.experiments ),
statsvTracker = getStatsvTracker( mw.user, mw.config, experiments ),
@ -258,19 +252,7 @@ function handleDOMEventIfEligible( handler ) {
]
} );
}
if ( referencePreviewsState !== null ) {
// Register the reference preview type
mw.popups.register( {
type: previewTypes.TYPE_REFERENCE,
selector: '#mw-content-text .reference a[ href*="#" ]',
delay: FETCH_DELAY_REFERENCE_TYPE,
gateway: referenceGateway,
renderFn: createReferencePreview,
init: () => {
initReferencePreviewsInstrumentation();
}
} );
}
if ( !isAnythingEligible() ) {
mw.log.warn( 'ext.popups was loaded but everything is disabled' );
return;

View file

@ -18,9 +18,7 @@ const previewTypes = {
/** Standard page preview with or without thumbnail */
TYPE_PAGE: 'page',
/** Disambiguation page preview */
TYPE_DISAMBIGUATION: 'disambiguation',
/** Reference preview */
TYPE_REFERENCE: 'reference'
TYPE_DISAMBIGUATION: 'disambiguation'
};
export { previewTypes };

View file

@ -5,9 +5,7 @@
/**
* Same as in includes/PopupsContext.php
*/
const NAV_POPUPS_ENABLED = 1,
REF_TOOLTIPS_ENABLED = 2,
REFERENCE_PREVIEWS_ENABLED = 4;
const NAV_POPUPS_ENABLED = 1;
/**
* Decodes the bitmask that represents preferences to the related config options.
@ -22,13 +20,5 @@ export default function setUserConfigFlags( config ) {
'wgPopupsConflictsWithNavPopupGadget',
!!( popupsFlags & NAV_POPUPS_ENABLED )
);
config.set(
'wgPopupsConflictsWithRefTooltipsGadget',
!!( popupsFlags & REF_TOOLTIPS_ENABLED )
);
config.set(
'wgPopupsReferencePreviews',
!!( popupsFlags & REFERENCE_PREVIEWS_ENABLED )
);
/* eslint-enable no-bitwise */
}

View file

@ -9,27 +9,3 @@
.popups-icon--close {
.cdx-mixin-css-icon( @cdx-icon-close );
}
.popups-icon--reference-generic {
.cdx-mixin-css-icon( @cdx-icon-reference );
}
.popups-icon--reference-book {
.cdx-mixin-css-icon( @cdx-icon-book );
}
.popups-icon--reference-journal {
.cdx-mixin-css-icon( @cdx-icon-journal );
}
.popups-icon--reference-news {
.cdx-mixin-css-icon( @cdx-icon-newspaper );
}
.popups-icon--reference-web {
.cdx-mixin-css-icon( @cdx-icon-browser );
}
.popups-icon--preview-disambiguation {
.cdx-mixin-css-icon( @cdx-icon-articles );
}

View file

@ -3,7 +3,6 @@
@import 'templates/settingsDialog/settingsDialog.less';
@import 'templates/popup/popup.less';
@import 'templates/preview/preview.less';
@import 'templates/referencePreview/referencePreview.less';
@import 'settingsDialogRenderer.less';
#mwe-popups-svg {

View file

@ -6,7 +6,6 @@ import wait from '../wait';
import pointerMaskSVG from './pointer-mask.svg';
import { SIZES, createThumbnail } from './thumbnail';
import { renderPreview } from './templates/preview/preview';
import { renderReferencePreview } from './templates/referencePreview/referencePreview';
import { renderPagePreview } from './templates/pagePreview/pagePreview';
const landscapePopupWidth = 450,
@ -244,18 +243,6 @@ export function createDisambiguationPreview( model ) {
};
}
/**
* @param {ext.popups.ReferencePreviewModel} model
* @return {ext.popups.Preview}
*/
export function createReferencePreview( model ) {
return {
el: renderReferencePreview( model ),
hasThumbnail: false,
isTall: false
};
}
/**
* Shows the preview.
*

1
tests/OWNERS.md Normal file
View file

@ -0,0 +1 @@
Code in this folder and subfolders is maintained by the Web Team unless stated.

View file

@ -0,0 +1 @@
Code in this folder and subfolders is maintained by WMDE.

View file

@ -1,7 +1,7 @@
import { createStubTitle } from '../stubs';
import createReferenceGateway from '../../../src/gateway/reference';
import createReferenceGateway from '../../../src/ext.popups.referencePreviews/createReferenceGateway';
QUnit.module( 'ext.popups/gateway/reference', {
QUnit.module( 'ext.popups.referencePreviews/createReferenceGateway', {
beforeEach() {
global.CSS = {
escape: ( str ) => $.escapeSelector( str )

View file

@ -1,5 +1,5 @@
import * as stubs from './stubs';
import isReferencePreviewsEnabled from '../../src/isReferencePreviewsEnabled';
import * as stubs from '../stubs';
import isReferencePreviewsEnabled from '../../../src/ext.popups.referencePreviews/isReferencePreviewsEnabled';
function createStubUserSettings( expectEnabled ) {
return {
@ -9,14 +9,9 @@ function createStubUserSettings( expectEnabled ) {
};
}
QUnit.module( 'ext.popups#isReferencePreviewsEnabled', {
beforeEach() {
mw.user = { options: { get: () => '1' } };
},
afterEach() {
mw.user = null;
}
} );
const options = { get: () => '1' };
QUnit.module( 'ext.popups.referencePreviews#isReferencePreviewsEnabled' );
QUnit.test( 'all relevant combinations of flags', ( assert ) => {
[
@ -125,7 +120,10 @@ QUnit.test( 'all relevant combinations of flags', ( assert ) => {
].forEach( ( data ) => {
const user = {
isNamed: () => !data.isAnon && !data.isIPMasked,
isAnon: () => data.isAnon
isAnon: () => data.isAnon,
options: {
get: () => {}
}
},
userSettings = {
isPreviewTypeEnabled: () => data.isAnon ?
@ -137,9 +135,9 @@ QUnit.test( 'all relevant combinations of flags', ( assert ) => {
};
if ( data.isAnon ) {
mw.user.options.get = () => assert.true( false, 'not expected to be called' );
user.options.get = () => assert.true( false, 'not expected to be called' );
} else {
mw.user.options.get = () => data.enabledByRegistered ? '1' : '0';
user.options.get = () => data.enabledByRegistered ? '1' : '0';
}
assert.strictEqual(
@ -151,7 +149,7 @@ QUnit.test( 'all relevant combinations of flags', ( assert ) => {
} );
QUnit.test( 'it should display reference previews when conditions are fulfilled', ( assert ) => {
const user = stubs.createStubUser( false ),
const user = stubs.createStubUser( false, options ),
userSettings = createStubUserSettings( false ),
config = new Map();

View file

@ -0,0 +1,126 @@
import * as renderer from '../../../src/ui/renderer';
import * as constants from '../../../src/constants';
import { previewTypes } from '../../../src/preview/model';
import createReferencePreview from '../../../src/ext.popups.referencePreviews/createReferencePreview';
QUnit.module( 'ext.popups.referencePreviews#renderer', {
beforeEach() {
this.sandbox.stub( constants.default, 'BRACKETED_DEVICE_PIXEL_RATIO' ).value( 1 );
mw.msg = ( key ) => `<${ key }>`;
mw.message = ( key ) => {
return { exists: () => !key.endsWith( 'generic' ), text: () => `<${ key }>` };
};
mw.html = {
escape: ( str ) => str && str.replace( /'/g, '&apos;' ).replace( /</g, '&lt;' )
};
mw.track = () => {};
global.navigator = {
sendBeacon() {}
};
// Some tests below stub this function. Keep a copy so it can be restored.
this.getElementById = document.getElementById;
},
afterEach() {
// Restore getElementsById to its original state.
document.getElementById = this.getElementById;
mw.msg = null;
mw.message = null;
mw.html = null;
renderer.test.reset();
}
} );
QUnit.test( 'createReferencePreview(model)', ( assert ) => {
renderer.registerPreviewUI(
previewTypes.TYPE_REFERENCE,
createReferencePreview
);
const model = {
url: '#custom_id',
extract: 'Custom <i>extract</i> with an <a href="/wiki/Internal">internal</a> and an <a href="//wikipedia.de" class="external">external</a> link',
type: previewTypes.TYPE_REFERENCE,
referenceType: 'web'
},
preview = renderer.createPreviewWithType( model );
assert.strictEqual( preview.hasThumbnail, false );
assert.strictEqual( preview.isTall, false );
assert.strictEqual(
$( preview.el ).find( '.mwe-popups-title' ).text().trim(),
'<popups-refpreview-web>'
);
assert.strictEqual(
$( preview.el ).find( '.mw-parser-output' ).text().trim(),
'Custom extract with an internal and an external link'
);
assert.strictEqual(
$( preview.el ).find( 'a[target="_blank"]' ).length,
1,
'only external links open in new tabs'
);
} );
QUnit.test( 'createReferencePreview default title', ( assert ) => {
renderer.registerPreviewUI(
previewTypes.TYPE_REFERENCE,
createReferencePreview
);
const model = {
url: '',
extract: '',
type: previewTypes.TYPE_REFERENCE
},
preview = renderer.createPreviewWithType( model );
assert.strictEqual(
$( preview.el ).find( '.mwe-popups-title' ).text().trim(),
'<popups-refpreview-reference>'
);
} );
QUnit.test( 'createReferencePreview updates fade-out effect on scroll', ( assert ) => {
renderer.registerPreviewUI(
previewTypes.TYPE_REFERENCE,
createReferencePreview
);
const model = {
url: '',
extract: '',
type: previewTypes.TYPE_REFERENCE
},
preview = renderer.createPreviewWithType( model ),
$extract = $( preview.el ).find( '.mwe-popups-extract' );
$extract.children()[ 0 ].dispatchEvent( new Event( 'scroll' ) );
assert.false( $extract.children()[ 0 ].isScrolling );
assert.false( $extract.hasClass( 'mwe-popups-fade-out' ) );
} );
QUnit.test( 'createReferencePreview collapsible/sortable handling', ( assert ) => {
renderer.registerPreviewUI(
previewTypes.TYPE_REFERENCE,
createReferencePreview
);
const model = {
url: '',
extract: '<table class="mw-collapsible"></table>' +
'<table class="sortable"><th class="headerSort" tabindex="1" title="Click here"></th></table>',
type: previewTypes.TYPE_REFERENCE
},
preview = renderer.createPreviewWithType( model );
assert.strictEqual( $( preview.el ).find( '.mw-collapsible, .sortable, .headerSort' ).length, 0 );
assert.strictEqual( $( preview.el ).find( 'th' ).attr( 'tabindex' ), undefined );
assert.strictEqual( $( preview.el ).find( 'th' ).attr( 'title' ), undefined );
assert.strictEqual(
$( preview.el ).find( '.mwe-collapsible-placeholder' ).text(),
'<popups-refpreview-collapsible-placeholder>'
);
} );

View file

@ -0,0 +1,51 @@
import setUserConfigFlags from '../../../src/ext.popups.referencePreviews/setUserConfigFlags';
QUnit.module( 'ext.popups.referencePreviews#setUserConfigFlags' );
QUnit.test( 'reference preview config settings are successfully set from bitmask', ( assert ) => {
const config = new Map();
config.set( 'wgPopupsFlags', '7' );
setUserConfigFlags( config );
assert.deepEqual(
[
config.get( 'wgPopupsConflictsWithRefTooltipsGadget' ),
config.get( 'wgPopupsReferencePreviews' )
],
[ true, true ]
);
config.set( 'wgPopupsFlags', '2' );
setUserConfigFlags( config );
assert.deepEqual(
[
config.get( 'wgPopupsConflictsWithRefTooltipsGadget' ),
config.get( 'wgPopupsReferencePreviews' )
],
[ true, false ]
);
config.set( 'wgPopupsFlags', '5' );
setUserConfigFlags( config );
assert.deepEqual(
[
config.get( 'wgPopupsConflictsWithRefTooltipsGadget' ),
config.get( 'wgPopupsReferencePreviews' )
],
[ false, true ]
);
config.set( 'wgPopupsFlags', '0' );
setUserConfigFlags( config );
assert.deepEqual(
[
config.get( 'wgPopupsConflictsWithRefTooltipsGadget' ),
config.get( 'wgPopupsReferencePreviews' )
],
[ false, false ]
);
} );

View file

@ -10,11 +10,9 @@ QUnit.test( 'config settings are successfully set from bitmask', ( assert ) => {
assert.deepEqual(
[
config.get( 'wgPopupsConflictsWithNavPopupGadget' ),
config.get( 'wgPopupsConflictsWithRefTooltipsGadget' ),
config.get( 'wgPopupsReferencePreviews' )
config.get( 'wgPopupsConflictsWithNavPopupGadget' )
],
[ true, true, true ]
[ true ]
);
config.set( 'wgPopupsFlags', '2' );
@ -22,11 +20,9 @@ QUnit.test( 'config settings are successfully set from bitmask', ( assert ) => {
assert.deepEqual(
[
config.get( 'wgPopupsConflictsWithNavPopupGadget' ),
config.get( 'wgPopupsConflictsWithRefTooltipsGadget' ),
config.get( 'wgPopupsReferencePreviews' )
config.get( 'wgPopupsConflictsWithNavPopupGadget' )
],
[ false, true, false ]
[ false ]
);
config.set( 'wgPopupsFlags', '5' );
@ -34,11 +30,9 @@ QUnit.test( 'config settings are successfully set from bitmask', ( assert ) => {
assert.deepEqual(
[
config.get( 'wgPopupsConflictsWithNavPopupGadget' ),
config.get( 'wgPopupsConflictsWithRefTooltipsGadget' ),
config.get( 'wgPopupsReferencePreviews' )
config.get( 'wgPopupsConflictsWithNavPopupGadget' )
],
[ true, false, true ]
[ true ]
);
config.set( 'wgPopupsFlags', '0' );
@ -46,10 +40,8 @@ QUnit.test( 'config settings are successfully set from bitmask', ( assert ) => {
assert.deepEqual(
[
config.get( 'wgPopupsConflictsWithNavPopupGadget' ),
config.get( 'wgPopupsConflictsWithRefTooltipsGadget' ),
config.get( 'wgPopupsReferencePreviews' )
config.get( 'wgPopupsConflictsWithNavPopupGadget' )
],
[ false, false, false ]
[ false ]
);
} );

View file

@ -3,9 +3,10 @@
* instance.
*
* @param {boolean} isAnon The return value of the `#isAnon`.
* @param {Object|mw.Map} [options]
* @return {Object}
*/
export function createStubUser( isAnon ) {
export function createStubUser( isAnon, options ) {
return {
getPageviewToken() {
return '9876543210';
@ -21,7 +22,8 @@ export function createStubUser( isAnon ) {
},
sessionId() {
return '0123456789';
}
},
options
};
}

View file

@ -321,96 +321,6 @@ QUnit.test( 'createDisambiguationPreview(model)', ( assert ) => {
);
} );
QUnit.test( 'createReferencePreview(model)', ( assert ) => {
renderer.registerPreviewUI(
previewTypes.TYPE_REFERENCE,
renderer.createReferencePreview
);
const model = {
url: '#custom_id',
extract: 'Custom <i>extract</i> with an <a href="/wiki/Internal">internal</a> and an <a href="//wikipedia.de" class="external">external</a> link',
type: previewTypes.TYPE_REFERENCE,
referenceType: 'web'
},
preview = renderer.createPreviewWithType( model );
assert.strictEqual( preview.hasThumbnail, false );
assert.strictEqual( preview.isTall, false );
assert.strictEqual(
$( preview.el ).find( '.mwe-popups-title' ).text().trim(),
'<popups-refpreview-web>'
);
assert.strictEqual(
$( preview.el ).find( '.mw-parser-output' ).text().trim(),
'Custom extract with an internal and an external link'
);
assert.strictEqual(
$( preview.el ).find( 'a[target="_blank"]' ).length,
1,
'only external links open in new tabs'
);
} );
QUnit.test( 'createReferencePreview collapsible/sortable handling', ( assert ) => {
renderer.registerPreviewUI(
previewTypes.TYPE_REFERENCE,
renderer.createReferencePreview
);
const model = {
url: '',
extract: '<table class="mw-collapsible"></table>' +
'<table class="sortable"><th class="headerSort" tabindex="1" title="Click here"></th></table>',
type: previewTypes.TYPE_REFERENCE
},
preview = renderer.createPreviewWithType( model );
assert.strictEqual( $( preview.el ).find( '.mw-collapsible, .sortable, .headerSort' ).length, 0 );
assert.strictEqual( $( preview.el ).find( 'th' ).attr( 'tabindex' ), undefined );
assert.strictEqual( $( preview.el ).find( 'th' ).attr( 'title' ), undefined );
assert.strictEqual(
$( preview.el ).find( '.mwe-collapsible-placeholder' ).text(),
'<popups-refpreview-collapsible-placeholder>'
);
} );
QUnit.test( 'createReferencePreview default title', ( assert ) => {
renderer.registerPreviewUI(
previewTypes.TYPE_REFERENCE,
renderer.createReferencePreview
);
const model = {
url: '',
extract: '',
type: previewTypes.TYPE_REFERENCE
},
preview = renderer.createPreviewWithType( model );
assert.strictEqual(
$( preview.el ).find( '.mwe-popups-title' ).text().trim(),
'<popups-refpreview-reference>'
);
} );
QUnit.test( 'createReferencePreview updates fade-out effect on scroll', ( assert ) => {
renderer.registerPreviewUI(
previewTypes.TYPE_REFERENCE,
renderer.createReferencePreview
);
const model = {
url: '',
extract: '',
type: previewTypes.TYPE_REFERENCE
},
preview = renderer.createPreviewWithType( model ),
$extract = $( preview.el ).find( '.mwe-popups-extract' );
$extract.children()[ 0 ].dispatchEvent( new Event( 'scroll' ) );
assert.false( $extract.children()[ 0 ].isScrolling );
assert.false( $extract.hasClass( 'mwe-popups-fade-out' ) );
} );
QUnit.test( 'bindBehavior - preview dwell', function ( assert ) {
const preview = createPagePreview(),
behavior = createBehavior( this.sandbox );

View file

@ -28,7 +28,10 @@ module.exports = ( env, argv ) => ( {
// working directory.
context: __dirname,
entry: { index: './src' },
entry: {
index: './src',
referencePreviews: './src/ext.popups.referencePreviews/index.js'
},
resolve: {
alias: {
@ -119,8 +122,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: 47.8 * 1024,
maxEntrypointSize: 47.8 * 1024,
maxAssetSize: 40.0 * 1024,
maxEntrypointSize: 40.0 * 1024,
// The default filter excludes map files but we rename ours.
assetFilter: ( filename ) => !filename.endsWith( srcMapExt )