2017-07-28 17:32:46 +00:00
|
|
|
import * as Redux from 'redux';
|
|
|
|
import * as ReduxThunk from 'redux-thunk';
|
|
|
|
import * as WaitModule from '../../src/wait';
|
|
|
|
import * as stubs from './stubs';
|
|
|
|
import * as actions from '../../src/actions';
|
|
|
|
import reducers from '../../src/reducers';
|
|
|
|
import registerChangeListener from '../../src/changeListener';
|
|
|
|
|
|
|
|
var mw = mediaWiki,
|
|
|
|
$ = jQuery,
|
|
|
|
// Store the real wait to be actually used in tests
|
2018-01-11 15:44:26 +00:00
|
|
|
wait = WaitModule.default,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether Gateway#getPageSummary is resolved or rejected.
|
|
|
|
* @enum {number}
|
|
|
|
*/
|
|
|
|
FETCH_RESOLUTION = { RESOLVE: 0, REJECT: 1 };
|
2017-02-27 16:10:50 +00:00
|
|
|
|
|
|
|
function identity( x ) { return x; }
|
|
|
|
function constant( x ) { return function () { return x; }; }
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Integration tests for actions and state of the preview part of the system.
|
|
|
|
* Follows a diagram of the interactions considered valid, which will be
|
|
|
|
* used as a basis for the following tests:
|
|
|
|
*
|
|
|
|
|
|
|
|
+--------+
|
|
|
|
|INACTIVE+-----------------------+
|
|
|
|
+---+----+ |
|
|
|
|
^ |
|
|
|
|
| link or preview |
|
|
|
|
| abandon end |
|
|
|
|
| link_dwell|
|
|
|
|
| |
|
|
|
|
| |
|
|
|
|
| |
|
|
|
|
+---|----------------------------|---+
|
|
|
|
| | ACTIVE | |
|
|
|
|
+---|----------------------------|---+
|
|
|
|
| + v |
|
|
|
|
| OFF_LINK ON_LINK | Inside ACTIVE, or out
|
|
|
|
| + ^ link or preview | ^ | of it, only actions with
|
|
|
|
| | | abandon start | | | that same active link are
|
|
|
|
| | +----------------------+ | | valid. Others are ignored.
|
|
|
|
| | | |
|
|
|
|
| +----------------------------+ |
|
|
|
|
| preview or same link dwell |
|
|
|
|
| |
|
|
|
|
| NO_DATA +-------> DATA |
|
|
|
|
| fetch end |
|
|
|
|
+------------------------------------+
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
QUnit.module( 'ext.popups preview @integration', {
|
2017-04-25 15:10:11 +00:00
|
|
|
beforeEach: function () {
|
2017-07-28 17:32:46 +00:00
|
|
|
var that = this;
|
2017-02-27 16:10:50 +00:00
|
|
|
|
2017-03-26 21:05:32 +00:00
|
|
|
// The worst-case implementation of mw.now.
|
|
|
|
mw.now = function () { return Date.now(); };
|
|
|
|
|
2017-02-27 16:10:50 +00:00
|
|
|
this.resetWait = function () {
|
|
|
|
that.waitDeferred = $.Deferred();
|
|
|
|
that.waitPromise = that.waitDeferred.promise();
|
|
|
|
that.wait.returns( that.waitPromise );
|
|
|
|
};
|
|
|
|
|
2017-07-28 17:32:46 +00:00
|
|
|
this.wait = this.sandbox.stub( WaitModule, 'default' );
|
2017-02-27 16:10:50 +00:00
|
|
|
this.resetWait();
|
|
|
|
|
|
|
|
this.store = Redux.createStore(
|
|
|
|
Redux.combineReducers( reducers ),
|
|
|
|
Redux.compose( Redux.applyMiddleware( ReduxThunk.default ) )
|
|
|
|
);
|
|
|
|
|
|
|
|
this.actions = Redux.bindActionCreators(
|
|
|
|
actions,
|
|
|
|
this.store.dispatch
|
|
|
|
);
|
|
|
|
|
|
|
|
this.registerChangeListener = function ( fn ) {
|
|
|
|
return registerChangeListener( that.store, fn );
|
|
|
|
};
|
|
|
|
|
2017-06-08 15:23:44 +00:00
|
|
|
this.title = stubs.createStubTitle( 1, 'Foo' );
|
2017-05-09 18:41:23 +00:00
|
|
|
|
2017-06-08 15:23:44 +00:00
|
|
|
this.el = $( '<a href="/wiki/Foo">' );
|
2017-05-09 18:41:23 +00:00
|
|
|
|
2017-02-27 16:10:50 +00:00
|
|
|
this.actions.boot(
|
|
|
|
/* isEnabled: */
|
|
|
|
constant( true ),
|
|
|
|
/* user */
|
|
|
|
{ sessionId: constant( 'sessiontoken' ),
|
|
|
|
isAnon: constant( true ) },
|
|
|
|
/* userSettings: */
|
|
|
|
{ getPreviewCount: constant( 1 ) },
|
|
|
|
/* generateToken: */
|
|
|
|
constant( 'pagetoken' ),
|
|
|
|
/* config: */
|
|
|
|
{ get: identity }
|
|
|
|
);
|
|
|
|
|
2018-01-18 18:48:16 +00:00
|
|
|
this.dwell = function (
|
|
|
|
title, el, ev, fetchResponse, resolution = FETCH_RESOLUTION.RESOLVE
|
|
|
|
) {
|
2017-02-27 16:10:50 +00:00
|
|
|
that.resetWait();
|
2017-06-08 15:23:44 +00:00
|
|
|
that.actions.linkDwell( title, el, ev, {
|
2017-02-27 16:10:50 +00:00
|
|
|
getPageSummary: function () {
|
2018-01-18 18:48:16 +00:00
|
|
|
var method = resolution === FETCH_RESOLUTION.RESOLVE ?
|
|
|
|
'resolve' : 'reject';
|
2018-01-11 15:44:26 +00:00
|
|
|
return $.Deferred()[ method ]( fetchResponse ).promise();
|
2017-02-27 16:10:50 +00:00
|
|
|
}
|
|
|
|
}, function () { return 'pagetoken'; } );
|
|
|
|
return that.waitPromise;
|
|
|
|
};
|
|
|
|
|
2018-01-18 18:48:16 +00:00
|
|
|
this.dwellAndShowPreview = function (
|
|
|
|
title, el, ev, fetchResponse, reject = FETCH_RESOLUTION.RESOLVE
|
|
|
|
) {
|
2018-01-11 15:44:26 +00:00
|
|
|
that.dwell( title, el, ev, fetchResponse, reject );
|
2017-02-27 16:10:50 +00:00
|
|
|
that.waitDeferred.resolve();
|
2017-03-26 23:18:29 +00:00
|
|
|
|
|
|
|
// Wait for the next tick to resolve pending callbacks. N.B. that the
|
|
|
|
// fetch action invokes wait twice.
|
|
|
|
return wait( 0 )
|
|
|
|
.then( function () {
|
|
|
|
that.waitDeferred.resolve();
|
|
|
|
|
|
|
|
return that.waitPromise;
|
|
|
|
} );
|
2017-02-27 16:10:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
this.abandon = function () {
|
|
|
|
that.resetWait();
|
|
|
|
that.actions.abandon();
|
|
|
|
return that.waitPromise;
|
|
|
|
};
|
|
|
|
|
|
|
|
this.abandonAndWait = function () {
|
|
|
|
that.abandon();
|
|
|
|
that.waitDeferred.resolve();
|
|
|
|
return wait( 0 ); // Wait for next tick to resolve pending callbacks
|
|
|
|
};
|
|
|
|
|
2017-06-08 15:23:44 +00:00
|
|
|
this.dwellAndPreviewDwell = function ( title, el, ev, res ) {
|
|
|
|
return that.dwellAndShowPreview( title, el, ev, res ).then( function () {
|
2017-02-27 16:10:50 +00:00
|
|
|
|
|
|
|
// Get out of the link, and before the delay ends...
|
|
|
|
var abandonPromise = that.abandon( el ),
|
|
|
|
abandonDeferred = that.waitDeferred;
|
|
|
|
|
|
|
|
// Dwell over the preview
|
|
|
|
that.actions.previewDwell( el );
|
|
|
|
|
|
|
|
// Then the abandon delay finishes
|
|
|
|
abandonDeferred.resolve();
|
|
|
|
|
|
|
|
return abandonPromise;
|
|
|
|
} );
|
|
|
|
};
|
|
|
|
|
|
|
|
this.abandonPreview = function () {
|
|
|
|
that.resetWait();
|
|
|
|
that.actions.abandon();
|
|
|
|
|
|
|
|
return that.waitPromise;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
QUnit.test( 'it boots in INACTIVE state', function ( assert ) {
|
|
|
|
var state = this.store.getState();
|
|
|
|
|
|
|
|
assert.equal( state.preview.activeLink, undefined );
|
|
|
|
assert.equal( state.preview.linkInteractionToken, undefined );
|
|
|
|
} );
|
|
|
|
|
|
|
|
QUnit.test( 'in INACTIVE state, a link dwell switches it to ACTIVE', function ( assert ) {
|
|
|
|
var state,
|
|
|
|
gateway = {
|
|
|
|
getPageSummary: function () {
|
|
|
|
$.Deferred().promise();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
this.actions.linkDwell(
|
2017-06-08 15:23:44 +00:00
|
|
|
this.title, this.el, 'event',
|
2017-02-27 16:10:50 +00:00
|
|
|
gateway,
|
|
|
|
constant( 'pagetoken' )
|
|
|
|
);
|
|
|
|
state = this.store.getState();
|
2017-05-09 18:41:23 +00:00
|
|
|
assert.equal( state.preview.activeLink, this.el, 'It has an active link' );
|
2017-02-27 16:10:50 +00:00
|
|
|
assert.equal( state.preview.shouldShow, false, 'Initializes with NO_DATA' );
|
|
|
|
} );
|
|
|
|
|
|
|
|
QUnit.test( 'in ACTIVE state, fetch end switches it to DATA', function ( assert ) {
|
|
|
|
var store = this.store,
|
2017-05-09 18:41:23 +00:00
|
|
|
el = this.el;
|
|
|
|
|
2018-01-18 18:48:16 +00:00
|
|
|
return this.dwellAndShowPreview( this.title, el, 'event', 42 )
|
|
|
|
.then( function () {
|
|
|
|
var state = store.getState();
|
|
|
|
assert.equal( state.preview.activeLink, el );
|
|
|
|
assert.equal(
|
|
|
|
state.preview.shouldShow, true,
|
|
|
|
'Should show when data has been fetched' );
|
|
|
|
} );
|
2017-02-27 16:10:50 +00:00
|
|
|
} );
|
|
|
|
|
2018-01-11 15:44:26 +00:00
|
|
|
QUnit.test( 'in ACTIVE state, fetch fail switches it to DATA', function ( assert ) {
|
|
|
|
var store = this.store,
|
|
|
|
el = this.el;
|
|
|
|
|
2018-01-18 18:48:16 +00:00
|
|
|
return this.dwellAndShowPreview(
|
|
|
|
this.title, el, 'event', 42, FETCH_RESOLUTION.REJECT
|
|
|
|
).then( function () {
|
2018-01-11 15:44:26 +00:00
|
|
|
var state = store.getState();
|
|
|
|
assert.equal( state.preview.activeLink, el );
|
2018-01-18 18:48:16 +00:00
|
|
|
assert.equal( state.preview.shouldShow, true,
|
|
|
|
'Should show when data couldn\'t be fetched' );
|
2018-01-11 15:44:26 +00:00
|
|
|
} );
|
|
|
|
} );
|
|
|
|
|
2017-02-27 16:10:50 +00:00
|
|
|
QUnit.test( 'in ACTIVE state, abandon start, and then end, switch it to INACTIVE', function ( assert ) {
|
|
|
|
var that = this,
|
2017-05-09 18:41:23 +00:00
|
|
|
el = this.el;
|
|
|
|
|
2018-01-18 18:48:16 +00:00
|
|
|
return this.dwellAndShowPreview( this.title, el, 'event', 42 )
|
|
|
|
.then( function () {
|
|
|
|
return that.abandonAndWait( el );
|
|
|
|
} ).then( function () {
|
|
|
|
var state = that.store.getState();
|
|
|
|
assert.equal( state.preview.activeLink, undefined,
|
|
|
|
'After abandoning, preview is back to INACTIVE' );
|
|
|
|
} );
|
2017-02-27 16:10:50 +00:00
|
|
|
} );
|
|
|
|
|
|
|
|
QUnit.test( 'in ACTIVE state, abandon link, and then dwell preview, should keep it active after all delays', function ( assert ) {
|
|
|
|
var that = this,
|
2017-05-09 18:41:23 +00:00
|
|
|
el = this.el;
|
|
|
|
|
2017-08-16 18:27:35 +00:00
|
|
|
return this.dwellAndPreviewDwell( this.title, el, 'event', 42 )
|
2017-02-27 16:10:50 +00:00
|
|
|
.then( function () {
|
|
|
|
var state = that.store.getState();
|
2017-05-09 18:41:23 +00:00
|
|
|
assert.equal( state.preview.activeLink, el );
|
2017-02-27 16:10:50 +00:00
|
|
|
} );
|
|
|
|
} );
|
|
|
|
|
|
|
|
QUnit.test( 'in ACTIVE state, abandon link, hover preview, back to link, should keep it active after all delays', function ( assert ) {
|
|
|
|
var that = this,
|
2017-05-09 18:41:23 +00:00
|
|
|
el = this.el;
|
2017-02-27 16:10:50 +00:00
|
|
|
|
2017-08-16 18:27:35 +00:00
|
|
|
return this.dwellAndPreviewDwell( this.title, el, 'event', 42 )
|
2017-02-27 16:10:50 +00:00
|
|
|
.then( function () {
|
|
|
|
var abandonPreviewDeferred, dwellPromise, dwellDeferred;
|
|
|
|
|
|
|
|
// Start abandoning the preview
|
2017-05-09 18:41:23 +00:00
|
|
|
that.abandonPreview( el );
|
2017-02-27 16:10:50 +00:00
|
|
|
|
|
|
|
abandonPreviewDeferred = that.waitDeferred;
|
|
|
|
// Dwell back into the link, new event is triggered
|
2017-06-08 15:23:44 +00:00
|
|
|
dwellPromise = that.dwell( that.title, el, 'event2', 42 );
|
2017-02-27 16:10:50 +00:00
|
|
|
dwellDeferred = that.waitDeferred;
|
|
|
|
|
|
|
|
// Preview abandon happens next, before the fetch
|
|
|
|
abandonPreviewDeferred.resolve();
|
|
|
|
|
|
|
|
// Then fetch happens
|
|
|
|
dwellDeferred.resolve();
|
|
|
|
|
|
|
|
return dwellPromise;
|
|
|
|
} )
|
|
|
|
.then( function () {
|
|
|
|
var state = that.store.getState();
|
2017-05-09 18:41:23 +00:00
|
|
|
assert.equal( state.preview.activeLink, el );
|
2017-02-27 16:10:50 +00:00
|
|
|
} );
|
|
|
|
} );
|