jenkins-bot has submitted this change and it was merged.

Change subject: Make API request after 500 ms
......................................................................


Make API request after 500 ms

If the user has abandoned the link or dwelled on a different link, then
don't make the API request.

Changes:
* Fix a bug in the linkDwell reducer where the activeLink was always
  being set to undefined.
* Add the private fetch action creator.
* Make the linkDwell action dispatch the result of the fetch action
  creator after 500ms.

Supporting changes:
* Make the link* action creators take DOM elements rather than jQuery
  instances so that:
  1. Testing whether the state tree has changed is an identity
     comparison.
  2. jQuery instances are constructed only when required.
* Document the ext.popups.Gateway type.
* Document the Redux.Store type.
* Rename the "previews-page-title" data attribute to
  "page-previews-title" to avoid confusion.

Change-Id: I0b1cf3337a6f8d6450ad2bd127cb292ebb73af4f
---
M resources/ext.popups/actions.js
M resources/ext.popups/boot.js
M resources/ext.popups/changeListener.js
M resources/ext.popups/gateway.js
M resources/ext.popups/processLinks.js
M resources/ext.popups/reducers.js
M tests/qunit/ext.popups/actions.test.js
M tests/qunit/ext.popups/processLinks.test.js
8 files changed, 176 insertions(+), 32 deletions(-)

Approvals:
  jenkins-bot: Verified
  Phuedx: Looks good to me, approved



diff --git a/resources/ext.popups/actions.js b/resources/ext.popups/actions.js
index 4aea7a2..80a3587 100644
--- a/resources/ext.popups/actions.js
+++ b/resources/ext.popups/actions.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function ( mw, $ ) {
 
        var actions = {},
                types = {
@@ -15,7 +15,8 @@
                        COG_CLICK: 'COG_CLICK',
                        SETTINGS_DIALOG_RENDERED: 'SETTINGS_DIALOG_RENDERED',
                        SETTINGS_DIALOG_CLOSED: 'SETTINGS_DIALOG_CLOSED'
-               };
+               },
+               FETCH_START_DELAY = 500; // ms.
 
        /**
         * Represents Link Previews booting.
@@ -44,16 +45,54 @@
        };
 
        /**
+        * Represents Page Previews fetching data via the 
[gateway](./gateway.js).
+        *
+        * @param {ext.popups.Gateway} gateway
+        * @param {String} title
+        * @return {Redux.Thunk}
+        */
+       function fetch( gateway, title ) {
+               return function ( dispatch ) {
+                       dispatch( {
+                               type: types.FETCH_START,
+                               title: title
+                       } );
+
+                       gateway( title )
+                               .fail( function () {
+                                       dispatch( {
+                                               type: types.FETCH_FAILED
+                                       } );
+                               } )
+                               .done( function ( result ) {
+                                       dispatch( {
+                                               type: types.FETCH_END,
+                                               result: result
+                                       } );
+                               } );
+               };
+       }
+
+       /**
         * Represents the user dwelling on a link, either by hovering over it 
with
         * their mouse or by focussing it using their keyboard or an assistive 
device.
         *
-        * @param {jQuery} $el
-        * @return {Object}
+        * @param {Element} el
+        * @param {Function} gateway
+        * @return {Redux.Thunk}
         */
