Merge remote-tracking branch 'gerrit/page-issues-cleanup'

Bug: T198765
Bug: T208514
Change-Id: I9467101c9b01cbd7682de859e43e04966760eea6
This commit is contained in:
Bartosz Dziewoński 2018-11-08 15:57:35 +01:00
commit 7b5928d795
11 changed files with 165 additions and 582 deletions

View File

@ -91,7 +91,6 @@ class MinervaHooks {
// additional scaffolding (minus initialisation scripts)
'tests/qunit/skins.minerva.scripts/stubs.js',
'resources/skins.minerva.scripts/pageIssuesLogger.js',
'resources/skins.minerva.scripts/pageIssuesParser.js',
'resources/skins.minerva.scripts/DownloadIcon.js',
'resources/skins.minerva.scripts/AB.js',
@ -101,7 +100,6 @@ class MinervaHooks {
'tests/qunit/skins.minerva.scripts/DownloadIcon.test.js',
'tests/qunit/skins.minerva.scripts/pageIssuesParser.test.js',
'tests/qunit/skins.minerva.scripts/AB.test.js',
'tests/qunit/skins.minerva.scripts/PageIssuesOverlay.test.js',
'tests/qunit/skins.minerva.scripts/pageIssues.test.js',
'tests/qunit/skins.minerva.notifications.badge/NotificationBadge.test.js'
],

View File

@ -2,7 +2,6 @@
var
router = require( 'mediawiki.router' ),
issues = M.require( 'skins.minerva.scripts/pageIssues' ),
overlayManager = M.require( 'skins.minerva.scripts/overlayManager' ),
loader = M.require( 'mobile.startup/rlModuleLoader' ),
skin = M.require( 'skins.minerva.scripts/skin' ),
@ -34,7 +33,6 @@
*/
function onEditLinkClick() {
var section = ( new mw.Uri( this.href ) ).query.section || 'all';
issues.log( { action: 'editClicked' } );
router.navigate( '#/editor/' + section );
// prevent folding section when clicking Edit by stopping propagation
return false;

View File

@ -13,13 +13,11 @@
* @extends Overlay
*
* @param {IssueSummary[]} issues list of page issue summaries for display.
* @param {PageIssuesLogger} logger E.g., { log: console.log }.
* @param {string} section
* @param {number} namespaceID
*/
function PageIssuesOverlay( issues, logger, section, namespaceID ) {
function PageIssuesOverlay( issues, section, namespaceID ) {
var
options,
// Note only the main namespace is expected to make use of section issues, so the
// heading will always be minerva-meta-data-issues-section-header regardless of
// namespace.
@ -27,24 +25,10 @@
getNamespaceHeadingText( namespaceID ) :
mwMsg( 'minerva-meta-data-issues-section-header' );
this.issues = issues;
this.logger = logger;
this.section = section;
options = {};
options.issues = issues;
// Set default logging data
this.defaultLoggerData = {};
// In the case of KEYWORD_ALL_SECTIONS all issues are in the overlay and the sectionNumbers
// field should be no different from the default behaviour.
if ( this.section !== KEYWORD_ALL_SECTIONS ) {
this.defaultLoggerData.sectionNumbers = [ this.section ];
}
options.heading = '<strong>' + headingText + '</strong>';
Overlay.call( this, options );
this.on( Overlay.EVENT_EXIT, this.onExit.bind( this ) );
Overlay.call( this, {
issues: issues,
heading: '<strong>' + headingText + '</strong>'
} );
}
OO.mfExtend( PageIssuesOverlay, Overlay, {
@ -54,128 +38,15 @@
*/
className: 'overlay overlay-issues',
/**
* @memberof PageIssuesOverlay
* @instance
*/
events: util.extend( {}, Overlay.prototype.events, {
'click a[href*=redlink]': 'onRedLinkClick',
'click a:not(.external):not([href*=edit])': 'onInternalClick',
// Only register attempts to edit an existing page (should be the one we are on),
// not internal clicks on redlinks to nonexistent pages:
'click a[href*="edit"]:not([href*=redlink])': 'onEditClick'
} ),
/**
* @memberof PageIssuesOverlay
* @instance
*/
templatePartials: util.extend( {}, Overlay.prototype.templatePartials, {
content: mw.template.get( 'skins.minerva.scripts', 'PageIssuesOverlayContent.hogan' )
} ),
/**
* Log data via the associated logger, adding sectionNumbers to override the event default
* if applicable.
* @param {Object} data
* @instance
*/
log: function ( data ) {
this.logger.log( util.extend( {}, this.defaultLoggerData, data ) );
},
/**
* Note: an "on enter" state is tracked by the issueClicked log event.
* @return {void}
*/
onExit: function () {
var logData = {
action: 'modalClose',
issuesSeverity: this.issues.map( issueSummaryToSeverity )
},
currentSection = this.section;
// When users close the modal, `sectionNumbers` should correlate to each visible issue
// in the modal, provided that this.section is a valid number and not
// `KEYWORD_ALL_SECTIONS`.
if ( this.section !== KEYWORD_ALL_SECTIONS ) {
logData.sectionNumbers = this.issues.map( function () {
return currentSection;
} );
}
this.log( logData );
},
/**
* Event that is triggered when an internal link inside the overlay is clicked.
* This event will not be triggered if the link contains the edit keyword,
* in which case onEditClick will be
* fired. This is primarily used for instrumenting page issues (see
* https://meta.wikimedia.org/wiki/Schema:PageIssues).
* @param {JQuery.Event} ev
* @memberof PageIssuesOverlay
* @instance
*/
onInternalClick: function ( ev ) {
var severity = parseSeverity( this.$( ev.target ) );
this.log( {
action: 'modalInternalClicked',
issuesSeverity: [ severity ]
} );
},
/**
* Event that is triggered when a red link (e.g. a link to a page which doesn't exist)
* inside the overlay is clicked.
* @param {JQuery.Event} ev
* @memberof PageIssuesOverlay
* @instance
*/
onRedLinkClick: function ( ev ) {
var severity = parseSeverity( this.$( ev.target ) );
this.log( {
action: 'modalRedLinkClicked',
issuesSeverity: [ severity ]
} );
},
/**
* Event that is triggered when an edit link inside the overlay is clicked.
* This is primarily
* used for instrumenting page issues (see https://meta.wikimedia.org/wiki/Schema:PageIssues).
* The event will not be triggered in the case of red links.
* See onRedLinkClick for red links.
* @param {JQuery.Event} ev
* @memberof PageIssuesOverlay
* @instance
*/
onEditClick: function ( ev ) {
var severity = parseSeverity( this.$( ev.target ) );
this.log( {
action: 'modalEditClicked',
issuesSeverity: [ severity ]
} );
}
} )
} );
/**
* Obtain severity associated with a given $target node by looking at associated parent node
* (defined by templatePartials, PageIssuesOverlayContent.hogan).
*
* @param {JQuery.Object} $target
* @return {string[]} severity as defined in associated PageIssue
*/
function parseSeverity( $target ) {
return $target.parents( '.issue-notice' ).data( 'severity' );
}
/**
* @param {IssueSummary} issue
* @return {string} A PageIssue.severity.
*/
function issueSummaryToSeverity( issue ) {
return issue.severity;
}
/**
* Obtain a suitable heading for the issues overlay based on the namespace
* @param {number} namespaceID is the namespace to generate heading for

View File

@ -2,7 +2,7 @@
{{#issues}}
<li>
<div class="issue-notice" data-severity="{{severity}}">
{{{icon}}}
{{{iconString}}}
<div class="issue-details">{{{text}}}</div>
</div>
</li>

View File

@ -1,105 +1,17 @@
( function ( M, $ ) {
var AB = M.require( 'skins.minerva.scripts/AB' ),
Page = M.require( 'mobile.startup/Page' ),
var Page = M.require( 'mobile.startup/Page' ),
allIssues = {},
KEYWORD_ALL_SECTIONS = 'all',
config = mw.config,
user = mw.user,
NS_MAIN = 0,
NS_TALK = 1,
NS_CATEGORY = 14,
CURRENT_NS = config.get( 'wgNamespaceNumber' ),
Icon = M.require( 'mobile.startup/Icon' ),
pageIssuesLogger = M.require( 'skins.minerva.scripts/pageIssuesLogger' ),
pageIssuesParser = M.require( 'skins.minerva.scripts/pageIssuesParser' ),
PageIssuesOverlay = M.require( 'skins.minerva.scripts/PageIssuesOverlay' ),
// setup ab test
abTest = new AB( {
testName: 'WME.PageIssuesAB',
// Run AB only on article namespace, otherwise set samplingRate to 0,
// forcing user into control (i.e. ignored/not logged) group.
samplingRate: ( CURRENT_NS === NS_MAIN ) ? config.get( 'wgMinervaABSamplingRate', 0 ) : 0,
sessionId: user.sessionId()
} ),
QUERY_STRING_FLAG = mw.util.getParamValue( 'minerva-issues' ),
// Per T204746 a user can request the new treatment regardless of test group
isUserRequestingNewTreatment = QUERY_STRING_FLAG === 'b',
newTreatmentEnabled = abTest.isB() || isUserRequestingNewTreatment;
/**
* @typedef {Object} IssueSummary
* @prop {string} severity A PageIssue.severity.
* @prop {Boolean} isMultiple Whether or not the issue is part of a "multiple issues" template.
* @prop {string} icon HTML string.
* @prop {string} text HTML string.
*/
function isLoggingRequired( pageIssues ) {
// No logging necessary when the A/B test is disabled (control group).
return !isUserRequestingNewTreatment && abTest.isEnabled() && pageIssues.length;
}
/**
* Array.reduce callback that returns the severity of page issues.
* In the case that a page-issue is part of a "multiple issues" template,
* returns the maximum severity for that group of issues.
*
* @param {array} formattedArr - the return array containing severities
* @param {IssueSummary} currentItem current IssueSummary object
* @param {number} currentIndex current index of pageIssues
* @param {array} pageIssues array of pageIssues
*
* @return {array} acc
*/
function formatPageIssuesSeverity( formattedArr, currentItem, currentIndex, pageIssues ) {
var lastItem = pageIssues[ currentIndex - 1 ],
lastFormattedIndex = formattedArr.length - 1,
lastFormattedValue = formattedArr[ lastFormattedIndex ];
// If the last and current item `isMultiple`, fold the maxSeverity
// of the two items into a single value.
if ( lastItem && lastItem.isMultiple && currentItem.isMultiple ) {
formattedArr[ lastFormattedIndex ] = pageIssuesParser.maxSeverity(
[ lastFormattedValue, currentItem.severity ]
);
} else {
formattedArr.push( currentItem.severity );
}
return formattedArr;
}
/**
* Extract a summary message from a cleanup template generated element that is
* friendly for mobile display.
* @param {Object} $box element to extract the message from
* @return {IssueSummary}
*/
function extractMessage( $box ) {
var SELECTOR = '.mbox-text, .ambox-text',
MULTIPLE_SELECTOR = '.mw-collapsible-content',
$container = $( '<div>' ),
pageIssue;
$box.find( SELECTOR ).each( function () {
var contents,
$this = $( this );
// Clean up talk page boxes
$this.find( 'table, .noprint' ).remove();
contents = $this.html();
if ( contents ) {
$( '<p>' ).html( contents ).appendTo( $container );
}
} );
pageIssue = pageIssuesParser.parse( $box.get( 0 ) );
return {
severity: pageIssue.severity,
isMultiple: $box.parent().is( MULTIPLE_SELECTOR ),
icon: pageIssue.icon.toHtmlString(),
text: $container.html()
};
}
// T206179 should update this value to enable it
newTreatmentEnabled = QUERY_STRING_FLAG === 'b';
/**
* Create a link element that opens the issues overlay.
@ -138,8 +50,7 @@
issueUrl = section === KEYWORD_ALL_SECTIONS ? '#/issues/' + KEYWORD_ALL_SECTIONS : '#/issues/' + section,
selector = 'table.ambox, table.tmbox, table.cmbox, table.fmbox',
issues = [],
$link,
severity;
$link;
if ( section === KEYWORD_ALL_SECTIONS ) {
$metadata = page.$( selector );
@ -154,8 +65,8 @@
$this = $( this );
if ( $this.find( selector ).length === 0 ) {
issue = extractMessage( $this );
// Some issues after "extractMessage" has been run will have no text.
issue = pageIssuesParser.extract( $this );
// Some issues after "extract" has been run will have no text.
// For example in Template:Talk header the table will be removed and no issue found.
// These should not be rendered.
if ( issue.text ) {
@ -166,14 +77,10 @@
// store it for later
allIssues[section] = issues;
if ( $metadata.length && inline ) {
severity = pageIssuesParser.maxSeverity(
issues.map( function ( issue ) { return issue.severity; } )
);
new Icon( {
glyphPrefix: 'minerva',
name: pageIssuesParser.iconName( $metadata.get( 0 ), severity )
} ).prependTo( $metadata.find( '.mbox-text' ) );
// If issues were extracted and there are inline amboxes, add learn more
// and icon to the UI element.
if ( issues.length && $metadata.length && inline ) {
issues[0].issue.icon.$el.prependTo( $metadata.eq( 0 ).find( '.mbox-text' ) );
$learnMore = $( '<span>' )
.addClass( 'ambox-learn-more' )
.text( mw.msg( 'skin-minerva-issue-learn-more' ) );
@ -185,34 +92,12 @@
$learnMore.appendTo( $metadata.find( '.mbox-text' ) );
}
$metadata.click( function () {
var pageIssue = pageIssuesParser.parse( this );
pageIssuesLogger.log( {
action: 'issueClicked',
issuesSeverity: [ pageIssue.severity ],
sectionNumbers: [ section ]
} );
overlayManager.router.navigate( issueUrl );
return false;
} );
} else {
$link = createLinkElement( labelText );
$link.attr( 'href', '#/issues/' + section );
$link.click( function () {
pageIssuesLogger.log( {
action: 'issueClicked',
issuesSeverity: [
pageIssuesParser.maxSeverity(
getIssues( '0' )
.map( function ( issue ) { return issue.severity; } )
)
],
// In the old treatment, an issuesClicked event will always be '0'
// as the old treatment is always associated with the lead section and we
// are only sending one maximum severity for all of them.
// An issuesClicked event should only ever be associated with one issue box.
sectionNumbers: [ '0' ]
} );
} );
if ( $metadata.length ) {
$link.insertAfter( $( 'h1#section_0' ) );
$metadata.remove();
@ -257,7 +142,7 @@
var lastIssue = allIssues[ section ][i - 1];
// If the last issue belongs to a "Multiple issues" template,
// and so does the current one, don't add the current one.
if ( lastIssue && lastIssue.isMultiple && issue.isMultiple ) {
if ( lastIssue && lastIssue.grouped && issue.grouped ) {
acc[ acc.length - 1 ] = section;
} else {
acc.push( section );
@ -316,40 +201,16 @@
}
}
if ( isLoggingRequired( getIssues( KEYWORD_ALL_SECTIONS ) ) ) {
// Enable logging of the PageIssues schema, setting up defaults.
pageIssuesLogger.subscribe(
newTreatmentEnabled,
pageIssuesLogger.newPageIssueSchemaData(
newTreatmentEnabled,
CURRENT_NS,
getIssues( KEYWORD_ALL_SECTIONS ).reduce( formatPageIssuesSeverity, [] ),
getAllIssuesSections( allIssues )
)
);
// Report that the page has been loaded.
pageIssuesLogger.log( {
action: 'pageLoaded'
} );
}
// Setup the overlay route.
overlayManager.add( new RegExp( '^/issues/(\\d+|' + KEYWORD_ALL_SECTIONS + ')$' ), function ( section ) {
return new PageIssuesOverlay(
getIssues( section ), pageIssuesLogger, section, CURRENT_NS );
getIssues( section ), section, CURRENT_NS );
} );
}
M.define( 'skins.minerva.scripts/pageIssues', {
init: initPageIssues,
// The logger requires initialization (subscription). Ideally, the logger would be
// initialized and passed to initPageIssues() by the client. Since it's not, expose a log
// method and hide the subscription call in cleanuptemplates.
log: pageIssuesLogger.log,
test: {
formatPageIssuesSeverity: formatPageIssuesSeverity,
extractMessage: extractMessage,
getAllIssuesSections: getAllIssuesSections,
createBanner: createBanner
}

View File

@ -1,111 +0,0 @@
( function ( M, mwConfig, mwTrack, mwTrackSubscribe, mwUser ) {
var
util = M.require( 'mobile.startup/util' ),
EVENT_PAGE_ISSUE_LOG = 'minerva.PageIssuesAB';
/**
* Defines default data for Schema:PageIssues that will be recorded with every event.
* @param {boolean} newTreatmentEnabled
* @param {number} namespaceId The namespace for the page that has issues.
* @param {string[]} pageIssueSeverities An array of PageIssue severities.
* @param {array} pageIssuesSections
*
* @return {Object} A Partial<Schema:PageIssues> Object meant to be mixed with track data.
*/
function newPageIssueSchemaData(
newTreatmentEnabled, namespaceId, pageIssueSeverities, pageIssuesSections
) {
return {
pageTitle: mwConfig.get( 'wgTitle' ),
namespaceId: namespaceId,
pageIdSource: mwConfig.get( 'wgArticleId' ),
issuesVersion: bucketToVersion( newTreatmentEnabled ),
issuesSeverity: pageIssueSeverities,
sectionNumbers: pageIssuesSections,
isAnon: mwUser.isAnon(),
editCountBucket: getUserEditBuckets(),
sessionToken: mwUser.sessionId()
};
}
/**
* Enable tracking and add page token to every logged event.
* @param {boolean} newTreatmentEnabled
* @param {Object} pageIssueSchemaData A Partial<Schema:PageIssues> Object that will be mixed
* with track data.
* @return {void}
*/
function subscribe( newTreatmentEnabled, pageIssueSchemaData ) {
// set the page token on the request.
pageIssueSchemaData.pageToken = mw.user.getPageviewToken();
// intermediary event bus that extends the event data before being passed to event-logging.
mwTrackSubscribe( EVENT_PAGE_ISSUE_LOG, function ( topic, data ) {
var mixedData = util.extend( {}, pageIssueSchemaData, data );
// Although we use strings inside the issues code (due to the usage of the key word
// `all`) - these need to be numbers to be validated by the schema
mixedData.sectionNumbers = ( mixedData.sectionNumbers || [] ).map(
function ( sectionStr ) { return parseInt( sectionStr, 10 ); }
);
// Log readingDepth schema.(ReadingDepth is guarded against multiple enables).
// See https://gerrit.wikimedia.org/r/#/c/mediawiki/extensions/WikimediaEvents/+/437686/
mwTrack( 'wikimedia.ReadingDepthSchema.enable', bucketToGroup( newTreatmentEnabled ) );
// Log PageIssues schema.
mwTrack( 'wikimedia.event.PageIssues', mixedData );
} );
}
/**
* @param {boolean} newTreatmentEnabled
* @return {string} The page issues group associated with the treatment bucket.
*/
function bucketToGroup( newTreatmentEnabled ) {
return newTreatmentEnabled ? 'page-issues-b_sample' : 'page-issues-a_sample';
}
/**
* @param {boolean} newTreatmentEnabled
* @return {string} The page issues version associated with the treatment bucket.
*/
function bucketToVersion( newTreatmentEnabled ) {
return newTreatmentEnabled ? 'new2018' : 'old';
}
/**
* Converts user edit count into a predefined string. Note: these buckets have *nothing* to do
* with A/B bucketing.
* @return {string}
*/
function getUserEditBuckets() {
var editCount = mwConfig.get( 'wgUserEditCount', 0 );
if ( editCount === 0 ) { return '0 edits'; }
if ( editCount < 5 ) { return '1-4 edits'; }
if ( editCount < 100 ) { return '5-99 edits'; }
if ( editCount < 1000 ) { return '100-999 edits'; }
if ( editCount >= 1000 ) { return '1000+ edits'; }
// This is unlikely to ever happen. If so, we'll want to cast to a string
// that is not accepted and allow EventLogging to complain
// about invalid events so we can investigate.
return 'error (' + editCount + ')';
}
/**
* Log data to the PageIssuesAB test schema. It's safe to call this function prior to
* subscription.
* @param {Object} data to log
* @return {void}
*/
function log( data ) {
mwTrack( EVENT_PAGE_ISSUE_LOG, data );
}
M.define( 'skins.minerva.scripts/pageIssuesLogger', {
newPageIssueSchemaData: newPageIssueSchemaData,
subscribe: subscribe,
log: log
} );
}( mw.mobileFrontend, mw.config, mw.track, mw.trackSubscribe, mw.user ) );

