mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Popups
synced 2024-11-15 11:46:55 +00:00
9477fd5fe6
If a user has hovercards disabled, when they right click a link this will trigger another hover event which will reset dwellStartTime. This means when a dwelledButAbandoned event fires shortly afterwards the totalInteractionTime will not be correct. To remedy this, the calculation of totalInteractionTime and perceivedWait is moved into ext.popups.schemaPopups Now that we can trigger events without logging to the server and checking the total interaction time duration in two places, let's always run the event and only check it once. A `hover` event triggers the setting of a dwell start time A `display` event triggers the setting of the perceivedWait value * Both are reset on a dwelledButAbandoned. Since dwellStartTime is controlled inside a single place, getMassageData no longer needs to clear it. The test "returns false if dwelledButAbandoned event without a dwellStartTime" is removed as this should no longer be possible. Bug: T147846 Change-Id: Ie5917ca86f0d0ab27f4cf507e6dfa2c271433c03
295 lines
10 KiB
JavaScript
295 lines
10 KiB
JavaScript
( function ( $, mw ) {
|
|
var schemaPopups = mw.popups.schemaPopups;
|
|
|
|
/**
|
|
* Simulates a test run of several events.
|
|
*
|
|
* @param {Array} actions list of event actions
|
|
* @param {Object} [additionalData] to log
|
|
* @return {Array|false} the result of the last event to be run.
|
|
*/
|
|
function runEventSequence( actions, additionalData ) {
|
|
var previousEvent;
|
|
|
|
$.each( actions, function ( i, action ) {
|
|
previousEvent = schemaPopups.getMassagedData( $.extend( {
|
|
action: action
|
|
}, additionalData || {} ), previousEvent );
|
|
} );
|
|
return previousEvent;
|
|
}
|
|
|
|
QUnit.module( 'ext.popups.schemaPopups.utils', {
|
|
setup: function () {
|
|
var ts = 1477422540409;
|
|
this.sandbox.stub( mw.popups, 'getPreviewCountBucket' ).returns(
|
|
'5-20 previews'
|
|
);
|
|
this.sandbox.stub( mw, 'now' )
|
|
.onFirstCall().returns( ts ) // hover
|
|
.onSecondCall().returns( ts + 5000 )
|
|
.onThirdCall().returns( ts + 15000 ) // sometimes second hover if dwelled but abandoned is end of first sequence
|
|
.onCall( 3 ).returns( ts + 15000 + 200 ) // second hover
|
|
.onCall( 4 ).returns( ts + 15000 + 200 + 500 )
|
|
.onCall( 5 ).returns( ts + 15000 + 200 + 700 );
|
|
}
|
|
} );
|
|
|
|
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 + '.'
|
|
);
|
|
}
|
|
} );
|
|
|
|
QUnit.test( 'getMassagedData - dwell start time gets deleted', 2, function ( assert ) {
|
|
var newData,
|
|
initialData = {};
|
|
|
|
newData = schemaPopups.getMassagedData( initialData );
|
|
assert.equal( newData.previewCountBucket, '5-20 previews' );
|
|
assert.ok( newData.dwellStartTime === undefined );
|
|
} );
|
|
|
|
QUnit.test( 'getMassagedData - namespaceIdHover is added', 1, function ( assert ) {
|
|
var newData,
|
|
initialData = {
|
|
pageTitleHover: 'Talk:Foo'
|
|
};
|
|
|
|
newData = schemaPopups.getMassagedData( initialData );
|
|
assert.ok( newData.namespaceIdHover === 1, 'namespace is added based on title' );
|
|
} );
|
|
|
|
QUnit.test( 'getMassagedData - returns false if the data should not be logged due to being a duplicate', 3, function ( assert ) {
|
|
var
|
|
thisEvent = {
|
|
action: 'myevent',
|
|
dwellStartTime: 1,
|
|
linkInteractionToken: 't'
|
|
},
|
|
previousEvent = {
|
|
action: 'myevent',
|
|
linkInteractionToken: 't'
|
|
},
|
|
settingsEvent = {
|
|
action: 'disabled',
|
|
linkInteractionToken: 't'
|
|
};
|
|
|
|
assert.ok( schemaPopups.getMassagedData( thisEvent, previousEvent ) === false, 'duplicate events are ignored...' );
|
|
assert.ok( schemaPopups.getMassagedData( settingsEvent, thisEvent ) !== false, '... unless disabled event' );
|
|
assert.ok( thisEvent.dwellStartTime === 1, 'and no side effects' );
|
|
} );
|
|
|
|
QUnit.test( 'getMassagedData - returns false for hover and display events', 2, function ( assert ) {
|
|
assert.ok( runEventSequence( [ 'hover' ] ) === false );
|
|
assert.ok( runEventSequence( [ 'display' ] ) === false );
|
|
} );
|
|
|
|
QUnit.test( 'getMassagedData - check calculations of opened in new window', 1, function ( assert ) {
|
|
var logData = runEventSequence( [ 'hover', 'display', 'opened in new window' ] );
|
|
|
|
assert.ok( logData.totalInteractionTime === 15000 );
|
|
} );
|
|
|
|
QUnit.test( 'getMassagedData - check calculations of dwelledButAbandoned event', 2, function ( assert ) {
|
|
var logData = runEventSequence( [ 'hover', 'dwelledButAbandoned' ] );
|
|
assert.ok( logData.perceivedWait === undefined );
|
|
assert.ok( logData.totalInteractionTime === 5000 );
|
|
} );
|
|
|
|
QUnit.test( 'getMassagedData - dwelledButAbandoned without hover', 1, function ( assert ) {
|
|
var logData = runEventSequence( [ 'hover', 'dwelledButAbandoned', 'dwelledButAbandoned' ],
|
|
{ linkInteractionToken: 'a' } );
|
|
|
|
assert.ok( logData === false, 'if no interaction time reject it' );
|
|
} );
|
|
|
|
QUnit.test( 'getMassagedData - interaction time is reset on hover', 2, function ( assert ) {
|
|
var logData = runEventSequence( [
|
|
'hover', 'dwelledButAbandoned',
|
|
'hover', 'display', 'opened in new window'
|
|
] );
|
|
|
|
assert.ok( logData.perceivedWait !== undefined );
|
|
assert.ok( logData.totalInteractionTime === 700 );
|
|
} );
|
|
|
|
QUnit.test( 'getMassagedData - multiple dwelledButAbandoned ignored', 1, function ( assert ) {
|
|
var logData = runEventSequence( [
|
|
'hover', 'dwelledButAbandoned', 'dwelledButAbandoned'
|
|
], {
|
|
linkInteractionToken: 'a'
|
|
} );
|
|
|
|
assert.ok( logData === false, 'duplicate dwelledButAbandoned ignored' );
|
|
} );
|
|
|
|
QUnit.test( 'getMassagedData - no display event (opened in same tab)', 2, function ( assert ) {
|
|
var logData = runEventSequence( [
|
|
'hover', 'opened in same tab'
|
|
] );
|
|
|
|
assert.ok( logData.perceivedWait === undefined,
|
|
'if no display event no perceived wait can be calculated' );
|
|
assert.ok( logData.totalInteractionTime === 5000,
|
|
'but totalInteractionTime can be calculated' );
|
|
} );
|
|
|
|
QUnit.test( 'getMassagedData - dwelledButAbandoned triggered if no link interaction token', 1, function ( assert ) {
|
|
var logData = runEventSequence( [
|
|
'hover', 'dwelledButAbandoned', 'dwelledButAbandoned'
|
|
] );
|
|
|
|
assert.ok( logData.totalInteractionTime === undefined,
|
|
// is this correct behaviour? We may want to revisit this.
|
|
'if no link interaction time the duplicate event is generated but has no total interaction time' );
|
|
} );
|
|
|
|
QUnit.test( 'getMassagedData - opened in same tab without a hover', 2, function ( assert ) {
|
|
var logDataSequence = runEventSequence( [
|
|
'opened in same tab'
|
|
], {
|
|
linkInteractionToken: 'a'
|
|
} ),
|
|
logDataSequenceTwo = runEventSequence( [
|
|
'opened in same tab'
|
|
] );
|
|
|
|
assert.ok( logDataSequence.totalInteractionTime === undefined,
|
|
'If a end lifecycle event occurs without a hover event occuring beforehand it generates an invalid event' );
|
|
assert.ok( logDataSequenceTwo.totalInteractionTime === undefined,
|
|
'If a end lifecycle event occurs without a hover event occuring beforehand it generates an invalid event' );
|
|
} );
|
|
|
|
QUnit.test( 'getMassagedData - dwell start time gets reset on dismissed events', 4, function ( assert ) {
|
|
var logDataSequence = runEventSequence( [
|
|
'hover', 'display', 'dismissed'
|
|
], {
|
|
linkInteractionToken: 'a'
|
|
} ),
|
|
logDataSequenceTwo = runEventSequence( [
|
|
'hover', 'display', 'dismissed'
|
|
], {
|
|
linkInteractionToken: 'b'
|
|
} );
|
|
|
|
assert.ok( logDataSequence.totalInteractionTime === 15000 );
|
|
assert.ok( logDataSequence.perceivedWait !== undefined );
|
|
assert.ok( logDataSequenceTwo.totalInteractionTime === 700,
|
|
'The first interaction leads to the rest of the timer.' );
|
|
assert.ok( logDataSequenceTwo.perceivedWait !== undefined );
|
|
} );
|
|
|
|
QUnit.test( 'getMassagedData - perceivedWait should be set for "opened in" events', 2, function ( assert ) {
|
|
var data = runEventSequence( [
|
|
'hover',
|
|
'display',
|
|
'opened in same tab'
|
|
] );
|
|
|
|
assert.ok( data.perceivedWait !== undefined );
|
|
|
|
data = runEventSequence( [
|
|
'hover',
|
|
'display',
|
|
'opened in new tab'
|
|
] );
|
|
|
|
assert.ok( data.perceivedWait !== undefined );
|
|
} );
|
|
} )( jQuery, mediaWiki );
|