-       actions.linkDwell = function ( $el ) {
-               return {
-                       type: types.LINK_DWELL,
-                       el: $el
+       actions.linkDwell = function ( el, gateway ) {
+               return function ( dispatch, getState ) {
+                       dispatch( {
+                               type: types.LINK_DWELL,
+                               el: el
+                       } );
+
+                       setTimeout( function () {
+                               if ( getState().preview.activeLink === el ) {
+                                       dispatch( fetch( gateway, $( el ).data( 
'page-previews-title' ) ) );
+                               }
+                       }, FETCH_START_DELAY );
                };
        };
 
@@ -62,13 +101,13 @@
         * from it or by shifting focus to another UI element using their 
keyboard or
         * an assistive device.
         *
-        * @param {jQuery} $el
+        * @param {Element} el
         * @return {Object}
         */
-       actions.linkAbandon = function ( $el ) {
+       actions.linkAbandon = function ( el ) {
                return {
                        type: types.LINK_ABANDON,
-                       el: $el
+                       el: el
                };
        };
 
@@ -76,13 +115,13 @@
         * Represents the user clicking on a link with their mouse, keyboard, 
or an
         * assistive device.
         *
-        * @param {jQuery} $el
+        * @param {Element} el
         * @return {Object}
         */
-       actions.linkClick = function ( $el ) {
+       actions.linkClick = function ( el ) {
                return {
                        type: 'LINK_CLICK',
-                       el: $el
+                       el: el
                };
        };
 
@@ -101,4 +140,4 @@
        mw.popups.actions = actions;
        mw.popups.actionTypes = types;
 
-}( mediaWiki ) );
+}( mediaWiki, jQuery ) );
diff --git a/resources/ext.popups/boot.js b/resources/ext.popups/boot.js
index 4a184ef..db54fdc 100644
--- a/resources/ext.popups/boot.js
+++ b/resources/ext.popups/boot.js
@@ -10,9 +10,11 @@
        ];
 
        /**
-        * Return whether the user is in the experiment group
+        * Creates an experiment with sensible values for the depenencies.
         *
-        * @return {Boolean}
+        * See `mw.popups.createExperiment`.
+        *
+        * @return {Function}
         */
        function isUserInCondition() {
                var userSettings = mw.popups.createUserSettings( mw.storage, 
mw.user );
@@ -25,13 +27,24 @@
        }
 
        /**
+        * Creates a gateway with sensible values for the dependencies.
+        *
+        * See `mw.popups.createGateway`.
+        *
+        * @return {ext.popups.Gateway}
+        */
+       function createGateway() {
+               return mw.popups.createGateway( new mw.Api() );
+       }
+
+       /**
         * Subscribes the registered change listeners to the
         * [store](http://redux.js.org/docs/api/Store.html#store).
         *
         * Change listeners are registered by setting a property on
         * `mw.popups.changeListeners`.
         *
-        * @param {Store} store
+        * @param {Redux.Store} store
         */
        function registerChangeListeners( store, actions ) {
                $.each( mw.popups.changeListeners, function ( _, 
changeListenerFactory ) {
@@ -45,7 +58,7 @@
         * Binds the actions (or "action creators") to the
         * [store](http://redux.js.org/docs/api/Store.html#store).
         *
-        * @param {Store} store
+        * @param {Redux.Store} store
         * @return {Object}
         */
        function createBoundActions( store ) {
@@ -67,7 +80,8 @@
                var compose = Redux.compose,
                        store,
                        actions,
-                       generateToken = mw.user.generateRandomSessionId;
+                       generateToken = mw.user.generateRandomSessionId,
+                       gateway = createGateway();
 
                // If debug mode is enabled, then enable Redux DevTools.
                if ( mw.config.get( 'debug' ) === true ) {
@@ -98,7 +112,7 @@
 
                        previewLinks
                                .on( 'mouseover focus', function () {
-                                       actions.linkDwell( this );
+                                       actions.linkDwell( this, gateway );
                                } )
                                .on( 'mouseout blur', function () {
                                        actions.linkAbandon( this );
diff --git a/resources/ext.popups/changeListener.js 
b/resources/ext.popups/changeListener.js
index 75ee53c..4077b3b 100644
--- a/resources/ext.popups/changeListener.js
+++ b/resources/ext.popups/changeListener.js
@@ -18,7 +18,7 @@
         * See 
[Store#subscribe](http://redux.js.org/docs/api/Store.html#subscribe)
         * for more information about what change listeners may and may not do.
         *
-        * @param {Store} store
+        * @param {Redux.Store} store
         * @param {ext.popups.ChangeListener} callback
         */
        mw.popups.registerChangeListener = function ( store, callback ) {
diff --git a/resources/ext.popups/gateway.js b/resources/ext.popups/gateway.js
index 7db25f1..c58cd84 100644
--- a/resources/ext.popups/gateway.js
+++ b/resources/ext.popups/gateway.js
@@ -7,6 +7,11 @@
                CACHE_LIFETIME = 300; // Public and private cache lifetime (5 
minutes)
 
        /**
+        * @typedef {Function} ext.popups.Gateway
+        * @param {String} title
+        */
+
+       /**
         * Creates a function that fetches all of the data required to give the 
user a
         * preview of the page from the MediaWiki API given the title of the 
page (see
         * `mw.popups.processLinks` for the definition of "title").
@@ -15,7 +20,7 @@
         * will reject; otherwise, it'll resolve.
         *
         * @param {mw.Api} api
-        * @return {Function}
+        * @return {ext.popups.Gateway}
         */
        mw.popups.createGateway = function ( api ) {
                return function ( title ) {
@@ -46,7 +51,6 @@
                                }
                        } )
                                .then( function ( data ) {
-
                                        // If the response is empty, i.e. 
data.query.pages is empty or isn't
                                        // set, then reject rather than resolve.
                                        if (
diff --git a/resources/ext.popups/processLinks.js 
b/resources/ext.popups/processLinks.js
index 7d992bc..0f46464 100644
--- a/resources/ext.popups/processLinks.js
+++ b/resources/ext.popups/processLinks.js
@@ -83,7 +83,7 @@
                                // Is titleText in a content namespace?
                                title = mw.Title.newFromText( titleText );
                                if ( title && ( $.inArray( title.namespace, 
contentNamespaces ) >= 0 ) ) {
-                                       $( this ).data( 'previews-page-title', 
titleText );
+                                       $( this ).data( 'page-previews-title', 
titleText );
 
                                        return true;
                                }
diff --git a/resources/ext.popups/reducers.js b/resources/ext.popups/reducers.js
index 568cbf6..3a840b2 100644
--- a/resources/ext.popups/reducers.js
+++ b/resources/ext.popups/reducers.js
@@ -39,14 +39,12 @@
                                } );
                        case mw.popups.actionTypes.LINK_DWELL:
                                return $.extend( OO.copy( state ), {
-                                       activeLink: action.activeLink,
+                                       activeLink: action.el,
                                        interactionStarted: 
action.interactionStarted,
                                        isDelayingFetch: true,
                                        linkInteractionToken: 
action.linkInteractionToken
                                } );
                        case mw.popups.actionTypes.LINK_ABANDON:
-                               // Should the action handle dispatching a 
FETCH_FAILED
-                               // if a new fetch has begun? Or should the 
reducer?
                                return $.extend( OO.copy( state ), {
                                        previousActiveLink: state.activeLink,
                                        activeLink: undefined,
diff --git a/tests/qunit/ext.popups/actions.test.js 
b/tests/qunit/ext.popups/actions.test.js
index 0110bf5..e46b92d 100644
--- a/tests/qunit/ext.popups/actions.test.js
+++ b/tests/qunit/ext.popups/actions.test.js
@@ -1,8 +1,8 @@
-( function ( mw ) {
+( function ( mw, $ ) {
 
        QUnit.module( 'ext.popups/actions' );
 
-       QUnit.test( '#boot', 1, function ( assert ) {
+       QUnit.test( '#boot', function ( assert ) {
                var isUserInCondition = function () {
                                return false;
                        },
@@ -10,6 +10,8 @@
                        generateToken = function () {
                                return '9876543210';
                        };
+
+               assert.expect( 1 );
 
                assert.deepEqual(
                        mw.popups.actions.boot( isUserInCondition, sessionID, 
generateToken ),
@@ -22,4 +24,91 @@
                );
        } );
 
-}( mediaWiki ) );
+       QUnit.module( 'ext.popups/actions#linkDwell @integration', {
+               setup: function () {
+                       var that = this;
+
+                       this.el = $( '<a>' )
+                               .data( 'page-previews-title', 'Foo' )
+                               .eq( 0 );
+
+                       that.state = {};
+                       that.getState = function () {
+                               return that.state;
+                       };
+               }
+       } );
+
+       QUnit.test( '#linkDwell', function ( assert ) {
+               var done,
+                       dispatch = this.sandbox.spy(),
+                       gatewayDeferred = $.Deferred(),
+                       gateway = function () {
+                               return gatewayDeferred;
+                       };
+
+               done = assert.async();
+
+               mw.popups.actions.linkDwell( this.el, gateway )( dispatch, 
this.getState );
+
+               assert.ok( dispatch.calledWith( {
+                       type: 'LINK_DWELL',
+                       el: this.el
+               } ) );
+
+               // Stub the state tree being updated.
+               this.state.preview = {
+                       activeLink: this.el
+               };
+
+               // ---
+
+               setTimeout( function () {
+                       var fetchThunk,
+                               result = {};
+
+                       assert.strictEqual(
+                               dispatch.callCount,
+                               2,
+                               'The fetch action is dispatched after 500 ms'
+                       );
+
+                       fetchThunk = dispatch.secondCall.args[0];
+                       fetchThunk( dispatch );
+
+                       assert.ok( dispatch.calledWith( {
+                               type: 'FETCH_START',
+                               title: 'Foo'
+                       } ) );
+
+                       gatewayDeferred.resolve( result );
+
+                       assert.ok( dispatch.calledWith( {
+                               type: 'FETCH_END',
+                               result: result
+                       } ) );
+
+                       done();
+               }, 500 );
+       } );
+
+       QUnit.test( '#linkDwell doesn\'t dispatch the fetch action if the 
active link has changed', function ( assert ) {
+               var done,
+                       dispatch = this.sandbox.spy();
+
+               done = assert.async();
+
+               mw.popups.actions.linkDwell( this.el /*, gateway */ )( 
dispatch, this.getState );
+
+               this.state.preview = {
+                       activeLink: undefined // Any value other than this.el.
+               };
+
+               setTimeout( function () {
+                       assert.strictEqual( dispatch.callCount, 1 );
+
+                       done();
+               }, 500 );
+       } );
+
+}( mediaWiki, jQuery ) );
diff --git a/tests/qunit/ext.popups/processLinks.test.js 
b/tests/qunit/ext.popups/processLinks.test.js
index 8059ed6..8f14c53 100644
--- a/tests/qunit/ext.popups/processLinks.test.js
+++ b/tests/qunit/ext.popups/processLinks.test.js
@@ -124,7 +124,7 @@
 
                $.each( cases, function ( i, testCase ) {
                        assert.strictEqual(
-                               $processedLinks.eq( i ).data( 
'previews-page-title' ),
+                               $processedLinks.eq( i ).data( 
'page-previews-title' ),
                                testCase[1]
                        );
                } );

-- 
To view, visit https://gerrit.wikimedia.org/r/321922
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I0b1cf3337a6f8d6450ad2bd127cb292ebb73af4f
Gerrit-PatchSet: 3
Gerrit-Project: mediawiki/extensions/Popups
Gerrit-Branch: mpga
Gerrit-Owner: Phuedx <[email protected]>
Gerrit-Reviewer: Phuedx <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to