mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Popups
synced 2024-12-01 02:36:35 +00:00
c64497e5df
The following upgrade was made: eslint-config-wikimedia | 0.8.1 | 0.9.0 The upgrade of eslint-config-wikimedia removes the need of eslint-plugin-qunit as a peerDependency because it is now a hard dependency [1] so it was removed in our package.json. It appears the biggest change in the upgrade was the use of separate profiles [2]. Given this, our root .eslintrc.json was updated to extend from 'wikimedia/client'. Several test files were flagged by the upgraded linter and were fixed in this patch. Additionally, our build file was flagged for having too many statements on one line. This rule was turned off in .eslintrc.es5.json [1] https://github.com/wikimedia/eslint-config-wikimedia/blob/master/package.json#L48 [2] https://github.com/wikimedia/eslint-config-wikimedia/blob/master/CHANGELOG.md#090--2018-11-19 Bug: T209314 Change-Id: I29db72e77f04a327bc9c2b558c6d53849287bb80
278 lines
7.6 KiB
JavaScript
278 lines
7.6 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';
|
|
|
|
const 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 () => 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() {
|
|
// The worst-case implementation of mw.now.
|
|
mw.now = () => 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 */
|
|
stubs.createStubUser( true ),
|
|
/* userSettings: */
|
|
{ getPreviewCount: constant( 1 ) },
|
|
/* config: */
|
|
{ get: identity }
|
|
);
|
|
|
|
this.dwell = (
|
|
title, el, ev, fetchResponse, resolution = FETCH_RESOLUTION.RESOLVE
|
|
) => {
|
|
this.resetWait();
|
|
return this.actions.linkDwell( title, el, ev, {
|
|
getPageSummary() {
|
|
const method = resolution === FETCH_RESOLUTION.RESOLVE ?
|
|
'resolve' : 'reject';
|
|
const abort = {
|
|
abort() {}
|
|
};
|
|
return $.Deferred()[ method ]( fetchResponse ).promise( abort );
|
|
}
|
|
}, () => 'pagetoken' );
|
|
};
|
|
|
|
this.dwellAndShowPreview = (
|
|
title, el, ev, fetchResponse, reject = FETCH_RESOLUTION.RESOLVE
|
|
) => {
|
|
const 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 = () => {
|
|
const 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...
|
|
const 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 ) {
|
|
const state = this.store.getState();
|
|
|
|
assert.strictEqual(
|
|
state.preview.activeLink,
|
|
undefined,
|
|
'The initial active link is undefined.'
|
|
);
|
|
assert.strictEqual(
|
|
state.preview.linkInteractionToken,
|
|
undefined,
|
|
'The initial token is undefined.'
|
|
);
|
|
} );
|
|
|
|
QUnit.test( 'in INACTIVE state, a link dwell switches it to ACTIVE', function ( assert ) {
|
|
const gateway = {
|
|
getPageSummary() {
|
|
$.Deferred().promise();
|
|
}
|
|
};
|
|
|
|
this.actions.linkDwell(
|
|
this.title, this.el, 'event',
|
|
gateway,
|
|
constant( 'pagetoken' )
|
|
);
|
|
const state = this.store.getState();
|
|
assert.strictEqual( state.preview.activeLink, this.el, 'It has an active link' );
|
|
assert.strictEqual( state.preview.shouldShow, false, 'Initializes with NO_DATA' );
|
|
} );
|
|
|
|
QUnit.test( 'in ACTIVE state, fetch end switches it to DATA', function ( assert ) {
|
|
const store = this.store,
|
|
el = this.el;
|
|
|
|
return this.dwellAndShowPreview( this.title, el, 'event', 42 )
|
|
.then( () => {
|
|
const state = store.getState();
|
|
assert.strictEqual(
|
|
state.preview.activeLink,
|
|
el,
|
|
'The active link is passed.'
|
|
);
|
|
assert.strictEqual(
|
|
state.preview.shouldShow, true,
|
|
'Should show when data has been fetched' );
|
|
} );
|
|
} );
|
|
|
|
QUnit.test( 'in ACTIVE state, fetch fail switches it to DATA', function ( assert ) {
|
|
const store = this.store,
|
|
el = this.el;
|
|
|
|
return this.dwellAndShowPreview(
|
|
this.title, el, 'event', 42, FETCH_RESOLUTION.REJECT
|
|
).then( () => {
|
|
const state = store.getState();
|
|
assert.strictEqual( state.preview.activeLink, el, 'The active link is passed.' );
|
|
assert.strictEqual( 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 ) {
|
|
const el = this.el;
|
|
|
|
return this.dwellAndShowPreview( this.title, el, 'event', 42 )
|
|
.then( () => {
|
|
return this.abandonAndWait( el );
|
|
} ).then( () => {
|
|
const state = this.store.getState();
|
|
assert.strictEqual( 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 ) {
|
|
const el = this.el;
|
|
|
|
return this.dwellAndPreviewDwell( this.title, el, 'event', 42 )
|
|
.then( () => {
|
|
const state = this.store.getState();
|
|
assert.strictEqual(
|
|
state.preview.activeLink,
|
|
el,
|
|
'The active link is passed.'
|
|
);
|
|
} );
|
|
} );
|
|
|
|
QUnit.test( 'in ACTIVE state, abandon link, hover preview, back to link, should keep it active after all delays', function ( assert ) {
|
|
const el = this.el;
|
|
|
|
// Dwell link, abandon it & hover preview
|
|
return this.dwellAndPreviewDwell( this.title, el, 'event', 42 )
|
|
.then( () => {
|
|
// Start abandoning the preview
|
|
const abandonedPreview = this.abandon();
|
|
const abandonWaitDeferred = this.waitDeferred;
|
|
|
|
// Dwell back into the link, new event ('event2') is triggered
|
|
const dwelled = this.dwell( this.title, el, 'event2', 42 );
|
|
const dwellWaitDeferred = this.waitDeferred;
|
|
|
|
// Preview abandon happens next, before the fetch
|
|
abandonWaitDeferred.resolve();
|
|
|
|
// Then dwell wait & fetch happens
|
|
dwellWaitDeferred.resolve();
|
|
|
|
return $.when( abandonedPreview, dwelled );
|
|
} )
|
|
.then( () => {
|
|
const state = this.store.getState();
|
|
assert.strictEqual(
|
|
state.preview.activeLink,
|
|
el,
|
|
'The active link is passed.'
|
|
);
|
|
} );
|
|
} );
|