View File

@ -2,8 +2,16 @@
/**
* @typedef PageIssue
* @prop {string} severity A SEVERITY_LEVEL key.
* @prop {boolean} grouped True if part of a group of multiple issues, false if singular.
* @prop {Icon} icon
*/
/**
* @typedef {Object} IssueSummary
* @prop {PageIssue} issue
* @prop {string} iconString a string representation of icon.
* This is kept for template compatibility (our views do not yet support composition).
* @prop {string} text HTML string.
*/
var Icon = M.require( 'mobile.startup/Icon' ),
// Icons are matching the type selector below use a TYPE_* icon. When unmatched, the icon is
@ -78,6 +86,7 @@
].join( '|' ) )
// ..And everything else that doesn't match is mapped to a "SEVERITY" type.
},
GROUPED_PARENT_REGEX = /mw-collapsible-content/,
// Variants supported by specific types. The "severity icon" supports all severities but the
// type icons only support one each by ResourceLoader.
TYPE_SEVERITY = {
@ -117,6 +126,14 @@
};
}
/**
* @param {Element} box
* @return {boolean} True if part of a group of multiple issues, false if singular.
*/
function parseGroup( box ) {
return !!box.parentNode && GROUPED_PARENT_REGEX.test( box.parentNode.className );
}
/**
* @param {Element} box
* @param {string} severity An SEVERITY_LEVEL key.
@ -147,6 +164,7 @@
var severity = parseSeverity( box );
return {
severity: severity,
grouped: parseGroup( box ),
icon: new Icon( {
glyphPrefix: 'minerva',
name: iconName( box, severity )
@ -154,21 +172,51 @@
};
}
/**
* Extract a summary message from a cleanup template generated element that is
* friendly for mobile display.
* @param {Object} $box element to extract the message from
* @return {IssueSummary}
*/
function extract( $box ) {
var SELECTOR = '.mbox-text, .ambox-text',
$container = $( '<div>' ),
pageIssue;
$box.find( SELECTOR ).each( function () {
var contents,
$this = $( this );
// Clean up talk page boxes
$this.find( 'table, .noprint' ).remove();
contents = $this.html();
if ( contents ) {
$( '<p>' ).html( contents ).appendTo( $container );
}
} );
pageIssue = parse( $box.get( 0 ) );
return {
issue: pageIssue,
// For template compatibility with PageIssuesOverlay
iconString: pageIssue.icon.toHtmlString(),
text: $container.html()
};
}
/**
* @module skins.minerva.scripts/utils
*/
M.define( 'skins.minerva.scripts/pageIssuesParser', {
/**
* Extract an icon for use with the issue.
* @param {JQuery.Object} $box element to extract the icon from
* @return {Icon} representing the icon
*/
extract: extract,
parse: parse,
maxSeverity: maxSeverity,
iconName: iconName,
test: {
parseSeverity: parseSeverity,
parseType: parseType
parseType: parseType,
parseGroup: parseGroup
}
} );

