node-qunit tests for GrowthExperiments

This also moves serializeActionData into a separate module.

Change-Id: I46e4d0746c90d0e97f73de8b272d67df8d27936e
这个提交包含在:
Kosta Harlan 2019-03-26 17:41:53 -04:00
父节点 9657448e6c
当前提交 815808e086
共有 16 个文件被更改,包括 240 次插入47 次删除

2
.gitignore vendored
查看文件

@ -1,3 +1,5 @@
/node_modules
/vendor
/composer.lock
/.nyc_output
/coverage

9
.nycrc.json 普通文件
查看文件

@ -0,0 +1,9 @@
{
"all": true,
"include": [ "modules/**/*.js" ],
"statements": 52,
"branches": 45,
"functions": 52,
"lines": 52,
"check-coverage": false
}

查看文件

@ -272,7 +272,8 @@
"ext.growthExperiments.Homepage": {
"packageFiles": [
"homepage/ext.growthExperiments.Homepage.js",
"homepage/ext.growthExperiments.Homepage.Logger.js"
"homepage/ext.growthExperiments.Homepage.Logger.js",
"utils/ext.growthExperiments.Utils.js"
],
"dependencies": [
"mediawiki.user"
@ -325,6 +326,7 @@
"packageFiles": [
"help/ext.growthExperiments.Help.js",
"help/ext.growthExperiments.HelpPanelLogger.js",
"utils/ext.growthExperiments.Utils.js",
"help/ext.growthExperiments.HelpPanelSearchWidget.js",
"help/ext.growthExperiments.HelpPanelProcessDialog.js",
{

查看文件

@ -11,12 +11,14 @@
.hide();
$emailInput.after( $warningBox );
}
// eslint-disable-next-line no-jquery/no-slide
$warningBox.slideDown();
} )
.on( 'blur', function () {
// Hide the warning again if the user leaves the email input without typing anything
var $warningBox = $emailInput.next( '.warning' );
if ( $warningBox.length && $emailInput.val().trim() === '' ) {
// eslint-disable-next-line no-jquery/no-slide
$warningBox.slideUp();
}
} );

查看文件

@ -1,5 +1,7 @@
( function () {
var Utils = require( '../utils/ext.growthExperiments.Utils.js' );
/**
* @class mw.libs.ge.HelpPanelLogger
* @constructor
@ -28,7 +30,7 @@
{
action: action,
/* eslint-disable-next-line camelcase */
action_data: this.serializeActionData( data )
action_data: Utils.serializeActionData( data )
},
this.getMetaData(),
metadataOverride
@ -43,27 +45,6 @@
this.previousEditorInterface = eventData.editor_interface;
};
HelpPanelLogger.prototype.serializeActionData = function ( data ) {
if ( !data ) {
return '';
}
if ( typeof data === 'object' ) {
return Object.keys( data )
.map( function ( key ) {
return key + '=' + data[ key ];
} )
.join( ';' );
}
if ( Array.isArray( data ) ) {
return data.join( ';' );
}
// assume it is string or number or bool
return data;
};
HelpPanelLogger.prototype.getMetaData = function () {
var editor = this.getEditor(),
readingMode = editor === 'reading';
@ -112,11 +93,13 @@
}
// Mobile: wikitext
// eslint-disable-next-line no-jquery/no-global-selector
if ( $( 'textarea#wikitext-editor:visible' ).length ) {
return 'wikitext';
}
// Mobile: VE
// eslint-disable-next-line no-jquery/no-global-selector
if ( $( '.ve-init-mw-mobileArticleTarget:visible' ).length ) {
return 'visualeditor';
}
@ -142,6 +125,7 @@
}
// Desktop: old wikitext editor
// eslint-disable-next-line no-jquery/no-global-selector
if ( $( '#wpTextbox1:visible' ).length ) {
return 'wikitext';
}

查看文件

@ -10,10 +10,12 @@
}
$( function () {
// eslint-disable-next-line no-jquery/no-global-selector
var $buttonToInfuse = $( '#mw-ge-help-panel-cta-button' ),
$buttonWrapper = $buttonToInfuse.parent(),
$mfOverlay,
$veUiOverlay,
// eslint-disable-next-line no-jquery/no-global-selector
$body = $( 'body' ),
windowManager = new OO.ui.WindowManager( { modal: OO.ui.isMobile() } ),
$overlay = $( '<div>' ).addClass( 'mw-ge-help-panel-widget-overlay' ),
@ -110,9 +112,11 @@
// HACK: Detach the MobileFrontend overlay for both VE and source edit modes.
// Per T212967, leaving them enabled results in a phantom text input that the
// user can only see the cursor input for.
// eslint-disable-next-line no-jquery/no-global-selector
$mfOverlay = $( '.overlay' ).detach();
// Detach the VE UI overlay, needed to prevent interference with scrolling in
// our dialog.
// eslint-disable-next-line no-jquery/no-global-selector
$veUiOverlay = $( '.ve-ui-overlay' ).detach();
// More hacks. WindowManager#toggleGlobalEvents adds the modal-active class,
// which is styled with position:relative. This seems to interfere with search

查看文件

@ -1,5 +1,7 @@
( function () {
var Utils = require( '../utils/ext.growthExperiments.Utils.js' );
/**
* @param {boolean} enabled
* @param {string} homepagePageviewToken
@ -28,7 +30,7 @@
mw.track( 'event.HomepageModule', {
/* eslint-disable camelcase */
action: action,
action_data: this.serializeActionData( data ),
action_data: Utils.serializeActionData( data ),
user_id: this.userId,
user_editcount: this.userEditCount,
module: module,
@ -38,26 +40,5 @@
} );
};
HomepageModuleLogger.prototype.serializeActionData = function ( data ) {
if ( !data ) {
return '';
}
if ( typeof data === 'object' ) {
return Object.keys( data )
.map( function ( key ) {
return key + '=' + data[ key ];
} )
.join( ';' );
}
if ( Array.isArray( data ) ) {
return data.join( ';' );
}
// assume it is string or number or bool
return data;
};
module.exports = HomepageModuleLogger;
}() );

