diff --git a/extension.json b/extension.json index e7eb70e02..3947fa9e8 100644 --- a/extension.json +++ b/extension.json @@ -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" diff --git a/includes/PopupsHooks.php b/includes/PopupsHooks.php index 0876abc31..59c6a42de 100644 --- a/includes/PopupsHooks.php +++ b/includes/PopupsHooks.php @@ -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' + ] ); } /** diff --git a/package.json b/package.json index dc81fb8fc..cfbe9c988 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,11 @@ "bundlesize": [ { "path": "resources/dist/index.js", - "maxSize": "15.1kB" + "maxSize": "13.5kB" + }, + { + "path": "resources/dist/referencePreviews.js", + "maxSize": "2.7kB" } ] } diff --git a/resources/dist/index.js b/resources/dist/index.js index 72f74773f..23925fd15 100644 Binary files a/resources/dist/index.js and b/resources/dist/index.js differ diff --git a/resources/dist/index.js.map.json b/resources/dist/index.js.map.json index a3e1b1f2b..6fe62011b 100644 Binary files a/resources/dist/index.js.map.json and b/resources/dist/index.js.map.json differ diff --git a/resources/dist/referencePreviews.js b/resources/dist/referencePreviews.js new file mode 100644 index 000000000..b64b5f1af Binary files /dev/null and b/resources/dist/referencePreviews.js differ diff --git a/resources/dist/referencePreviews.js.map.json b/resources/dist/referencePreviews.js.map.json new file mode 100644 index 000000000..2adfe35e5 Binary files /dev/null and b/resources/dist/referencePreviews.js.map.json differ diff --git a/resources/ext.popups.referencePreviews/index.js b/resources/ext.popups.referencePreviews/index.js new file mode 100644 index 000000000..0071f360f --- /dev/null +++ b/resources/ext.popups.referencePreviews/index.js @@ -0,0 +1 @@ +module.exports = window.refPreviews; diff --git a/src/ui/templates/referencePreview/referencePreview.less b/resources/ext.popups.referencePreviews/referencePreview.less similarity index 57% rename from src/ui/templates/referencePreview/referencePreview.less rename to resources/ext.popups.referencePreviews/referencePreview.less index bd5e1dd43..bc81b0b84 100644 --- a/src/ui/templates/referencePreview/referencePreview.less +++ b/resources/ext.popups.referencePreviews/referencePreview.less @@ -1,11 +1,36 @@ @import 'mediawiki.skin.variables.less'; +@import '../../src/ui/variables.less'; -// Don't do any event bubbling on childs like [2], 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 [2] +// 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; diff --git a/src/OWNERS.md b/src/OWNERS.md new file mode 100644 index 000000000..7c5f6cffa --- /dev/null +++ b/src/OWNERS.md @@ -0,0 +1 @@ +Code in this folder and subfolders is maintained by the Web Team unless stated. diff --git a/src/constants.js b/src/constants.js index 8f8cc2edb..919a33458 100644 --- a/src/constants.js +++ b/src/constants.js @@ -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 diff --git a/src/ext.popups.referencePreviews/OWNERS.md b/src/ext.popups.referencePreviews/OWNERS.md new file mode 100644 index 000000000..ebf9c0bb0 --- /dev/null +++ b/src/ext.popups.referencePreviews/OWNERS.md @@ -0,0 +1 @@ +Code in this folder and subfolders is maintained by WMDE. diff --git a/src/ext.popups.referencePreviews/constants.js b/src/ext.popups.referencePreviews/constants.js new file mode 100644 index 000000000..03b0e44de --- /dev/null +++ b/src/ext.popups.referencePreviews/constants.js @@ -0,0 +1,2 @@ +export const TYPE_REFERENCE = 'reference'; +export const FETCH_DELAY_REFERENCE_TYPE = 150; // ms. diff --git a/src/gateway/reference.js b/src/ext.popups.referencePreviews/createReferenceGateway.js similarity index 95% rename from src/gateway/reference.js rename to src/ext.popups.referencePreviews/createReferenceGateway.js index 5fba69a79..856131f16 100644 --- a/src/gateway/reference.js +++ b/src/ext.popups.referencePreviews/createReferenceGateway.js @@ -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 diff --git a/src/ui/templates/referencePreview/referencePreview.js b/src/ext.popups.referencePreviews/createReferencePreview.js similarity index 92% rename from src/ui/templates/referencePreview/referencePreview.js rename to src/ext.popups.referencePreviews/createReferencePreview.js index 15af80eef..620f894d5 100644 --- a/src/ui/templates/referencePreview/referencePreview.js +++ b/src/ext.popups.referencePreviews/createReferencePreview.js @@ -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 = `
@@ -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 + }; +} diff --git a/src/ext.popups.referencePreviews/index.js b/src/ext.popups.referencePreviews/index.js new file mode 100644 index 000000000..5f0fd442f --- /dev/null +++ b/src/ext.popups.referencePreviews/index.js @@ -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; diff --git a/src/isReferencePreviewsEnabled.js b/src/ext.popups.referencePreviews/isReferencePreviewsEnabled.js similarity index 82% rename from src/isReferencePreviewsEnabled.js rename to src/ext.popups.referencePreviews/isReferencePreviewsEnabled.js index d47728ad1..73f09e53c 100644 --- a/src/isReferencePreviewsEnabled.js +++ b/src/ext.popups.referencePreviews/isReferencePreviewsEnabled.js @@ -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; } diff --git a/src/instrumentation/referencePreviews.js b/src/ext.popups.referencePreviews/referencePreviews.js similarity index 100% rename from src/instrumentation/referencePreviews.js rename to src/ext.popups.referencePreviews/referencePreviews.js diff --git a/src/ext.popups.referencePreviews/setUserConfigFlags.js b/src/ext.popups.referencePreviews/setUserConfigFlags.js new file mode 100644 index 000000000..06bf718b7 --- /dev/null +++ b/src/ext.popups.referencePreviews/setUserConfigFlags.js @@ -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 */ +} diff --git a/src/index.js b/src/index.js index 0f0b1a917..80d313883 100644 --- a/src/index.js +++ b/src/index.js @@ -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; diff --git a/src/preview/model.js b/src/preview/model.js index 72c36f0c1..7be22dfeb 100644 --- a/src/preview/model.js +++ b/src/preview/model.js @@ -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 }; diff --git a/src/setUserConfigFlags.js b/src/setUserConfigFlags.js index b9c55af4d..52cf69120 100644 --- a/src/setUserConfigFlags.js +++ b/src/setUserConfigFlags.js @@ -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 */ } diff --git a/src/ui/icons.less b/src/ui/icons.less index b2750863f..346820fcc 100644 --- a/src/ui/icons.less +++ b/src/ui/icons.less @@ -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 ); -} diff --git a/src/ui/index.less b/src/ui/index.less index 590300fec..02569a13f 100644 --- a/src/ui/index.less +++ b/src/ui/index.less @@ -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 { diff --git a/src/ui/renderer.js b/src/ui/renderer.js index 8d7c9b587..dac15b0da 100644 --- a/src/ui/renderer.js +++ b/src/ui/renderer.js @@ -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. * diff --git a/tests/OWNERS.md b/tests/OWNERS.md new file mode 100644 index 000000000..7c5f6cffa --- /dev/null +++ b/tests/OWNERS.md @@ -0,0 +1 @@ +Code in this folder and subfolders is maintained by the Web Team unless stated. diff --git a/tests/node-qunit/ext.popups.referencePreviews/OWNERS.md b/tests/node-qunit/ext.popups.referencePreviews/OWNERS.md new file mode 100644 index 000000000..ebf9c0bb0 --- /dev/null +++ b/tests/node-qunit/ext.popups.referencePreviews/OWNERS.md @@ -0,0 +1 @@ +Code in this folder and subfolders is maintained by WMDE. diff --git a/tests/node-qunit/gateway/reference.test.js b/tests/node-qunit/ext.popups.referencePreviews/gateway.test.js similarity index 96% rename from tests/node-qunit/gateway/reference.test.js rename to tests/node-qunit/ext.popups.referencePreviews/gateway.test.js index c6af93a2e..a625ca598 100644 --- a/tests/node-qunit/gateway/reference.test.js +++ b/tests/node-qunit/ext.popups.referencePreviews/gateway.test.js @@ -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 ) diff --git a/tests/node-qunit/isReferencePreviewsEnabled.test.js b/tests/node-qunit/ext.popups.referencePreviews/isReferencePreviewsEnabled.test.js similarity index 91% rename from tests/node-qunit/isReferencePreviewsEnabled.test.js rename to tests/node-qunit/ext.popups.referencePreviews/isReferencePreviewsEnabled.test.js index 524e70d81..18dc0c67a 100644 --- a/tests/node-qunit/isReferencePreviewsEnabled.test.js +++ b/tests/node-qunit/ext.popups.referencePreviews/isReferencePreviewsEnabled.test.js @@ -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(); diff --git a/tests/node-qunit/ext.popups.referencePreviews/renderer.test.js b/tests/node-qunit/ext.popups.referencePreviews/renderer.test.js new file mode 100644 index 000000000..75a5c9040 --- /dev/null +++ b/tests/node-qunit/ext.popups.referencePreviews/renderer.test.js @@ -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, ''' ).replace( / {}; + + 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 extract with an internal and an external 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(), + '' + ); + 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(), + '' + ); +} ); + +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: '
' + + '
', + 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(), + '' + ); +} ); diff --git a/tests/node-qunit/ext.popups.referencePreviews/setUserConfigFlags.test.js b/tests/node-qunit/ext.popups.referencePreviews/setUserConfigFlags.test.js new file mode 100644 index 000000000..aaad84932 --- /dev/null +++ b/tests/node-qunit/ext.popups.referencePreviews/setUserConfigFlags.test.js @@ -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 ] + ); +} ); diff --git a/tests/node-qunit/setUserConfigFlags.test.js b/tests/node-qunit/setUserConfigFlags.test.js index e9007e8f9..88d782970 100644 --- a/tests/node-qunit/setUserConfigFlags.test.js +++ b/tests/node-qunit/setUserConfigFlags.test.js @@ -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 ] ); } ); diff --git a/tests/node-qunit/stubs.js b/tests/node-qunit/stubs.js index 21c1ac3ea..7e7949e52 100644 --- a/tests/node-qunit/stubs.js +++ b/tests/node-qunit/stubs.js @@ -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 }; } diff --git a/tests/node-qunit/ui/renderer.test.js b/tests/node-qunit/ui/renderer.test.js index 69111e2ba..7170682e7 100644 --- a/tests/node-qunit/ui/renderer.test.js +++ b/tests/node-qunit/ui/renderer.test.js @@ -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 extract with an internal and an external 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(), - '' - ); - 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: '
' + - '
', - 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(), - '' - ); -} ); - -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(), - '' - ); -} ); - -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 ); diff --git a/webpack.config.js b/webpack.config.js index 63ec996b7..c2b51f70b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -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 )