mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Popups
synced 2024-11-15 11:46:55 +00:00
1fff0d2ea7
This change fixes some issues with assertions not running, removes unnecessary promise dances, and improves legibility and some code patterns in the action and integration node tests mainly. Detailed changes: * actions.js * Fully migrate out of jQuery 1 promises (no done/fail) * Fix linkDwell action not returning the fetch action promise * actions.test.js * Simplify setupWait for the tests * It always autoresolves immediately the wait call to ensure speedy tests * No waitDeferreds or waitPromises array coordination, rely on action returned promises instead * Get rid of that = this in favor of arrow functions * Rename generic "p" promises to meaningful names * Add assert.expect for more solid tests (so that we don't skip assertions in the future if we change them) * Fix some assertions that weren't being run because of the incorrect promise being returned (p.then, and then just returning p) * Get rid of $.when stubbing in favor of waiting for the promise returned from the action * Get rid of hacky setTimeout(..., 0) to run assertions after the promises * integration.test.js * Get rid of wait(0) calls to hack around asynchronous actions * Use the action returned promises instead of the waitPromises/Deferreds * Remove unused "el" parameter being passed to this.abandon in several tests * Remove unnecessary test helper this.abandonPreview (it was the same as this.abandon) * Clarify a bit the last and more complex test with some comments and variable name changes * Get rid of that=this in favor of arrow functions * container.test.js * Get rid of that=this in favor of arrow functions * previewBehavior.test.js * Get rid of that=this in favor of arrow functions * Get rid of $.each in favor of .forEach Bug: T170807 Change-Id: I06fafd92a1712f143018e2ddff24fadf1a6882b3
257 lines
7.4 KiB
JavaScript
257 lines
7.4 KiB
JavaScript
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,
|
|
/**
|
|
* Whether Gateway#getPageSummary is resolved or rejected.
|
|
* @enum {number}
|
|
*/
|
|
FETCH_RESOLUTION = { RESOLVE: 0, REJECT: 1 };
|
|
|
|
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', {
|
|
beforeEach: function () {
|
|
// The worst-case implementation of mw.now.
|
|
mw.now = function () { return Date.now(); };
|
|
|
|
this.resetWait = () => {
|
|
this.waitDeferred = $.Deferred();
|
|
this.waitPromise = this.waitDeferred.promise();
|
|
this.wait.returns( this.waitPromise );
|
|
};
|
|
|
|
this.wait = this.sandbox.stub( WaitModule, 'default' );
|
|
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 = ( fn ) => {
|
|
return registerChangeListener( this.store, fn );
|
|
};
|
|
|
|
this.title = stubs.createStubTitle( 1, 'Foo' );
|
|
|
|
this.el = $( '<a href="/wiki/Foo">' );
|
|
|
|
this.actions.boot(
|
|
/* isEnabled: */
|
|
constant( true ),
|
|
/* user */
|
|
{ sessionId: constant( 'sessiontoken' ),
|
|
isAnon: constant( true ) },
|
|
/* userSettings: */
|
|
{ getPreviewCount: constant( 1 ) },
|
|
/* generateToken: */
|
|
constant( 'pagetoken' ),
|
|
/* config: */
|
|
{ get: identity }
|
|
);
|
|
|
|
this.dwell = (
|
|
title, el, ev, fetchResponse, resolution = FETCH_RESOLUTION.RESOLVE
|
|
) => {
|
|
this.resetWait();
|
|
return this.actions.linkDwell( title, el, ev, {
|
|
getPageSummary: function () {
|
|
var method = resolution === FETCH_RESOLUTION.RESOLVE ?
|
|
'resolve' : 'reject';
|
|
return $.Deferred()[ method ]( fetchResponse ).promise();
|
|
}
|
|
}, function () { return 'pagetoken'; } );
|
|
};
|
|
|
|
this.dwellAndShowPreview = (
|
|
title, el, ev, fetchResponse, reject = FETCH_RESOLUTION.RESOLVE
|
|
) => {
|
|
var dwelled = this.dwell( title, el, ev, fetchResponse, reject );
|
|
// Resolve the wait timeout for the linkDwell and the fetch action
|
|
this.waitDeferred.resolve();
|
|
return dwelled;
|
|
};
|
|
|
|
this.abandon = () => {
|
|
this.resetWait();
|
|
return this.actions.abandon();
|
|
};
|
|
|
|
this.abandonAndWait = () => {
|
|
var abandoned = this.abandon();
|
|
this.waitDeferred.resolve();
|
|
return abandoned;
|
|
};
|
|
|
|
this.dwellAndPreviewDwell = ( title, el, ev, res ) => {
|
|
return this.dwellAndShowPreview( title, el, ev, res ).then( () => {
|
|
|
|
// Get out of the link, and before the delay ends...
|
|
var abandonPromise = this.abandon(),
|
|
abandonWaitDeferred = this.waitDeferred;
|
|
|
|
// Dwell over the preview
|
|
this.actions.previewDwell( el );
|
|
|
|
// Then the abandon delay finishes
|
|
abandonWaitDeferred.resolve();
|
|
|
|
return abandonPromise;
|
|
} );
|
|
};
|
|
}
|
|
} );
|
|
|
|
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(
|
|
this.title, this.el, 'event',
|
|
gateway,
|
|
constant( 'pagetoken' )
|
|
);
|
|
state = this.store.getState();
|
|
assert.equal( state.preview.activeLink, this.el, 'It has an active link' );
|
|
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,
|
|
el = this.el;
|
|
|
|
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' );
|
|
} );
|
|
} );
|
|
|
|
QUnit.test( 'in ACTIVE state, fetch fail switches it to DATA', function ( assert ) {
|
|
var store = this.store,
|
|
el = this.el;
|
|
|
|
return this.dwellAndShowPreview(
|
|
this.title, el, 'event', 42, FETCH_RESOLUTION.REJECT
|
|
).then( function () {
|
|
var state = store.getState();
|
|
assert.equal( state.preview.activeLink, el );
|
|
assert.equal( state.preview.shouldShow, true,
|
|
'Should show when data couldn\'t be fetched' );
|
|
} );
|
|
} );
|
|
|
|
QUnit.test( 'in ACTIVE state, abandon start, and then end, switch it to INACTIVE', function ( assert ) {
|
|
var el = this.el;
|
|
|
|
return this.dwellAndShowPreview( this.title, el, 'event', 42 )
|
|
.then( () => {
|
|
return this.abandonAndWait( el );
|
|
} ).then( () => {
|
|
var state = this.store.getState();
|
|
assert.equal( state.preview.activeLink, undefined,
|
|
'After abandoning, preview is back to INACTIVE' );
|
|
} );
|
|
} );
|
|
|
|
QUnit.test( 'in ACTIVE state, abandon link, and then dwell preview, should keep it active after all delays', function ( assert ) {
|
|
var el = this.el;
|
|
|
|
return this.dwellAndPreviewDwell( this.title, el, 'event', 42 )
|
|
.then( () => {
|
|
var state = this.store.getState();
|
|
assert.equal( state.preview.activeLink, el );
|
|
} );
|
|
} );
|
|
|
|
QUnit.test( 'in ACTIVE state, abandon link, hover preview, back to link, should keep it active after all delays', function ( assert ) {
|
|
var el = this.el;
|
|
|
|
// Dwell link, abandon it & hover preview
|
|
return this.dwellAndPreviewDwell( this.title, el, 'event', 42 )
|
|
.then( () => {
|
|
var abandonedPreview, abandonWaitDeferred, dwelled, dwellWaitDeferred;
|
|
|
|
// Start abandoning the preview
|
|
abandonedPreview = this.abandon();
|
|
abandonWaitDeferred = this.waitDeferred;
|
|
|
|
// Dwell back into the link, new event ('event2') is triggered
|
|
dwelled = this.dwell( this.title, el, 'event2', 42 );
|
|
dwellWaitDeferred = this.waitDeferred;
|
|
|
|
// Preview abandon happens next, before the fetch
|
|
abandonWaitDeferred.resolve();
|
|
|
|
// Then dwell wait & fetch happens
|
|
dwellWaitDeferred.resolve();
|
|
|
|
return $.when( abandonedPreview, dwelled );
|
|
} )
|
|
.then( () => {
|
|
var state = this.store.getState();
|
|
assert.equal( state.preview.activeLink, el );
|
|
} );
|
|
} );
|