qunit: Abort unfinished AJAX requests in test teardown
authorTimo Tijhof <krinklemail@gmail.com>
Thu, 16 Apr 2015 00:19:09 +0000 (01:19 +0100)
committerTimo Tijhof <krinklemail@gmail.com>
Thu, 16 Apr 2015 00:19:54 +0000 (01:19 +0100)
Similar to what we do with animations already, ensure we give
the next test a clean start by aborting any requests that were
made during the test that are still pending.

Also log the details of the request (ajax options, e.g. url) to
the console.

To test:
* Add "sleep( 1 );" to LocalSettings.php.
* add "$.ajax( mw.util.wikiScript() )" to a test in mediawiki.util.test.js.
* Run Special:JavaScriptTest/qunit/plain?module=mediawiki.util

Change-Id: Iefef89effc092d296baa9df68a86d95497730708

tests/qunit/data/testrunner.js

index 03aaf4a..3c44b55 100644 (file)
         * </code>
         */
        QUnit.newMwEnvironment = ( function () {
-               var warn, log, liveConfig, liveMessages;
+               var warn, log, liveConfig, liveMessages,
+                       ajaxRequests = [];
 
                liveConfig = mw.config.values;
                liveMessages = mw.messages.values;
                        return $.extend( /*deep=*/true, {}, liveMessages, custom );
                }
 
+               /**
+                * @param {jQuery.Event} event
+                * @param {jqXHR} jqXHR
+                * @param {Object} ajaxOptions
+                */
+               function trackAjax( event, jqXHR, ajaxOptions ) {
+                       ajaxRequests.push( { xhr: jqXHR, options: ajaxOptions } );
+               }
+
                log = QUnit.urlParams.mwlogenv ? mw.log : function () {};
 
                return function ( localEnv ) {
                                        this.suppressWarnings = suppressWarnings;
                                        this.restoreWarnings = restoreWarnings;
 
+                                       // Start tracking ajax requests
+                                       $( document ).on( 'ajaxSend', trackAjax );
+
                                        localEnv.setup.call( this );
                                },
 
                                teardown: function () {
-                                       var timers;
+                                       var timers, active;
                                        log( 'MwEnvironment> TEARDOWN for "' + QUnit.config.current.module
                                                + ': ' + QUnit.config.current.testName + '"' );
 
                                        localEnv.teardown.call( this );
 
+                                       // Stop tracking ajax requests
+                                       $( document ).off( 'ajaxSend', trackAjax );
+
                                        // Farewell, mock environment!
                                        mw.config.values = liveConfig;
                                        mw.messages.values = liveMessages;
                                        // still suppressed by the end of the test.
                                        restoreWarnings();
 
-                                       // Check for incomplete animations/requests/etc and throw
-                                       // error if there are any.
+                                       // Tests should use fake timers or wait for animations to complete
+                                       // Check for incomplete animations/requests/etc and throw if there are any.
                                        if ( $.timers && $.timers.length !== 0 ) {
                                                timers = $.timers.length;
-                                               // Tests shoulld use fake timers or wait for animations to complete
                                                $.each( $.timers, function ( i, timer ) {
                                                        var node = timer.elem;
                                                        mw.log.warn( 'Unfinished animation #' + i + ' in ' + timer.queue + ' queue on ' +
 
                                                throw new Error( 'Unfinished animations: ' + timers );
                                        }
+
+                                       // Test should use fake XHR, wait for requests, or call abort()
                                        if ( $.active !== undefined && $.active !== 0 ) {
-                                               // Test may need to use fake XHR, wait for requests or
-                                               // call abort().
-                                               throw new Error( 'Unfinished AJAX requests: ' + $.active );
+                                               active = $.grep( ajaxRequests, function ( ajax ) {
+                                                       return ajax.xhr.state() === 'pending';
+                                               } );
+                                               if ( active.length !== $.active ) {
+                                                       mw.log.warn( 'Pending requests does not match jQuery.active count' );
+                                               }
+                                               // Force requests to stop to give the next test a clean start
+                                               $.each( active, function ( i, ajax ) {
+                                                       mw.log.warn( 'Unfinished AJAX request #' + i, ajax.options );
+                                                       ajax.xhr.abort();
+                                               } );
+                                               ajaxRequests = [];
+
+                                               throw new Error( 'Unfinished AJAX requests: ' + active.length );
                                        }
                                }
                        };