查看文件

@ -16,6 +16,7 @@
logger: logger
}, config.dialog ) );
// eslint-disable-next-line no-jquery/no-global-selector
$( 'body' ).append( windowManager.$element );
windowManager.addWindows( [ dialog ] );

查看文件

@ -34,9 +34,11 @@
logger.log( moduleName, 'impression', getModuleExtraData( moduleName ) );
};
/* eslint-disable no-jquery/no-event-shorthand */
$( moduleSelector )
.hover( handleHover( 'in' ), handleHover( 'out' ) )
.on( 'click', '[data-link-id]', handleClick )
.each( logImpression );
/* eslint-enable no-jquery/no-event-shorthand */
}() );

查看文件

@ -0,0 +1,34 @@
( function () {
/**
* Serialize data for use with action_data event logging property.
*
* @param {Object|string|boolean|integer|Array} data
* @return {string|*}
*/
function serializeActionData( data ) {
if ( !data ) {
return '';
}
if ( Array.isArray( data ) ) {
return data.join( ';' );
}
if ( typeof data === 'object' ) {
return Object.keys( data )
.map( function ( key ) {
return key + '=' + data[ key ];
} )
.join( ';' );
}
// assume it is string or number or bool
return data;
}
module.exports = {
serializeActionData: serializeActionData
};
}() );

查看文件

@ -12,6 +12,7 @@
privacyStatementUrl: mw.config.get( 'wgWelcomeSurveyPrivacyPolicyUrl' )
} );
// eslint-disable-next-line no-jquery/no-global-selector
$( 'body' ).append( windowManager.$element );
windowManager.addWindows( [ survey, confirmation ] );
windowManager.openWindow( survey );

查看文件

