Switch to Schema:Popups revid 15597282

Bug: T131315
Change-Id: I2ed18e00afb3e355327b417e68e5930b46d49086
This commit is contained in:
Baha 2016-05-16 16:32:11 -04:00 committed by jdlrobson
parent 058b863304
commit aba43fd560
11 changed files with 646 additions and 75 deletions

View file

@ -75,6 +75,7 @@ class PopupsHooks {
$schemaPopups += [
'dependencies' => [
'schema.Popups',
'ext.popups.schemaPopups.utils',
],
'scripts' => [
'resources/ext.popups.schemaPopups.js',
@ -229,12 +230,14 @@ class PopupsHooks {
'tests/qunit/ext.popups.renderer/desktopRenderer.test.js',
'tests/qunit/ext.popups.renderer.article.test.js',
'tests/qunit/ext.popups.core.test.js',
'tests/qunit/ext.popups.schemaPopups.utils.test.js',
'tests/qunit/ext.popups.settings.test.js',
'tests/qunit/ext.popups.experiment.test.js',
),
'dependencies' => array(
'ext.popups.desktop',
'ext.popups.experiment'
'ext.popups.experiment',
'ext.popups.schemaPopups.utils'
),
'localBasePath' => __DIR__,
'remoteExtPath' => 'Popups',

View file

@ -43,7 +43,7 @@
]
},
"EventLoggingSchemas": {
"Popups": 11625443
"Popups": 15597282
},
"config": {
"@PopupsBetaFeature": "@var bool: Whether the extension should be enabled as an opt-in beta feature. If true, the BetaFeatures extension must be installed. False by default.",
@ -112,6 +112,21 @@
"targets": [
"desktop"
]
},
"ext.popups.schemaPopups.utils": {
"scripts": [
"resources/ext.popups.schemaPopups.utils.js"
],
"dependencies": [
"mediawiki.experiments",
"mediawiki.Title",
"mediawiki.user",
"ext.popups.core",
"ext.popups.renderer.desktopRenderer"
],
"targets": [
"desktop"
]
}
},
"ResourceFileModulePaths": {

View file

@ -1,5 +1,7 @@
( function ( $, mw ) {
'use strict';
var previewCountStorageKey = 'ext.popups.core.previewCount',
popupsEnabledStorageKey = 'mwe-popups-enabled';
/**
* @class mw.popups
@ -70,7 +72,7 @@
return;
}
mw.popups.render.render( $( this ), event );
mw.popups.render.render( $( this ), event, mw.now(), mw.popups.getRandomToken() );
} );
};
@ -161,4 +163,80 @@
}
};
/**
* Get a random token.
* Append the current timestamp to make the return value more unique.
*
* @return {string}
*/
mw.popups.getRandomToken = function () {
return mw.user.generateRandomSessionId() + Math.round( mw.now() ).toString();
};
/**
* Return edit count bucket based on the number of edits.
* The returned value is "unknown" is `window.localStorage` is not supported.
*
* @return {string}
*/
mw.popups.getPreviewCountBucket = function () {
var bucket,
previewCount = mw.storage.get( previewCountStorageKey );
// no support for localStorage
if ( previewCount === false ) {
return 'unknown';
}
// Fall back to 0 if this is the first time.
previewCount = parseInt( previewCount || 0, 10 );
if ( previewCount === 0 ) {
bucket = '0';
} else if ( previewCount >= 1 && previewCount <= 4 ) {
bucket = '1-4';
} else if ( previewCount >= 5 && previewCount <= 20 ) {
bucket = '5-20';
} else if ( previewCount >= 21 ) {
bucket = '21+';
}
return bucket + ' previews';
};
/**
* Increment the preview count and save it to localStorage.
*/
mw.popups.incrementPreviewCount = function () {
var previewCount = parseInt( mw.storage.get( previewCountStorageKey ) || 0, 10 );
mw.storage.set( previewCountStorageKey, ( previewCount + 1 ).toString() );
};
/**
* Save the popups enabled state via $.jStorage
*
* @param {boolean} isEnabled
*/
mw.popups.saveEnabledState = function ( isEnabled ) {
$.jStorage.set( popupsEnabledStorageKey, isEnabled.toString() );
};
/**
* Retrieve the popups enabled state via $.jStorage or 'wgPopupsExperiment'
* config variable.
* If the experiment isn't running, then continue to enable Popups
* by default during initialisation. In this case the return value
* defaults to `true` if the setting hasn't been saved before.
*
* @return {boolean}
*/
mw.popups.getEnabledState = function () {
if ( !mw.config.get( 'wgPopupsExperiment', false ) ) {
return $.jStorage.get( popupsEnabledStorageKey ) !== 'false';
} else {
return mw.popups.experiment.isUserInCondition();
}
};
} )( jQuery, mediaWiki );

View file

@ -36,9 +36,10 @@
* Send an API request and cache the jQuery element
*
* @param {jQuery} link
* @param {Object} logData data to be logged
* @return {jQuery.Promise}
*/
article.init = function ( link ) {
article.init = function ( link, logData ) {
var href = link.attr( 'href' ),
title = mw.popups.getTitle( href ),
deferred = $.Deferred(),
@ -71,7 +72,14 @@
}
} );
mw.popups.render.currentRequest.fail( deferred.reject );
mw.popups.render.currentRequest.fail( function ( textStatus ) {
mw.track( 'ext.popups.schemaPopups', $.extend( logData, {
action: 'error',
errorState: textStatus,
totalInteractionTime: Math.round( mw.now() - logData.dwellStartTime )
} ) );
deferred.reject();
} );
mw.popups.render.currentRequest.done( function ( re ) {
mw.popups.render.currentRequest = undefined;
@ -149,6 +157,7 @@
mw.popups.render.cache[ href ].settings = {
title: page.title,
namespace: page.ns,
tall: ( tall === undefined ) ? false : tall,
thumbnail: ( thumbnail === undefined ) ? false : thumbnail
};
@ -550,8 +559,9 @@
*
* @method processPopups
* @param {jQuery} link
* @param {Object} logData data to be logged
*/
article.processPopup = function ( link ) {
article.processPopup = function ( link, logData ) {
var
svg = mw.popups.supportsSVG,
cache = mw.popups.render.cache [ link.attr( 'href' ) ],
@ -562,7 +572,15 @@
flippedX = cache.settings.flippedX;
popup.find( '.mwe-popups-settings-icon' ).click( function () {
mw.popups.settings.open();
delete logData.pageTitleHover;
delete logData.namespaceIdHover;
mw.popups.settings.open( $.extend( {}, logData ) );
mw.track( 'ext.popups.schemaPopups', $.extend( logData, {
action: 'tapped settings cog',
totalInteractionTime: Math.round( mw.now() - logData.dwellStartTime )
} ) );
} );
if ( !flippedY && !tall && cache.settings.thumbnail.height < article.SIZES.landscapeImage.h ) {

View file

@ -1,6 +1,19 @@
/*global popupDelay: true, popupHideDelay: true*/
( function ( $, mw ) {
var logData = {};
/**
* Logs the click on link or popup
*
* @param {Object} event
*/
function logClickAction( event ) {
mw.track( 'ext.popups.schemaPopups', $.extend( {}, logData, {
action: mw.popups.getAction( event ),
totalInteractionTime: Math.round( mw.now() - logData.dwellStartTime )
} ) );
}
/**
* @class mw.popups.render
@ -79,8 +92,12 @@
* @method render
* @param {Object} link
* @param {Object} event
* @param {number} dwellStartTime the instant when the link is dwelled on
* @param {string} linkInteractionToken random token representing the current interaction with the link
*/
mw.popups.render.render = function ( link, event ) {
mw.popups.render.render = function ( link, event, dwellStartTime, linkInteractionToken ) {
var linkHref = link.attr( 'href' );
// This will happen when the mouse goes from the popup box back to the
// anchor tag. In such a case, the timer to close the box is cleared.
if (
@ -101,7 +118,7 @@
// Ignore if its meant to call a function
// TODO: Remove this when adding reference popups
if ( link.attr( 'href' ) === '#' ) {
if ( linkHref === '#' ) {
return;
}
@ -109,7 +126,15 @@
mw.popups.disableNavPopup();
mw.popups.render.currentLink = link;
logData = {
pageTitleHover: mw.popups.getTitle( linkHref ),
dwellStartTime: dwellStartTime,
linkInteractionToken: linkInteractionToken
};
link.on( 'mouseleave blur', mw.popups.render.leaveInactive );
link.off( 'click', logClickAction ).on( 'click', logClickAction );
if ( mw.popups.render.cache[ link.attr( 'href' ) ] ) {
mw.popups.render.openTimer = mw.popups.render.wait( mw.popups.render.POPUP_DELAY )
@ -127,14 +152,14 @@
for ( key in renderers ) {
if ( renderers.hasOwnProperty( key ) && key !== 'article' ) {
if ( !!renderers[ key ].matcher( link.attr( 'href' ) ) ) {
cachePopup = renderers[ key ].init( link );
cachePopup = renderers[ key ].init( link, $.extend( {}, logData ) );
}
}
}
// Use the article renderer if nothing else matches
if ( cachePopup === undefined ) {
cachePopup = mw.popups.render.renderers.article.init( link );
cachePopup = mw.popups.render.renderers.article.init( link, $.extend( {}, logData ) );
}
mw.popups.render.openTimer = mw.popups.render.wait( mw.popups.render.POPUP_DELAY - mw.popups.render.API_DELAY );
@ -183,15 +208,15 @@
// More information and workarounds here - http://stackoverflow.com/a/13654655/366138
mw.popups.$popup.html( mw.popups.$popup.html() );
cache.process( link );
// Event logging
mw.popups.logData = {
$.extend( logData, {
pageTitleHover: cache.settings.title,
pageTitleSource: mw.config.get( 'wgTitle' ),
popupEnabled: mw.popups.enabled,
time: mw.now()
};
namespaceIdHover: cache.settings.namespace,
perceivedWait: Math.round( mw.now() - logData.dwellStartTime )
} );
cache.process( link, $.extend( {}, logData ) );
mw.popups.$popup.find( 'a.mwe-popups-extract, a.mwe-popups-discreet' ).click( mw.popups.render.clickHandler );
link
@ -199,6 +224,8 @@
.on( 'mouseleave blur', mw.popups.render.leaveActive );
$( document ).on( 'keydown', mw.popups.render.closeOnEsc );
mw.popups.incrementPreviewCount();
};
/**
@ -208,15 +235,11 @@
* @param {Object} event
*/
mw.popups.render.clickHandler = function ( event ) {
var action = mw.popups.getAction( event ),
logData;
var action = mw.popups.getAction( event );
logClickAction( event );
if ( action === 'opened in same tab' ) {
logData = $.extend( {}, mw.popups.logData, {
action: action
} );
computeDurationFromTime( logData );
mw.track( 'ext.popups.schemaPopups', logData );
window.location.href = mw.popups.render.currentLink.attr( 'href' );
}
};
@ -228,19 +251,12 @@
* @method closePopup
*/
mw.popups.render.closePopup = function () {
var fadeInClass, fadeOutClass,
logData = $.extend( {}, mw.popups.logData, {
action: 'dismissed'
} );
var fadeInClass, fadeOutClass;
if ( mw.popups.render.currentLink === undefined ) {
return false;
}
// Event logging
computeDurationFromTime( logData );
mw.track( 'ext.popups.schemaPopups', logData );
$( mw.popups.render.currentLink ).off( 'mouseleave blur', mw.popups.render.leaveActive );
fadeInClass = ( mw.popups.$popup.hasClass( 'mwe-popups-fade-in-up' ) ) ?
@ -318,6 +334,11 @@
*/
mw.popups.render.leaveActive = function () {
mw.popups.render.closeTimer = mw.popups.render.wait( mw.popups.render.POPUP_CLOSE_DELAY ).done( function () {
mw.track( 'ext.popups.schemaPopups', $.extend( {}, logData, {
action: 'dismissed',
totalInteractionTime: Math.round( mw.now() - logData.dwellStartTime )
} ) );
mw.popups.render.closePopup();
} );
};
@ -328,6 +349,16 @@
* @method leaveInactive
*/
mw.popups.render.leaveInactive = function () {
if ( logData.dwellStartTime &&
logData.linkInteractionToken &&
mw.now() - logData.dwellStartTime >= 250
) {
mw.track( 'ext.popups.schemaPopups', $.extend( {}, logData, {
action: 'dwelledButAbandoned',
totalInteractionTime: Math.round( mw.now() - logData.dwellStartTime )
} ) );
}
// TODO: should `blur` also be here?
$( mw.popups.render.currentLink ).off( 'mouseleave', mw.popups.render.leaveInactive );
if ( mw.popups.render.openTimer ) {
mw.popups.render.openTimer.abort();
@ -345,22 +376,11 @@
* @method reset
*/
mw.popups.render.reset = function () {
logData = {};
mw.popups.render.currentLink = undefined;
mw.popups.render.currentRequest = undefined;
mw.popups.render.openTimer = undefined;
mw.popups.render.closeTimer = undefined;
};
/**
* Utility function that computes duration from time.
* Modifies the data so that it can be logged with EL.
*
* @ignore
* @param {Object} data
*/
function computeDurationFromTime( data ) {
data.duration = Math.floor( mw.now() - data.time );
delete data.time;
}
} )( jQuery, mediaWiki );

View file

@ -1,15 +1,30 @@
( function ( $, mw ) {
/**
* Log the popup event as defined in the schema
*
* https://meta.wikimedia.org/wiki/Schema:Popups
*/
var schemaPopups = new mw.eventLog.Schema(
var previousLogData,
// Log the popup event as defined in the schema
// https://meta.wikimedia.org/wiki/Schema:Popups
schemaPopups = new mw.eventLog.Schema(
'Popups',
mw.config.get( 'wgPopupsSchemaPopupsSamplingRate', 0 )
mw.popups.schemaPopups.getSamplingRate(),
mw.popups.schemaPopups.getDefaultValues()
);
mw.trackSubscribe( 'ext.popups.schemaPopups', function ( topic, data ) {
var shouldLog = true;
data = mw.popups.schemaPopups.getMassagedData( data );
// Only one action is recorded per link interaction token...
if ( data.linkInteractionToken &&
data.linkInteractionToken === previousLogData.linkInteractionToken ) {
// however, the 'disabled' action takes two clicks by nature, so allow it
if ( data.action !== 'disabled' ) {
shouldLog = false;
}
}
if ( shouldLog ) {
schemaPopups.log( data );
}
previousLogData = data;
} );
} )( jQuery, mediaWiki );

View file

@ -0,0 +1,111 @@
( function ( $, mw ) {
/**
* Return data that will be logged with each EL request
*
* @return {Object}
*/
function getDefaultValues() {
var defaults = {
pageTitleSource: mw.config.get( 'wgTitle' ),
namespaceIdSource: mw.config.get( 'wgNamespaceNumber' ),
pageIdSource: mw.config.get( 'wgArticleId' ),
isAnon: mw.user.isAnon(),
popupEnabled: mw.popups.getEnabledState(),
popupDelay: mw.popups.render.POPUP_DELAY,
pageToken: mw.user.generateRandomSessionId() +
Math.floor( mw.now() ).toString() +
mw.user.generateRandomSessionId(),
// arbitrary name that represents the current UI of the popups
version: 'legacy',
// current API version
api: 'mwapi'
};
// Include edit count bucket to the list of default values if the user is logged in.
if ( !mw.user.isAnon() ) {
defaults.editCountBucket = mw.popups.schemaPopups.getEditCountBucket(
mw.config.get( 'wgUserEditCount' ) );
}
// Also add the session token
defaults.sessionToken = mw.user.sessionId();
return defaults;
}
/**
* Return the sampling rate for the Schema:Popups
*
* User's session ID is used to determine the eligibility for logging,
* thus the function will result the same outcome as long as the browser
* hasn't been restarted or the cookie hasn't been cleared.
*
* @return {number}
*/
function getSamplingRate() {
var bucket,
samplingRate = mw.config.get( 'wgPopupsSchemaPopupsSamplingRate', 0 );
if ( !$.isFunction( navigator.sendBeacon ) ) {
return 0;
}
bucket = mw.experiments.getBucket( {
name: 'ext.popups.schemaPopus',
enabled: true,
buckets: {
control: 1 - samplingRate,
A: samplingRate
}
}, mw.user.sessionId() );
return bucket === 'A' ? 1 : 0;
}
/**
* Return edit count bucket based on the number of edits
*
* @param {number} editCount
* @return {string}
*/
function getEditCountBucket( editCount ) {
var bucket;
if ( editCount === 0 ) {
bucket = '0';
} else if ( editCount >= 1 && editCount <= 4 ) {
bucket = '1-4';
} else if ( editCount >= 5 && editCount <= 99 ) {
bucket = '5-99';
} else if ( editCount >= 100 && editCount <= 999 ) {
bucket = '100-999';
} else if ( editCount >= 1000 ) {
bucket = '1000+';
}
return bucket + ' edits';
}
/**
* Return data after making some adjustments so that it's ready to be logged
*
* @param {Object}data
* @return {Object}
*/
function getMassagedData( data ) {
data.previewCountBucket = mw.popups.getPreviewCountBucket();
delete data.dwellStartTime;
// Figure out `namespaceIdHover` from `pageTitleHover`.
if ( data.pageTitleHover && data.namespaceIdHover === undefined ) {
data.namespaceIdHover = new mw.Title( data.pageTitleHover ).getNamespaceId();
}
return data;
}
mw.popups.schemaPopups = {
getDefaultValues: getDefaultValues,
getSamplingRate: getSamplingRate,
getEditCountBucket: getEditCountBucket,
getMassagedData: getMassagedData
};
} )( jQuery, mediaWiki );

View file

@ -1,10 +1,11 @@
( function ( $, mw ) {
var currentLinkLogData,
/**
* @class mw.popups.settings
* @singleton
*/
var settings = {};
settings = {};
/**
* The settings' dialog's section element.
@ -138,13 +139,16 @@
settings.save = function () {
var v = $( 'input[name=mwe-popups-setting]:checked', '#mwe-popups-settings' ).val();
if ( v === 'simple' ) {
$.jStorage.set( 'mwe-popups-enabled', 'true' );
mw.popups.saveEnabledState( true );
settings.reloadPage();
settings.close();
} else {
$.jStorage.set( 'mwe-popups-enabled', 'false' );
mw.popups.saveEnabledState( false );
$( '#mwe-popups-settings-form' ).hide();
$( '#mwe-popups-settings-help' ).show();
mw.track( 'ext.popups.schemaPopups', $.extend( {}, currentLinkLogData, {
action: 'disabled'
} ) );
}
};
@ -152,12 +156,15 @@
* Show the settings element and position it correctly
*
* @method open
* @param {Object} logData data to log
*/
settings.open = function () {
settings.open = function ( logData ) {
var
h = $( window ).height(),
w = $( window ).width();
currentLinkLogData = logData;
$( 'body' ).append( $( '<div>' ).addClass( 'mwe-popups-overlay' ) );
if ( !settings.$element ) {

View file

@ -1,12 +1,5 @@
( function ( $, mw ) {
// If the experiment isn't running, then continue to enable Popups by default during
// initialisation.
if ( !mw.config.get( 'wgPopupsExperiment', false ) ) {
mw.popups.enabled = $.jStorage.get( 'mwe-popups-enabled' ) !== 'false';
} else {
mw.popups.enabled = mw.popups.experiment.isUserInCondition();
}
mw.popups.enabled = mw.popups.getEnabledState();
/**
* Returns valid jQuery selectors for which a popup should be triggered.
@ -99,7 +92,8 @@
};
mw.hook( 'wikipage.content' ).add( function ( $content ) {
var $elements;
var $elements, dwellStartTime, linkInteractionToken;
mw.popups.$content = $content;
$elements = mw.popups.selectPopupElements();
@ -107,6 +101,13 @@
mw.popups.removeTooltips( $elements );
mw.popups.setupTriggers( $elements );
} else {
$elements.on( mw.popups.triggers, function () {
// cache the hover start time and link interaction token for a later use
dwellStartTime = mw.now();
linkInteractionToken = mw.popups.getRandomToken();
} );
// Events are logged even when Hovercards are disabled
// See T88166 for details
$elements.on( 'click', function ( event ) {
@ -116,9 +117,9 @@
mw.track( 'ext.popups.schemaPopups', {
pageTitleHover: $this.attr( 'title' ),
pageTitleSource: mw.config.get( 'wgTitle' ),
popupEnabled: mw.popups.enabled,
action: action
action: action,
totalInteractionTime: Math.round( mw.now() - dwellStartTime ),
linkInteractionToken: linkInteractionToken
} );
if ( action === 'opened in same tab' ) {
@ -135,5 +136,9 @@
mw.popups.createSVGMask();
mw.popups.createPopupElement();
}
mw.track( 'ext.popups.schemaPopups', {
action: 'pageLoaded'
} );
} );
} )( jQuery, mediaWiki );

View file

@ -185,4 +185,199 @@
}
} );
QUnit.test( 'getRandomToken', function ( assert ) {
var token;
QUnit.expect( 3 );
token = mw.popups.getRandomToken();
assert.ok(
token.length > mw.user.generateRandomSessionId().length,
'Random token is long enough.'
);
assert.ok(
typeof token === 'string',
'Random token is a string.'
);
assert.notEqual(
mw.popups.getRandomToken(),
token,
'Newly generated random token is not equal to the one generated earlier.'
);
} );
QUnit.test( 'getPreviewCountBucket', function ( assert ) {
var i, previewCount, bucket,
cases = [
[ '0', '0 previews' ],
[ '1', '1-4 previews' ],
[ '2', '1-4 previews' ],
[ '4', '1-4 previews' ],
[ '5', '5-20 previews' ],
[ '10', '5-20 previews' ],
[ '20', '5-20 previews' ],
[ '21', '21+ previews' ],
[ '100', '21+ previews' ],
[ '1000', '21+ previews' ]
],
storageKey = 'ext.popups.core.previewCount',
mwStorageStub = this.sandbox.stub( mw.storage, 'get' )
.withArgs( storageKey );
QUnit.expect( cases.length + 1 );
mwStorageStub.returns( false );
assert.equal(
mw.popups.getPreviewCountBucket(),
'unknown',
'Preview count bucket is `unkown` when localStorage is unsupported.'
);
for ( i = 0; i < cases.length; i++ ) {
previewCount = cases[ i ][ 0 ];
mwStorageStub.returns( previewCount );
bucket = mw.popups.getPreviewCountBucket();
assert.equal(
bucket,
cases[ i ][ 1 ],
'Preview count bucket is "' + bucket + '" when preview count is ' +
previewCount + '.'
);
}
} );
QUnit.test( 'incrementPreviewCount', function ( assert ) {
var storageKey = 'ext.popups.core.previewCount',
mwStorageGetStub = this.sandbox.stub( mw.storage, 'get' )
.withArgs( storageKey ),
mwStorageSetStub = this.sandbox.stub( mw.storage, 'set' );
QUnit.expect( 3 );
mwStorageGetStub.returns( null );
mw.popups.incrementPreviewCount();
assert.equal(
mwStorageSetStub.firstCall.args[ 1 ],
1,
'Incrementing the preview count to 1 when no value has' +
' been saved in localStorage yet.'
);
mwStorageGetStub.returns( '1' );
mw.popups.incrementPreviewCount();
assert.equal(
mwStorageSetStub.secondCall.args[ 1 ],
2,
'Incrementing the preview count to 2 when the value in localStorage' +
' is already "1".'
);
mwStorageGetStub.returns( '5' );
mw.popups.incrementPreviewCount();
assert.equal(
mwStorageSetStub.thirdCall.args[ 1 ],
6,
'Incrementing the preview count to 6 when the value in localStorage' +
' is already "5".'
);
} );
QUnit.test( 'saveEnabledState', function ( assert ) {
var storageKey = 'mwe-popups-enabled',
jStorageStub = this.sandbox.stub( $.jStorage, 'set' )
.withArgs( storageKey );
QUnit.expect( 2 );
mw.popups.saveEnabledState( true );
assert.equal(
jStorageStub.firstCall.args[ 1 ],
'true',
'Popups enabled state has been saved as "true".'
);
mw.popups.saveEnabledState( false );
assert.equal(
jStorageStub.secondCall.args[ 1 ],
'false',
'Popups enabled state has been saved as "false".'
);
} );
QUnit.test( 'getEnabledState', function ( assert ) {
var storageKey = 'mwe-popups-enabled',
mwConfigStub = this.sandbox.stub( mw.config, 'get' )
.withArgs( 'wgPopupsExperiment' ),
jStorageStub = this.sandbox.stub( $.jStorage, 'get' )
.withArgs( storageKey ),
experimentStub = this.sandbox.stub( mw.popups.experiment,
'isUserInCondition' );
QUnit.expect( 7 );
mwConfigStub.returns( null );
jStorageStub.returns( null );
assert.equal(
mw.popups.getEnabledState(),
true,
'Popups are enabled when the experiment is not running, nor has' +
' Popups been disabled via the settings.'
);
mwConfigStub.returns( null );
jStorageStub.returns( 'true' );
assert.equal(
mw.popups.getEnabledState(),
true,
'Popups are enabled when the experiment is not running and when' +
' Popups has been enabled via the settings.'
);
mwConfigStub.returns( null );
jStorageStub.returns( 'false' );
assert.equal(
mw.popups.getEnabledState(),
false,
'Popups are disabled when the experiment is not running and when' +
' Popups has been disabled via the settings.'
);
mwConfigStub.returns( false );
jStorageStub.returns( 'true' );
assert.equal(
mw.popups.getEnabledState(),
true,
'Popups are enabled when the experiment is disabled and when' +
' Popups has been enabled via the settings.'
);
mwConfigStub.returns( false );
jStorageStub.returns( 'false' );
assert.equal(
mw.popups.getEnabledState(),
false,
'Popups are disabled when the experiment is disabled and when' +
' Popups has been disabled via the settings.'
);
mwConfigStub.returns( true );
experimentStub.returns( true );
assert.equal(
mw.popups.getEnabledState(),
true,
'Popups are disabled when the experiment is running and ' +
' the user is bucketed.'
);
mwConfigStub.returns( true );
experimentStub.returns( false );
assert.equal(
mw.popups.getEnabledState(),
false,
'Popups are disabled when the experiment is running and ' +
' the user is not bucketed.'
);
} );
} )( jQuery, mediaWiki );

View file

@ -0,0 +1,104 @@
( function ( $, mw ) {
var schemaPopups = mw.popups.schemaPopups;
QUnit.module( 'ext.popups.schemaPopups.utils' );
QUnit.test( 'getSamplingRate', function ( assert ) {
var configStub = this.sandbox.stub( mw.config, 'get' )
.withArgs( 'wgPopupsSchemaPopupsSamplingRate' ),
isFunctionStub = this.sandbox.stub( $, 'isFunction' )
.withArgs( navigator.sendBeacon ),
mwUserSessionIdStub = this.sandbox.stub( mw.user, 'sessionId' );
QUnit.expect( 9 );
isFunctionStub.returns( false );
assert.equal( schemaPopups.getSamplingRate(), 0,
'Sampling rate is 0 when `navigator.sendBeacon` is unavailable.' );
isFunctionStub.returns( true );
configStub.returns( null );
mwUserSessionIdStub.returns( 'abc' );
assert.equal( schemaPopups.getSamplingRate(), 0,
'Sampling rate is 0 when the `wgPopupsSchemaPopupsSamplingRate`' +
' config variable is undefined and' +
' `mw.user.sessionId() = ' + mw.user.sessionId() + '`.' );
configStub.returns( null );
mwUserSessionIdStub.returns( 'def' );
assert.equal( schemaPopups.getSamplingRate(), 0,
'Sampling rate is 0 when the `wgPopupsSchemaPopupsSamplingRate`' +
' config variable is undefined and' +
' `mw.user.sessionId() = ' + mw.user.sessionId() + '`.' );
configStub.returns( 0 );
mwUserSessionIdStub.returns( 'abc' );
assert.equal( schemaPopups.getSamplingRate(), 0,
'Sampling rate is 0 when `wgPopupsSchemaPopupsSamplingRate = 0`' +
' `mw.user.sessionId() = ' + mw.user.sessionId() + '`.' );
configStub.returns( 0 );
mwUserSessionIdStub.returns( 'def' );
assert.equal( schemaPopups.getSamplingRate(), 0,
'Sampling rate is 0 when `wgPopupsSchemaPopupsSamplingRate = 0`' +
' `mw.user.sessionId() = ' + mw.user.sessionId() + '`.' );
configStub.returns( 1 );
mwUserSessionIdStub.returns( 'abc' );
assert.equal( schemaPopups.getSamplingRate(), 1,
'Sampling rate is 1 when `wgPopupsSchemaPopupsSamplingRate = 1`' +
' `mw.user.sessionId() = ' + mw.user.sessionId() + '`.' );
configStub.returns( 1 );
mwUserSessionIdStub.returns( 'def' );
assert.equal( schemaPopups.getSamplingRate(), 1,
'Sampling rate is 1 when `wgPopupsSchemaPopupsSamplingRate = 1`' +
' `mw.user.sessionId() = ' + mw.user.sessionId() + '`.' );
configStub.returns( 0.5 );
mwUserSessionIdStub.returns( 'abc' );
assert.equal( schemaPopups.getSamplingRate(), 1,
'Sampling rate is 1 when `wgPopupsSchemaPopupsSamplingRate = 0.5` and' +
' `mw.user.sessionId() = ' + mw.user.sessionId() + '`.' );
configStub.returns( 0.5 );
mwUserSessionIdStub.returns( 'def' );
assert.equal( schemaPopups.getSamplingRate(), 0,
'Sampling rate is 0 when `wgPopupsSchemaPopupsSamplingRate = 0.5` and' +
' `mw.user.sessionId() = ' + mw.user.sessionId() + '`.' );
} );
QUnit.test( 'getEditCountBucket', function ( assert ) {
var i, bucket, editCount,
cases = [
[ 0, '0 edits' ],
[ 1, '1-4 edits' ],
[ 2, '1-4 edits' ],
[ 4, '1-4 edits' ],
[ 5, '5-99 edits' ],
[ 25, '5-99 edits' ],
[ 50, '5-99 edits' ],
[ 99, '5-99 edits' ],
[ 100, '100-999 edits' ],
[ 101, '100-999 edits' ],
[ 500, '100-999 edits' ],
[ 999, '100-999 edits' ],
[ 1000, '1000+ edits' ],
[ 1500, '1000+ edits' ]
];
QUnit.expect( cases.length );
for ( i = 0; i < cases.length; i++ ) {
editCount = cases[ i ][ 0 ];
bucket = schemaPopups.getEditCountBucket( editCount );
assert.equal(
bucket,
cases[ i ][ 1 ],
'Edit count bucket is "' + bucket + '" when edit count is ' +
editCount + '.'
);
}
} );
} )( jQuery, mediaWiki );