View File

@ -155,7 +155,6 @@
}
},
"EventLoggingSchemas": {
"PageIssues": 18392542,
"WebClientError": 18340282
},
"ResourceModules": {
@ -435,7 +434,6 @@
"resources/skins.minerva.scripts/errorLogging.js",
"resources/skins.minerva.scripts/preInit.js",
"resources/skins.minerva.scripts/DownloadIcon.js",
"resources/skins.minerva.scripts/pageIssuesLogger.js",
"resources/skins.minerva.scripts/pageIssuesParser.js",
"resources/skins.minerva.scripts/AB.js",
"resources/skins.minerva.scripts/PageIssuesOverlay.js",

View File

@ -1,57 +0,0 @@
( function ( M ) {
var PageIssuesOverlay = M.require( 'skins.minerva.scripts/PageIssuesOverlay' );
QUnit.module( 'Minerva PageIssuesOverlay', {
beforeEach: function () {
this.logger = {
log: this.sandbox.spy()
};
}
} );
QUnit.test( '#log (section=all)', function ( assert ) {
var overlay = new PageIssuesOverlay( [], this.logger, 'all', 0 );
overlay.onExit();
assert.strictEqual( this.logger.log.calledOnce, true, 'Logger called once' );
assert.strictEqual(
this.logger.log.calledWith( {
action: 'modalClose',
issuesSeverity: []
} ), true, 'sectionNumbers is not set (T202940)'
);
} );
QUnit.test( '#log (section=1)', function ( assert ) {
var overlay = new PageIssuesOverlay( [
{
severity: 'MEDIUM'
}
], this.logger, '1', 0 );
overlay.onExit();
assert.strictEqual(
this.logger.log.calledWith( {
action: 'modalClose',
issuesSeverity: [ 'MEDIUM' ],
sectionNumbers: [ '1' ]
} ), true, 'sectionNumbers is set'
);
} );
QUnit.test( '#log (section=2) multiple issues', function ( assert ) {
var overlay = new PageIssuesOverlay(
[ {
severity: 'MEDIUM'
},
{
severity: 'LOW'
} ], this.logger, '2', 0 );
overlay.onExit();
assert.strictEqual(
this.logger.log.calledWith( {
action: 'modalClose',
issuesSeverity: [ 'MEDIUM', 'LOW' ],
sectionNumbers: [ '2', '2' ]
} ), true, 'sectionNumbers is set for each issue'
);
} );
}( mw.mobileFrontend ) );

View File

@ -1,35 +1,30 @@
( function ( M ) {
var pageIssues = M.require( 'skins.minerva.scripts/pageIssues' ),
util = M.require( 'mobile.startup/util' ),
pageIssuesParser = M.require( 'skins.minerva.scripts/pageIssuesParser' ),
extractMessage = pageIssues.test.extractMessage,
createBanner = pageIssues.test.createBanner,
formatPageIssuesSeverity = pageIssues.test.formatPageIssuesSeverity,
icon = {},
MEDIUM_ISSUE = {
severity: 'MEDIUM',
icon: 'i',
text: 't'
},
MEDIUM_MULTIPLE_ISSUE = {
severity: 'MEDIUM',
isMultiple: true,
icon: 'i',
text: 't'
},
LOW_MULTIPLE_ISSUE = {
severity: 'LOW',
isMultiple: true,
icon: 'i',
issue: {
severity: 'MEDIUM',
icon: icon
},
iconString: 'i',
text: 't'
},
LOW_ISSUE = {
severity: 'LOW',
icon: 'i',
issue: {
severity: 'LOW',
icon: icon
},
iconString: 'i',
text: 't'
},
HIGH_ISSUE = {
severity: 'HIGH',
icon: 'i',
issue: {
severity: 'HIGH',
icon: icon
},
iconString: 'i',
text: 't'
},
getAllIssuesSections = pageIssues.test.getAllIssuesSections,
@ -67,93 +62,6 @@
assert.strictEqual( window.location.hash, '#/issues/' + SECTION );
} );
// NOTE: Only for PageIssues AB
QUnit.test( 'clicking on the product of createBanner() should trigger a custom event', function ( assert ) {
var mockAction = {
action: 'issueClicked',
issuesSeverity: [ 'MEDIUM' ],
sectionNumbers: [ SECTION ]
};
mw.trackSubscribe( 'minerva.PageIssuesAB', function ( topic, data ) {
assert.deepEqual( mockAction, data );
} );
processedAmbox.click();
} );
QUnit.test( 'formatPageIssuesSeverity', function ( assert ) {
var multipleIssues = [
MEDIUM_MULTIPLE_ISSUE,
LOW_MULTIPLE_ISSUE,
LOW_MULTIPLE_ISSUE
],
multipleSingleIssues = [
LOW_ISSUE,
HIGH_ISSUE,
MEDIUM_ISSUE
],
mixedMultipleSingle = [
HIGH_ISSUE,
LOW_MULTIPLE_ISSUE,
MEDIUM_MULTIPLE_ISSUE,
LOW_ISSUE,
MEDIUM_ISSUE,
HIGH_ISSUE
],
testMultiple = multipleIssues.reduce( formatPageIssuesSeverity, [] ),
testSingle = multipleSingleIssues.reduce( formatPageIssuesSeverity, [] ),
testMixed = mixedMultipleSingle.reduce( formatPageIssuesSeverity, [] );
assert.deepEqual( testMultiple, [ 'MEDIUM' ], 'Multiple issues return one maxSeverity value' );
assert.deepEqual( testSingle, [ 'LOW', 'HIGH', 'MEDIUM' ], 'Single issues return each corresponding severity' );
assert.deepEqual( testMixed, [ 'HIGH', 'MEDIUM', 'LOW', 'MEDIUM', 'HIGH' ], 'Mixed single/multiple return one value for multiples' );
} );
QUnit.test( 'extractMessage', function ( assert ) {
this.sandbox.stub( pageIssuesParser, 'parse' ).returns(
{
severity: 'LOW',
icon: {
toHtmlString: function () {
return '<icon />';
}
}
}
);
[
[
$( '<div />' ).html(
'<div class="mbox-text">Smelly</div>'
).appendTo( '<div class="mw-collapsible-content" />' ),
{
severity: 'LOW',
isMultiple: true,
icon: '<icon />',
text: '<p>Smelly</p>'
},
'When the box is a child of mw-collapsible-content it isMultiple'
],
[
$( '<div />' ).html(
'<div class="mbox-text">Dirty</div>'
),
{
severity: 'LOW',
isMultiple: false,
icon: '<icon />',
text: '<p>Dirty</p>'
},
'When the box is not child of mw-collapsible-content it !isMultiple'
]
].forEach( function ( test ) {
assert.deepEqual(
extractMessage( test[ 0 ] ),
test[ 1 ],
test[ 2 ]
);
} );
} );
QUnit.test( 'getAllIssuesSections', function ( assert ) {
var multipleIssuesWithDeletion,
multipleIssues, allIssuesOldTreatment, allIssuesNewTreatment;
@ -166,17 +74,17 @@
};
multipleIssues = {
0: [
util.extend( {}, MEDIUM_ISSUE, { isMultiple: true } ),
util.extend( {}, LOW_ISSUE, { isMultiple: true } ),
util.extend( {}, MEDIUM_ISSUE, { isMultiple: true } )
util.extend( {}, MEDIUM_ISSUE, { grouped: true } ),
util.extend( {}, LOW_ISSUE, { grouped: true } ),
util.extend( {}, MEDIUM_ISSUE, { grouped: true } )
]
};
multipleIssuesWithDeletion = {
0: [
HIGH_ISSUE,
util.extend( {}, MEDIUM_ISSUE, { isMultiple: true } ),
util.extend( {}, LOW_ISSUE, { isMultiple: true } ),
util.extend( {}, MEDIUM_ISSUE, { isMultiple: true } )
util.extend( {}, MEDIUM_ISSUE, { grouped: true } ),
util.extend( {}, LOW_ISSUE, { grouped: true } ),
util.extend( {}, MEDIUM_ISSUE, { grouped: true } )
]
};
allIssuesNewTreatment = {

View File

@ -1,5 +1,7 @@
( function ( M ) {
var pageIssuesParser = M.require( 'skins.minerva.scripts/pageIssuesParser' );
var icon = {},
pageIssuesParser = M.require( 'skins.minerva.scripts/pageIssuesParser' ),
extractMessage = pageIssuesParser.extract;
QUnit.module( 'Minerva pageIssuesParser' );
@ -13,6 +15,47 @@
return box;
}
QUnit.test( 'extractMessage', function () {
[
[
$( '<div />' ).html(
'<div class="mbox-text">Smelly</div>'
).appendTo( '<div class="mw-collapsible-content" />' ),
{
issue: {
severity: 'DEFAULT',
grouped: true,
icon: icon
},
iconString: this.sandbox.match.typeOf( 'string' ),
text: '<p>Smelly</p>'
},
'When the box is a child of mw-collapsible-content it grouped'
],
[
$( '<div />' ).html(
'<div class="mbox-text">Dirty</div>'
),
{
issue: {
severity: 'DEFAULT',
grouped: false,
icon: icon
},
iconString: this.sandbox.match.typeOf( 'string' ),
text: '<p>Dirty</p>'
},
'When the box is not child of mw-collapsible-content it !grouped'
]
].forEach( function ( test ) {
sinon.assert.match( // eslint-disable-line no-undef
extractMessage( test[ 0 ] ),
test[ 1 ],
test[ 2 ]
);
} );
} );
QUnit.test( 'parseSeverity', function ( assert ) {
var tests = [
[ '', 'DEFAULT', 'empty' ],
@ -70,6 +113,32 @@
} );
} );
QUnit.test( 'parseGroup', function ( assert ) {
var tests = [
[ undefined, false, 'orphaned' ],
[ '', false, 'ungrouped' ],
[ 'mw-collapsible-content', true, 'grouped' ]
];
tests.forEach( function ( params, i ) {
var
parentClassName = params[0],
expect = params[1],
test = params[2],
parent,
box = newBox( '' );
if ( parentClassName !== undefined ) {
parent = document.createElement( 'div' );
parent.className = parentClassName;
parent.appendChild( box );
}
assert.strictEqual(
pageIssuesParser.test.parseGroup( box ),
expect,
'Result should be the correct grouping; case ' + i + ': ' + test + '.'
);
} );
} );
QUnit.test( 'iconName', function ( assert ) {
var tests = [
[ '', 'DEFAULT', 'issue-generic-defaultColor' ],