@ -1,10 +1,18 @@
{
"private": true,
"scripts": {
"test": "grunt test"
"test": "grunt test && npm run test:unit",
"test:unit": "NODE_PATH=modules nyc --reporter=lcovonly --reporter=lcov --reporter=text --reporter=text-summary qunit 'tests/node-qunit/**/*.test.js'"
},
"devDependencies": {
"eslint-config-wikimedia": "0.9.0",
"jquery": "3.3.1",
"jsdom": "14.0.0",
"oojs": "2.2.2",
"oojs-ui": "0.31.1",
"qunit": "2.9.2",
"sinon": "7.2.7",
"nyc": "13.1.0",
"eslint-config-wikimedia": "0.11.0",
"grunt": "1.0.3",
"grunt-eslint": "21.0.0",
"grunt-banana-checker": "0.6.0",

查看文件

@ -0,0 +1,13 @@
{
"extends": [
"wikimedia/qunit",
"../../.eslintrc.json"
],
"globals": {
"global": true,
"require": true
},
"rules": {
"no-implicit-globals": "off"
}
}

查看文件

@ -0,0 +1,74 @@
var jsdom = require( 'jsdom' ),
sinon = require( 'sinon' ),
HelpPanelLogger = require( '../../../modules/help/ext.growthExperiments.HelpPanelLogger.js' ),
sandbox,
dom;
QUnit.module( 'HelpPanelLogger', {
beforeEach: function () {
sandbox = sinon.createSandbox();
dom = new jsdom.JSDOM( '<!doctype html><html><body></body></html>' );
global.window = dom.window;
global.document = global.window.document;
global.jQuery = global.$ = window.jQuery = window.$ = require( 'jquery' );
global.OO = require( 'oojs' );
// Both OOUI and the WMF theme need to be loaded into scope via require();
// properties are automatically added to OO namespace.
require( 'oojs-ui' );
require( 'oojs-ui/dist/oojs-ui-wikimediaui.js' );
global.mw = {};
global.mw.config = {};
global.mw.config.get = sinon.stub().returns( 42 );
global.mw.user = {};
global.mw.user.generateRandomSessionId = sinon.stub().returns( 'foo' );
global.mw.user.getId = sinon.stub().returns( 24 );
global.mw.user.sessionId = sinon.stub().returns( 'bar' );
global.mw.track = sinon.stub();
global.mw.Uri = sinon.stub().returns( {
query: sinon.stub()
} );
},
afterEach: function () {
delete require.cache[ require.resolve( 'jquery' ) ];
sandbox.reset();
}
}, function () {
QUnit.test( 'disabled/enabled', function ( assert ) {
var helpPanelLogger = new HelpPanelLogger( false );
helpPanelLogger.log();
assert.strictEqual( global.mw.track.called, false );
helpPanelLogger = new HelpPanelLogger( true );
helpPanelLogger.log();
assert.strictEqual( global.mw.track.called, true );
} );
QUnit.test( 'log', function ( assert ) {
var helpPanelLogger = new HelpPanelLogger( true, {
editorInterface: 'reading',
sessionId: 'foo'
} );
// eslint-disable-next-line camelcase
helpPanelLogger.log( 'impression', 'blah', { editor_interface: 'wikitext' } );
assert.strictEqual( global.mw.track.getCall( 0 ).args[ 0 ], 'event.HelpPanel' );
assert.deepEqual( global.mw.track.getCall( 0 ).args[ 1 ], {
/* eslint-disable camelcase */
action: 'impression',
action_data: 'blah',
user_id: 24,
user_editcount: 42,
editor_interface: 'wikitext',
is_mobile: false,
page_id: 0,
page_title: '',
page_ns: 42,
user_can_edit: 42,
page_protection: '',
session_token: 'bar',
help_panel_session_id: 'foo'
/* eslint-enable camelcase */
} );
} );
} );

查看文件

@ -0,0 +1,64 @@
var jsdom = require( 'jsdom' ),
sinon = require( 'sinon' ),
HomepageModuleLogger = require( '../../../modules/homepage/ext.growthExperiments.Homepage.Logger.js' ),
sandbox,
dom;
QUnit.module( 'HomepageLogger', {
beforeEach: function () {
sandbox = sinon.createSandbox();
dom = new jsdom.JSDOM( '<!doctype html><html><body></body></html>' );
global.window = dom.window;
global.document = global.window.document;
global.jQuery = global.$ = window.jQuery = window.$ = require( 'jquery' );
global.OO = require( 'oojs' );
// Both OOUI and the WMF theme need to be loaded into scope via require();
// properties are automatically added to OO namespace.
require( 'oojs-ui' );
require( 'oojs-ui/dist/oojs-ui-wikimediaui.js' );
global.mw = {};
global.mw.config = {};
global.mw.config.get = sinon.stub().returns( 42 );
global.mw.user = {};
global.mw.user.generateRandomSessionId = sinon.stub().returns( 'foo' );
global.mw.user.getId = sinon.stub().returns( 24 );
global.mw.user.sessionId = sinon.stub().returns( 'bar' );
global.mw.track = sinon.stub();
global.mw.Uri = sinon.stub().returns( {
query: sinon.stub()
} );
},
afterEach: function () {
delete require.cache[ require.resolve( 'jquery' ) ];
sandbox.reset();
}
}, function () {
QUnit.test( 'disabled/enabled', function ( assert ) {
var homepageModuleLogger = new HomepageModuleLogger( false, 'blah' );
homepageModuleLogger.log();
assert.strictEqual( global.mw.track.called, false );
homepageModuleLogger = new HomepageModuleLogger( true, 'blah' );
homepageModuleLogger.log();
assert.strictEqual( global.mw.track.called, true );
} );
QUnit.test( 'log', function ( assert ) {
var homepageModuleLogger = new HomepageModuleLogger( true, 'blah' );
homepageModuleLogger.log( 'tutorial', 'hover-in', { foo: 'bar' } );
assert.strictEqual( global.mw.track.getCall( 0 ).args[ 0 ], 'event.HomepageModule' );
assert.deepEqual( global.mw.track.getCall( 0 ).args[ 1 ], {
/* eslint-disable camelcase */
action: 'hover-in',
action_data: 'foo=bar',
user_id: 24,
user_editcount: 42,
module: 'tutorial',
is_mobile: false,
homepage_pageview_token: 'blah'
/* eslint-enable camelcase */
} );
} );
} );

查看文件

@ -0,0 +1,12 @@
QUnit.module( 'ext.growthExperiments.Utils.js', {}, function () {
QUnit.test( 'serializeActionData', function ( assert ) {
var Utils = require( '../../../modules/utils/ext.growthExperiments.Utils.js' );
assert.strictEqual( Utils.serializeActionData( null ), '' );
assert.strictEqual( Utils.serializeActionData( { foo: 'bar', blah: 1 } ), 'foo=bar;blah=1' );
assert.strictEqual( Utils.serializeActionData( [ 'abc', 'def', 'ghi' ] ), 'abc;def;ghi' );
assert.strictEqual( Utils.serializeActionData( '' ), '' );
assert.strictEqual( Utils.serializeActionData( 'foo' ), 'foo' );
assert.strictEqual( Utils.serializeActionData( 42 ), 42 );
assert.strictEqual( Utils.serializeActionData( true ), true );
} );
} );