mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Popups
synced 2024-11-15 03:34:03 +00:00
557fd113c8
Instead of using constructor functions, use factory functions to generate the gateway objects, because of: * Consistency on the approach in the repository, no constructor functions are used, the factory function pattern prevales. * Real private data with closures * No use of `this`, which is dynamic and unbound in JS and a source of errors. In contrast, by using a factory function, the function/methods-of-the-object are tightly bound to the internal data. Additional changes: * Specify more the type of createRESTBaseGateway's parameter to improve clarity on the intent of the parameter * From: @param {jQuery} api * To: @param {Function} ajax function from jQuery for example Supporting documentation: * https://medium.com/javascript-scene/javascript-factory-functions-vs-constructor-functions-vs-classes-2f22ceddf33e Change-Id: Iacbb098b646843a01f459b15343057e2e4851d35
393 lines
8.2 KiB
JavaScript
393 lines
8.2 KiB
JavaScript
( function ( mw, $ ) {
|
|
|
|
function generateToken() {
|
|
return '9876543210';
|
|
}
|
|
|
|
QUnit.module( 'ext.popups/actions' );
|
|
|
|
QUnit.test( '#boot', function ( assert ) {
|
|
var config = new mw.Map(),
|
|
stubUser = mw.popups.tests.stubs.createStubUser( /* isAnon = */ true ),
|
|
stubUserSettings,
|
|
action;
|
|
|
|
config.set( {
|
|
wgTitle: 'Foo',
|
|
wgNamespaceNumber: 1,
|
|
wgArticleId: 2,
|
|
wgUserEditCount: 3,
|
|
wgPopupsConflictsWithNavPopupGadget: true
|
|
} );
|
|
|
|
stubUserSettings = {
|
|
getPreviewCount: function () {
|
|
return 22;
|
|
}
|
|
};
|
|
|
|
assert.expect( 1 );
|
|
|
|
action = mw.popups.actions.boot(
|
|
false,
|
|
stubUser,
|
|
stubUserSettings,
|
|
generateToken,
|
|
config
|
|
);
|
|
|
|
assert.deepEqual(
|
|
action,
|
|
{
|
|
type: 'BOOT',
|
|
isEnabled: false,
|
|
isNavPopupsEnabled: true,
|
|
sessionToken: '0123456789',
|
|
pageToken: '9876543210',
|
|
page: {
|
|
title: 'Foo',
|
|
namespaceID: 1,
|
|
id: 2
|
|
},
|
|
user: {
|
|
isAnon: true,
|
|
editCount: 3,
|
|
previewCount: 22
|
|
}
|
|
}
|
|
);
|
|
} );
|
|
|
|
/**
|
|
* Stubs `mw.popups.wait` and adds the deferred and its promise as properties
|
|
* of the module.
|
|
*
|
|
* @param {Object} module
|
|
*/
|
|
function setupWait( module ) {
|
|
module.waitDeferred = $.Deferred();
|
|
module.waitPromise = module.waitDeferred.promise();
|
|
|
|
module.sandbox.stub( mw.popups, 'wait', function () {
|
|
return module.waitPromise;
|
|
} );
|
|
}
|
|
|
|
/**
|
|
* Sets up an `a` element that can be passed to action creators.
|
|
*
|
|
* @param {Object} module
|
|
*/
|
|
function setupEl( module ) {
|
|
module.el = $( '<a>' )
|
|
.data( 'page-previews-title', 'Foo' )
|
|
.eq( 0 );
|
|
}
|
|
|
|
QUnit.module( 'ext.popups/actions#linkDwell @integration', {
|
|
setup: function () {
|
|
var that = this;
|
|
|
|
this.state = {
|
|
preview: {}
|
|
};
|
|
this.getState = function () {
|
|
return that.state;
|
|
};
|
|
|
|
setupWait( this );
|
|
setupEl( this );
|
|
}
|
|
} );
|
|
|
|
QUnit.test( '#linkDwell', function ( assert ) {
|
|
var done = assert.async(),
|
|
event = {},
|
|
dispatch = this.sandbox.spy();
|
|
|
|
this.sandbox.stub( mw, 'now' ).returns( new Date() );
|
|
this.sandbox.stub( mw.popups.actions, 'fetch' );
|
|
|
|
// Set the state for when dispatch is called. New token is accepted
|
|
this.state.preview = {
|
|
activeToken: generateToken()
|
|
};
|
|
|
|
mw.popups.actions.linkDwell( this.el, event, /* gateway = */ null, generateToken )(
|
|
dispatch,
|
|
this.getState
|
|
);
|
|
|
|
assert.deepEqual( dispatch.getCall( 0 ).args[ 0 ], {
|
|
type: 'LINK_DWELL',
|
|
el: this.el,
|
|
event: event,
|
|
token: '9876543210',
|
|
timestamp: mw.now()
|
|
} );
|
|
|
|
// Stub the state tree being updated.
|
|
this.state.preview = {
|
|
enabled: true,
|
|
activeLink: this.el,
|
|
activeToken: generateToken()
|
|
};
|
|
|
|
// ---
|
|
|
|
this.waitPromise.then( function () {
|
|
assert.strictEqual(
|
|
dispatch.callCount,
|
|
2,
|
|
'The fetch action is dispatched after 50 ms'
|
|
);
|
|
|
|
done();
|
|
} );
|
|
|
|
// After 50 ms...
|
|
this.waitDeferred.resolve();
|
|
} );
|
|
|
|
QUnit.test( '#linkDwell doesn\'t continue when previews are disabled', function ( assert ) {
|
|
var done = assert.async(),
|
|
event = {},
|
|
dispatch = this.sandbox.spy();
|
|
|
|
mw.popups.actions.linkDwell( this.el, event, /* gateway = */ null, generateToken )(
|
|
dispatch,
|
|
this.getState
|
|
);
|
|
|
|
this.state.preview = {
|
|
enabled: false
|
|
};
|
|
|
|
this.waitPromise.then( function () {
|
|
assert.strictEqual( dispatch.callCount, 1 );
|
|
|
|
done();
|
|
} );
|
|
|
|
// After 500 ms...
|
|
this.waitDeferred.resolve();
|
|
} );
|
|
|
|
QUnit.test( '#linkDwell doesn\'t continue if the token has changed', function ( assert ) {
|
|
var done = assert.async(),
|
|
event = {},
|
|
dispatch = this.sandbox.spy();
|
|
|
|
mw.popups.actions.linkDwell( this.el, event, /* gateway = */ null, generateToken )(
|
|
dispatch,
|
|
this.getState
|
|
);
|
|
|
|
this.state.preview = {
|
|
enabled: true,
|
|
|
|
// Consider the user tabbing back and forth between two links in the time
|
|
// it takes to start fetching data via the gateway: the active link hasn't
|
|
// changed, but the active token has.
|
|
activeLink: this.el,
|
|
|
|
activeToken: '0123456789'
|
|
};
|
|
|
|
this.waitPromise.then( function () {
|
|
assert.strictEqual( dispatch.callCount, 1 );
|
|
|
|
done();
|
|
} );
|
|
|
|
// After 500 ms...
|
|
this.waitDeferred.resolve();
|
|
} );
|
|
|
|
QUnit.test( '#linkDwell doesn\'t continue if the interaction is the same one', function ( assert ) {
|
|
var done = assert.async(),
|
|
event = {},
|
|
dispatch = this.sandbox.spy();
|
|
|
|
this.state.preview = {
|
|
activeToken: '0123456789'
|
|
};
|
|
|
|
mw.popups.actions.linkDwell( this.el, event, /* gateway = */ null, generateToken )(
|
|
dispatch,
|
|
this.getState
|
|
);
|
|
|
|
this.waitPromise.then( function () {
|
|
assert.strictEqual( dispatch.callCount, 1 );
|
|
|
|
done();
|
|
} );
|
|
|
|
// After 500 ms...
|
|
this.waitDeferred.resolve();
|
|
} );
|
|
|
|
QUnit.module( 'ext.popups/actions#fetch', {
|
|
setup: function () {
|
|
var that = this;
|
|
|
|
this.gatewayDeferred = $.Deferred(),
|
|
this.gatewayPromise = this.gatewayDeferred.promise();
|
|
this.gateway = {
|
|
getPageSummary: this.sandbox.stub().returns( this.gatewayPromise )
|
|
};
|
|
|
|
// Setup the mw.now stub.
|
|
that.now = 0;
|
|
|
|
this.sandbox.stub( mw, 'now', function () {
|
|
return that.now;
|
|
} );
|
|
|
|
setupWait( this );
|
|
setupEl( this );
|
|
|
|
this.dispatch = this.sandbox.spy();
|
|
|
|
// Sugar.
|
|
this.fetch = function () {
|
|
mw.popups.actions.fetch( that.gateway, that.el, that.now )( that.dispatch );
|
|
};
|
|
}
|
|
} );
|
|
|
|
QUnit.test( 'it should fetch data from the gateway immediately', function ( assert ) {
|
|
this.fetch();
|
|
|
|
assert.ok( this.gateway.getPageSummary.calledWith( 'Foo' ) );
|
|
|
|
assert.ok(
|
|
this.dispatch.calledWith( {
|
|
type: 'FETCH_START',
|
|
el: this.el,
|
|
title: 'Foo'
|
|
} ),
|
|
'It dispatches the FETCH_START action immediately.'
|
|
);
|
|
} );
|
|
|
|
QUnit.test( 'it should delay dispatching the FETCH_END action', function ( assert ) {
|
|
var that = this,
|
|
result = {};
|
|
|
|
this.fetch();
|
|
|
|
this.gatewayPromise.then( function () {
|
|
assert.ok(
|
|
mw.popups.wait.calledWith( 250 ),
|
|
'FETCH_END is delayed by 250 (500 - 250) ms. ' +
|
|
'If you\'ve changed FETCH_END_TARGET_DELAY, then have you spoken with #Design about changing this value?'
|
|
);
|
|
|
|
that.waitDeferred.resolve();
|
|
|
|
assert.ok( that.dispatch.calledWith( {
|
|
type: 'FETCH_END',
|
|
el: that.el,
|
|
result: result
|
|
} ) );
|
|
} );
|
|
|
|
// The API request took 250 ms.
|
|
this.now += 250;
|
|
this.gatewayDeferred.resolve( result );
|
|
} );
|
|
|
|
QUnit.test(
|
|
'it shouldn\'t delay dispatching the FETCH_END action if the API request is over the target',
|
|
function ( assert ) {
|
|
|
|
this.fetch();
|
|
|
|
this.gatewayPromise.then( function () {
|
|
assert.ok(
|
|
mw.popups.wait.calledWith( 0 ),
|
|
'FETCH_END isn\'t delayed.'
|
|
);
|
|
} );
|
|
|
|
// The API request took 301 ms.
|
|
this.now += 501;
|
|
this.gatewayDeferred.resolve();
|
|
}
|
|
);
|
|
|
|
QUnit.module( 'ext.popups/actions#abandon', {
|
|
setup: function () {
|
|
setupWait( this );
|
|
}
|
|
} );
|
|
|
|
QUnit.test( 'it should dispatch start and end actions', function ( assert ) {
|
|
var that = this,
|
|
dispatch = that.sandbox.spy(),
|
|
token = '0123456789',
|
|
getState = function () {
|
|
return {
|
|
preview: {
|
|
activeToken: token
|
|
}
|
|
};
|
|
},
|
|
done = assert.async();
|
|
|
|
this.sandbox.stub( mw, 'now' ).returns( new Date() );
|
|
|
|
mw.popups.actions.abandon( that.el )( dispatch, getState );
|
|
|
|
assert.ok( dispatch.calledWith( {
|
|
type: 'ABANDON_START',
|
|
timestamp: mw.now(),
|
|
token: token
|
|
} ) );
|
|
|
|
// ---
|
|
|
|
assert.ok(
|
|
mw.popups.wait.calledWith( 300 ),
|
|
'Have you spoken with #Design about changing this value?'
|
|
);
|
|
|
|
that.waitPromise.then( function () {
|
|
assert.ok(
|
|
dispatch.calledWith( {
|
|
type: 'ABANDON_END',
|
|
token: token
|
|
} ),
|
|
'ABANDON_* share the same token.'
|
|
);
|
|
|
|
done();
|
|
} );
|
|
|
|
// After 300 ms...
|
|
that.waitDeferred.resolve();
|
|
} );
|
|
|
|
QUnit.module( 'ext.popups/actions#saveSettings' );
|
|
|
|
QUnit.test( 'it should dispatch an action with previous and current enabled state', function ( assert ) {
|
|
var dispatch = this.sandbox.spy(),
|
|
getState = this.sandbox.stub().returns( {
|
|
preview: {
|
|
enabled: false
|
|
}
|
|
} );
|
|
|
|
mw.popups.actions.saveSettings( /* enabled = */ true )( dispatch, getState );
|
|
|
|
assert.ok( getState.calledOnce, 'it should query the global state for the current state' );
|
|
assert.ok( dispatch.calledWith( {
|
|
type: 'SETTINGS_CHANGE',
|
|
wasEnabled: false,
|
|
enabled: true
|
|
} ), 'it should dispatch the action with the previous and next enabled state' );
|
|
} );
|
|
}( mediaWiki, jQuery ) );
|