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

Change subject: Fix hash self-reaction
......................................................................


Fix hash self-reaction

* Fixes bug where the mmv would react to its own hash changes
* Adds jquery.hashchange for older browser support

Change-Id: I2d23699bb444d9eb533d239e25bc47fa96c902a9
Mingle: https://wikimedia.mingle.thoughtworks.com/projects/multimedia/cards/254
---
M .jshintignore
M MultimediaViewer.php
A resources/jquery.hashchange/jquery.hashchange.js
M resources/mmv/mmv.bootstrap.js
M resources/mmv/mmv.js
M resources/mmv/mmv.lightboxinterface.js
M tests/qunit/mmv.bootstrap.test.js
M tests/qunit/mmv.lightboxinterface.test.js
M tests/qunit/mmv.test.js
9 files changed, 474 insertions(+), 32 deletions(-)

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



diff --git a/.jshintignore b/.jshintignore
index bbac23b..bc13f20 100644
--- a/.jshintignore
+++ b/.jshintignore
@@ -1,3 +1,3 @@
 resources/jquery.scrollTo
-resources/jquery.throttle.debounce
+resources/jquery.hashchange
 docs/js/*
diff --git a/MultimediaViewer.php b/MultimediaViewer.php
index 29abb0e..b47866e 100644
--- a/MultimediaViewer.php
+++ b/MultimediaViewer.php
@@ -452,6 +452,7 @@
                        'mediawiki.Title',
                        'mmv.logger',
                        'mmv.ui',
+                       'jquery.hashchange',
                ),
        ), $moduleInfo( 'mmv' ) );
 
@@ -461,6 +462,12 @@
                ),
        ), $moduleInfo( 'jquery.scrollTo' ) );
 
+       $wgResourceModules['jquery.hashchange'] = array_merge( array(
+               'scripts' => array(
+                       'jquery.hashchange.js',
+               ),
+       ), $moduleInfo( 'jquery.hashchange' ) );
+
        $wgExtensionFunctions[] = function () {
                global $wgResourceModules;
 
diff --git a/resources/jquery.hashchange/jquery.hashchange.js 
b/resources/jquery.hashchange/jquery.hashchange.js
new file mode 100644
index 0000000..1f0592b
--- /dev/null
+++ b/resources/jquery.hashchange/jquery.hashchange.js
@@ -0,0 +1,390 @@
+/*!
+ * jQuery hashchange event - v1.3 - 7/21/2010
+ * http://benalman.com/projects/jquery-hashchange-plugin/
+ *
+ * Copyright (c) 2010 "Cowboy" Ben Alman
+ * Dual licensed under the MIT and GPL licenses.
+ * http://benalman.com/about/license/
+ */
+
+// Script: jQuery hashchange event
+//
+// *Version: 1.3, Last updated: 7/21/2010*
+//
+// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/
+// GitHub       - http://github.com/cowboy/jquery-hashchange/
+// Source       - 
http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js
+// (Minified)   - 
http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js
 (0.8kb gzipped)
+//
+// About: License
+//
+// Copyright (c) 2010 "Cowboy" Ben Alman,
+// Dual licensed under the MIT and GPL licenses.
+// http://benalman.com/about/license/
+//
+// About: Examples
+//
+// These working examples, complete with fully commented code, illustrate a few
+// ways in which this plugin can be used.
+//
+// hashchange event - 
http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/
+// document.domain - 
http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/
+//
+// About: Support and Testing
+//
+// Information about what version or versions of jQuery this plugin has been
+// tested with, what browsers it has been tested in, and where the unit tests
+// reside (so you can test it yourself).
+//
+// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2
+// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 
3.2-5,
+//                   Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 
4.6-5.
+// Unit Tests      - http://benalman.com/code/projects/jquery-hashchange/unit/
+//
+// About: Known issues
+//
+// While this jQuery hashchange event implementation is quite stable and
+// robust, there are a few unfortunate browser bugs surrounding expected
+// hashchange event-based behaviors, independent of any JavaScript
+// window.onhashchange abstraction. See the following examples for more
+// information:
+//
+// Chrome: Back Button - 
http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/
+// Firefox: Remote XMLHttpRequest - 
http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/
+// WebKit: Back Button in an Iframe - 
http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/
+// Safari: Back Button from a different domain - 
http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/
+//
+// Also note that should a browser natively support the window.onhashchange
+// event, but not report that it does, the fallback polling loop will be used.
+//
+// About: Release History
+//
+// 1.3   - (7/21/2010) Reorganized IE6/7 Iframe code to make it more
+//         "removable" for mobile-only development. Added IE6/7 document.title
+//         support. Attempted to make Iframe as hidden as possible by using
+//         techniques from http://www.paciellogroup.com/blog/?p=604. Added
+//         support for the "shortcut" format $(window).hashchange( fn ) and
+//         $(window).hashchange() like jQuery provides for built-in events.
+//         Renamed jQuery.hashchangeDelay to <jQuery.fn.hashchange.delay> and
+//         lowered its default value to 50. Added <jQuery.fn.hashchange.domain>
+//         and <jQuery.fn.hashchange.src> properties plus document-domain.html
+//         file to address access denied issues when setting document.domain in
+//         IE6/7.
+// 1.2   - (2/11/2010) Fixed a bug where coming back to a page using this 
plugin
+//         from a page on another domain would cause an error in Safari 4. 
Also,
+//         IE6/7 Iframe is now inserted after the body (this actually works),
+//         which prevents the page from scrolling when the event is first 
bound.
+//         Event can also now be bound before DOM ready, but it won't be usable
+//         before then in IE6/7.
+// 1.1   - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug
+//         where browser version is incorrectly reported as 8.0, despite
+//         inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag.
+// 1.0   - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special
+//         window.onhashchange functionality into a separate plugin for users
+//         who want just the basic event & back button support, without all the
+//         extra awesomeness that BBQ provides. This plugin will be included as
+//         part of jQuery BBQ, but also be available separately.
+
+(function($,window,undefined){
+  '$:nomunge'; // Used by YUI compressor.
+
+  // Reused string.
+  var str_hashchange = 'hashchange',
+
+    // Method / object references.
+    doc = document,
+    fake_onhashchange,
+    special = $.event.special,
+
+    // Does the browser support window.onhashchange? Note that IE8 running in
+    // IE7 compatibility mode reports true for 'onhashchange' in window, even
+    // though the event isn't supported, so also test document.documentMode.
+    doc_mode = doc.documentMode,
+    supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === 
undefined || doc_mode > 7 );
+
+  // Get location.hash (or what you'd expect location.hash to be) sans any
+  // leading #. Thanks for making this necessary, Firefox!
+  function get_fragment( url ) {
+    url = url || location.href;
+    return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' );
+  };
+
+  // Method: jQuery.fn.hashchange
+  //
+  // Bind a handler to the window.onhashchange event or trigger all bound
+  // window.onhashchange event handlers. This behavior is consistent with
+  // jQuery's built-in event handlers.
+  //
+  // Usage:
+  //
+  // > jQuery(window).hashchange( [ handler ] );
+  //
+  // Arguments:
+  //
+  //  handler - (Function) Optional handler to be bound to the hashchange
+  //    event. This is a "shortcut" for the more verbose form:
+  //    jQuery(window).bind( 'hashchange', handler ). If handler is omitted,
+  //    all bound window.onhashchange event handlers will be triggered. This
+  //    is a shortcut for the more verbose
+  //    jQuery(window).trigger( 'hashchange' ). These forms are described in
+  //    the <hashchange event> section.
+  //
+  // Returns:
+  //
+  //  (jQuery) The initial jQuery collection of elements.
+
+  // Allow the "shortcut" format $(elem).hashchange( fn ) for binding and
+  // $(elem).hashchange() for triggering, like jQuery does for built-in events.
+  $.fn[ str_hashchange ] = function( fn ) {
+    return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange 
);
+  };
+
+  // Property: jQuery.fn.hashchange.delay
+  //
+  // The numeric interval (in milliseconds) at which the <hashchange event>
+  // polling loop executes. Defaults to 50.
+
+  // Property: jQuery.fn.hashchange.domain
+  //
+  // If you're setting document.domain in your JavaScript, and you want hash
+  // history to work in IE6/7, not only must this property be set, but you must
+  // also set document.domain BEFORE jQuery is loaded into the page. This
+  // property is only applicable if you are supporting IE6/7 (or IE8 operating
+  // in "IE7 compatibility" mode).
+  //
+  // In addition, the <jQuery.fn.hashchange.src> property must be set to the
+  // path of the included "document-domain.html" file, which can be renamed or
+  // modified if necessary (note that the document.domain specified must be the
+  // same in both your main JavaScript as well as in this file).
+  //
+  // Usage:
+  //
+  // jQuery.fn.hashchange.domain = document.domain;
+
+  // Property: jQuery.fn.hashchange.src
+  //
+  // If, for some reason, you need to specify an Iframe src file (for example,
+  // when setting document.domain as in <jQuery.fn.hashchange.domain>), you can
+  // do so using this property. Note that when using this property, history
+  // won't be recorded in IE6/7 until the Iframe src file loads. This property
+  // is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7
+  // compatibility" mode).
+  //
+  // Usage:
+  //
+  // jQuery.fn.hashchange.src = 'path/to/file.html';
+
+  $.fn[ str_hashchange ].delay = 50;
+  /*
+  $.fn[ str_hashchange ].domain = null;
+  $.fn[ str_hashchange ].src = null;
+  */
+
+  // Event: hashchange event
+  //
+  // Fired when location.hash changes. In browsers that support it, the native
+  // HTML5 window.onhashchange event is used, otherwise a polling loop is
+  // initialized, running every <jQuery.fn.hashchange.delay> milliseconds to
+  // see if the hash has changed. In IE6/7 (and IE8 operating in "IE7
+  // compatibility" mode), a hidden Iframe is created to allow the back button
+  // and hash-based history to work.
+  //
+  // Usage as described in <jQuery.fn.hashchange>:
+  //
+  // > // Bind an event handler.
+  // > jQuery(window).hashchange( function(e) {
+  // >   var hash = location.hash;
+  // >   ...
+  // > });
+  // >
+  // > // Manually trigger the event handler.
+  // > jQuery(window).hashchange();
+  //
+  // A more verbose usage that allows for event namespacing:
+  //
+  // > // Bind an event handler.
+  // > jQuery(window).bind( 'hashchange', function(e) {
+  // >   var hash = location.hash;
+  // >   ...
+  // > });
+  // >
+  // > // Manually trigger the event handler.
+  // > jQuery(window).trigger( 'hashchange' );
+  //
+  // Additional Notes:
+  //
+  // * The polling loop and Iframe are not created until at least one handler
+  //   is actually bound to the 'hashchange' event.
+  // * If you need the bound handler(s) to execute immediately, in cases where
+  //   a location.hash exists on page load, via bookmark or page refresh for
+  //   example, use jQuery(window).hashchange() or the more verbose
+  //   jQuery(window).trigger( 'hashchange' ).
+  // * The event can be bound before DOM ready, but since it won't be usable
+  //   before then in IE6/7 (due to the necessary Iframe), recommended usage is
+  //   to bind it inside a DOM ready handler.
+
+  // Override existing $.event.special.hashchange methods (allowing this plugin
+  // to be defined after jQuery BBQ in BBQ's source code).
+  special[ str_hashchange ] = $.extend( special[ str_hashchange ], {
+
+    // Called only when the first 'hashchange' event is bound to window.
+    setup: function() {
+      // If window.onhashchange is supported natively, there's nothing to do..
+      if ( supports_onhashchange ) { return false; }
+
+      // Otherwise, we need to create our own. And we don't want to call this
+      // until the user binds to the event, just in case they never do, since 
it
+      // will create a polling loop and possibly even a hidden Iframe.
+      $( fake_onhashchange.start );
+    },
+
+    // Called only when the last 'hashchange' event is unbound from window.
+    teardown: function() {
+      // If window.onhashchange is supported natively, there's nothing to do..
+      if ( supports_onhashchange ) { return false; }
+
+      // Otherwise, we need to stop ours (if possible).
+      $( fake_onhashchange.stop );
+    }
+
+  });
+
+  // fake_onhashchange does all the work of triggering the window.onhashchange
+  // event for browsers that don't natively support it, including creating a
+  // polling loop to watch for hash changes and in IE 6/7 creating a hidden
+  // Iframe to enable back and forward.
+  fake_onhashchange = (function(){
+    var self = {},
+      timeout_id,
+
+      // Remember the initial hash so it doesn't get triggered immediately.
+      last_hash = get_fragment(),
+
+      fn_retval = function(val){ return val; },
+      history_set = fn_retval,
+      history_get = fn_retval;
+
+    // Start the polling loop.
+    self.start = function() {
+      timeout_id || poll();
+    };
+
+    // Stop the polling loop.
+    self.stop = function() {
+      timeout_id && clearTimeout( timeout_id );
+      timeout_id = undefined;
+    };
+
+    // This polling loop checks every $.fn.hashchange.delay milliseconds to see
+    // if location.hash has changed, and triggers the 'hashchange' event on
+    // window when necessary.
+    function poll() {
+      var hash = get_fragment(),
+        history_hash = history_get( last_hash );
+
+      if ( hash !== last_hash ) {
+        history_set( last_hash = hash, history_hash );
+
+        $(window).trigger( str_hashchange );
+
+      } else if ( history_hash !== last_hash ) {
+        location.href = location.href.replace( /#.*/, '' ) + history_hash;
+      }
+
+      timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay );
+    };
+
+    // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+    // vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv
+    // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+    $.browser.msie && !supports_onhashchange && (function(){
+      // Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8
+      // when running in "IE7 compatibility" mode.
+
+      var iframe,
+        iframe_src;
+
+      // When the event is bound and polling starts in IE 6/7, create a hidden
+      // Iframe for history handling.
+      self.start = function(){
+        if ( !iframe ) {
+          iframe_src = $.fn[ str_hashchange ].src;
+          iframe_src = iframe_src && iframe_src + get_fragment();
+
+          // Create hidden Iframe. Attempt to make Iframe as hidden as possible
+          // by using techniques from http://www.paciellogroup.com/blog/?p=604.
+          iframe = $('<iframe tabindex="-1" title="empty"/>').hide()
+
+            // When Iframe has completely loaded, initialize the history and
+            // start polling.
+            .one( 'load', function(){
+              iframe_src || history_set( get_fragment() );
+              poll();
+            })
+
+            // Load Iframe src if specified, otherwise nothing.
+            .attr( 'src', iframe_src || 'javascript:0' )
+
+            // Append Iframe after the end of the body to prevent unnecessary
+            // initial page scrolling (yes, this works).
+            .insertAfter( 'body' )[0].contentWindow;
+
+          // Whenever `document.title` changes, update the Iframe's title to
+          // prettify the back/next history menu entries. Since IE sometimes
+          // errors with "Unspecified error" the very first time this is set
+          // (yes, very useful) wrap this with a try/catch block.
+          doc.onpropertychange = function(){
+            try {
+              if ( event.propertyName === 'title' ) {
+                iframe.document.title = doc.title;
+              }
+            } catch(e) {}
+          };
+
+        }
+      };
+
+      // Override the "stop" method since an IE6/7 Iframe was created. Even
+      // if there are no longer any bound event handlers, the polling loop
+      // is still necessary for back/next to work at all!
+      self.stop = fn_retval;
+
+      // Get history by looking at the hidden Iframe's location.hash.
+      history_get = function() {
+        return get_fragment( iframe.location.href );
+      };
+
+      // Set a new history item by opening and then closing the Iframe
+      // document, *then* setting its location.hash. If document.domain has
+      // been set, update that as well.
+      history_set = function( hash, history_hash ) {
+        var iframe_doc = iframe.document,
+          domain = $.fn[ str_hashchange ].domain;
+
+        if ( hash !== history_hash ) {
+          // Update Iframe with any initial `document.title` that might be set.
+          iframe_doc.title = doc.title;
+
+          // Opening the Iframe's document after it has been closed is what
+          // actually adds a history entry.
+          iframe_doc.open();
+
+          // Set document.domain for the Iframe document as well, if necessary.
+          domain && iframe_doc.write( '<script>document.domain="' + domain + 
'"</script>' );
+
+          iframe_doc.close();
+
+          // Update the Iframe's hash, for great justice.
+          iframe.location.hash = hash;
+        }
+      };
+
+    })();
+    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+    // ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^
+    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+    return self;
+  })();
+
+})(jQuery,this);
diff --git a/resources/mmv/mmv.bootstrap.js b/resources/mmv/mmv.bootstrap.js
index cad5a87..187bac6 100755
--- a/resources/mmv/mmv.bootstrap.js
+++ b/resources/mmv/mmv.bootstrap.js
@@ -39,10 +39,13 @@
                this.thumbs = [];
                this.$thumbs = $( '.gallery .image img, a.image img' );
                this.processThumbs();
-               this.hash();
 
-               $( window ).on( 'popstate.mmvb', function () {
+               $( window ).hashchange( function () {
                        self.hash();
+               } ).hashchange();
+
+               $( document ).on( 'mmv.hash', function ( e ) {
+                       self.internalHashChange( e );
                } );
        }
 
@@ -159,7 +162,7 @@
        };
 
        /**
-        * Handles the browser location hash on pageload or popstate
+        * Handles the browser location hash on pageload or hash change
         */
        MMVB.hash = function () {
                // There is no point loading the mmv if it isn't loaded yet for 
hash changes unrelated to the mmv
@@ -168,11 +171,27 @@
                        return;
                }
 
+               if ( this.skipNextHashHandling ) {
+                       this.skipNextHashHandling = false;
+                       return;
+               }
+
                this.loadViewer().then( function () {
                        mw.mediaViewer.hash();
                } );
        };
 
+       /**
+        * Handles hash change requests coming from mmv
+        * @param {jQuery.Event} e Custom mmv.hash event
+        */
+       MMVB.internalHashChange = function ( e ) {
+               // Since we voluntarily changed the hash, we don't want 
MMVB.hash to treat it
+               this.skipNextHashHandling = true;
+
+               window.location.hash = e.hash;
+       };
+
        mw.mmv.MultimediaViewerBootstrap = MultimediaViewerBootstrap;
 
        bootstrap = new MultimediaViewerBootstrap();
diff --git a/resources/mmv/mmv.js b/resources/mmv/mmv.js
index 23e57e3..3878625 100755
--- a/resources/mmv/mmv.js
+++ b/resources/mmv/mmv.js
@@ -17,7 +17,7 @@
 
 ( function ( mw, $ ) {
        var MMVP,
-               comingFromPopstate = false;
+               comingFromHashChange = false;
 
        /**
         * Analyses the page, looks for image content and sets up the hooks
@@ -214,7 +214,7 @@
 
                this.currentImageFilename = 
image.filePageTitle.getPrefixedText();
                this.currentImageFileTitle = image.filePageTitle;
-               this.lightbox.iface.comingFromPopstate = 
this.comingFromPopstate;
+               this.lightbox.iface.comingFromHashChange = 
this.comingFromHashChange;
 
                if ( !this.isOpen ) {
                        this.lightbox.open();
@@ -253,7 +253,7 @@
                                .then( function() { 
viewer.startListeningToScroll(); } );
                } );
 
-               this.comingFromPopstate = false;
+               this.comingFromHashChange = false;
        };
 
        /**
@@ -268,7 +268,7 @@
                        return;
                }
 
-               this.comingFromPopstate = !updateHash;
+               this.comingFromHashChange = !updateHash;
 
                $.each( this.thumbs, function ( idx, thumb ) {
                        if ( thumb.title.getPrefixedText() === title ) {
@@ -541,10 +541,10 @@
         */
        MMVP.close = function () {
                $( document.body ).removeClass( 'mw-mlb-lightbox-open' );
-               if ( comingFromPopstate === false ) {
-                       history.pushState( {}, '', '#' );
+               if ( comingFromHashChange === false ) {
+                       $( document ).trigger( $.Event( 'mmv.hash', { hash : 
'#' } ) );
                } else {
-                       comingFromPopstate = false;
+                       comingFromHashChange = false;
                }
 
                this.isOpen = false;
@@ -554,14 +554,14 @@
         * Handles a hash change coming from the browser
         */
        MMVP.hash = function () {
-               var hash = decodeURIComponent( document.location.hash ),
+               var hash = decodeURIComponent( window.location.hash ),
                        linkState = hash.split( '/' );
 
                if ( linkState[0] === '#mediaviewer' ) {
                        this.loadImageByTitle( linkState[ 1 ] );
                } else if ( this.isOpen ) {
-                       // This allows us to avoid the pushState that normally 
happens on close
-                       comingFromPopstate = true;
+                       // This allows us to avoid the mmv.hash event that 
normally happens on close
+                       comingFromHashChange = true;
 
                        if ( this.lightbox && this.lightbox.iface ) {
                                this.lightbox.iface.unattach();
diff --git a/resources/mmv/mmv.lightboxinterface.js 
b/resources/mmv/mmv.lightboxinterface.js
index 9e8ea02..cfc7e1a 100644
--- a/resources/mmv/mmv.lightboxinterface.js
+++ b/resources/mmv/mmv.lightboxinterface.js
@@ -152,8 +152,8 @@
 
                this.viewer.ui = this;
 
-               if ( !this.comingFromPopstate ) {
-                       history.pushState( {}, '', hashFragment );
+               if ( !this.comingFromHashChange ) {
+                       $( document ).trigger( $.Event( 'mmv.hash', { hash : 
hashFragment } ) );
                }
 
                this.handleEvent( 'keydown', function ( e ) { ui.keydown( e ); 
} );
diff --git a/tests/qunit/mmv.bootstrap.test.js 
b/tests/qunit/mmv.bootstrap.test.js
index a2511f7..7238618 100644
--- a/tests/qunit/mmv.bootstrap.test.js
+++ b/tests/qunit/mmv.bootstrap.test.js
@@ -171,7 +171,7 @@
                bootstrap.hash = $.noop;
        } );
 
-       QUnit.test( 'Only load the viewer on a valid hash', 1, function ( 
assert ) {
+       QUnit.asyncTest( 'Only load the viewer on a valid hash', 1, function ( 
assert ) {
                var bootstrap;
 
                window.location.hash = '';
@@ -186,14 +186,38 @@
                window.location.hash = 'Foo';
 
                bootstrap.loadViewer = function () {
+                       QUnit.start();
                        assert.ok( true, 'Viewer should be loaded' );
+                       bootstrap.hash = $.noop;
+                       window.location.hash = '';
                        return $.Deferred().reject();
                };
 
                window.location.hash = 'mediaviewer/foo';
+       } );
 
-               bootstrap.hash = $.noop;
-
+       QUnit.asyncTest( 'internalHashChange', 1, function ( assert ) {
                window.location.hash = '';
+
+               var bootstrap = new mw.mmv.MultimediaViewerBootstrap(),
+                       hash = '#mediaviewer/foo',
+                       oldHash = bootstrap.hash;
+
+               bootstrap.hash = function () {
+                       oldHash.call( this );
+
+                       bootstrap.hash = $.noop;
+                       window.location.hash = '';
+                       QUnit.start();
+               };
+
+               bootstrap.loadViewer = function () {
+                       assert.ok( false, 'Viewer should not be loaded' );
+                       return $.Deferred().reject();
+               };
+
+               bootstrap.internalHashChange( { hash: hash } );
+
+               assert.strictEqual( window.location.hash, hash, 'Window\'s hash 
has been updated correctly' );
        } );
 }( mediaWiki, jQuery ) );
diff --git a/tests/qunit/mmv.lightboxinterface.test.js 
b/tests/qunit/mmv.lightboxinterface.test.js
index 1ca8a71..ddd20dd 100644
--- a/tests/qunit/mmv.lightboxinterface.test.js
+++ b/tests/qunit/mmv.lightboxinterface.test.js
@@ -212,7 +212,7 @@
                mw.mediaViewer.lightbox = { currentIndex: 0 };
 
                // This lets us avoid pushing a state to the history, which 
might interfere with other tests
-               lightbox.comingFromPopstate = true;
+               lightbox.comingFromHashChange = true;
                // Load is needed to start listening to metadata events
                lightbox.load( { getImageElement: function() { return 
$.Deferred().reject(); } } );
 
diff --git a/tests/qunit/mmv.test.js b/tests/qunit/mmv.test.js
index 8b7aafb..c404a07 100644
--- a/tests/qunit/mmv.test.js
+++ b/tests/qunit/mmv.test.js
@@ -87,7 +87,7 @@
                        imageSrc = 'Foo bar.jpg',
                        image = { filePageTitle: new mw.Title( 'File:' + 
imageSrc ) };
 
-               document.location.hash = '';
+               window.location.hash = '';
 
                oldUnattach = lightbox.unattach;
 
@@ -102,16 +102,14 @@
 
                assert.ok( !mw.mediaViewer.isOpen, 'Viewer is closed' );
 
-               // The mediaViewer won't be initialized through bootstrap by 
any other way than a valid action
-               document.location.hash = 'mediaviewer/Foo';
                mw.mediaViewer.isOpen = true;
-               // From now on we're certain that the viewer receives hash 
changes through bootstrap
 
                // Verify that passing an invalid mmv hash when the mmv is open 
triggers unattach()
-               document.location.hash = 'Foo';
+               window.location.hash = 'Foo';
+               mw.mediaViewer.hash();
 
                // Verify that mmv doesn't reset a foreign hash
-               assert.strictEqual( document.location.hash, '#Foo', 'Foreign 
hash remains intact' );
+               assert.strictEqual( window.location.hash, '#Foo', 'Foreign hash 
remains intact' );
                assert.ok( !mw.mediaViewer.isOpen, 'Viewer is closed' );
 
                lightbox.unattach = function () {
@@ -120,10 +118,11 @@
                };
 
                // Verify that passing an invalid mmv hash  when the mmv is 
closed doesn't trigger unattach()
-               document.location.hash = 'Bar';
+               window.location.hash = 'Bar';
+               mw.mediaViewer.hash();
 
                // Verify that mmv doesn't reset a foreign hash
-               assert.strictEqual( document.location.hash, '#Bar', 'Foreign 
hash remains intact' );
+               assert.strictEqual( window.location.hash, '#Bar', 'Foreign hash 
remains intact' );
 
                mw.mediaViewer.lightbox = { images: [ image ] };
 
@@ -135,18 +134,21 @@
 
                // Open a valid mmv hash link and check that the right image is 
requested.
                // imageSrc contains a space without any encoding on purpose
-               document.location.hash = 'mediaviewer/File:' + imageSrc;
+               window.location.hash = 'mediaviewer/File:' + imageSrc;
+               mw.mediaViewer.hash();
 
                // Reset the hash, because for some browsers switching from the 
non-URI-encoded to
                // the non-URI-encoded version of the same text with a space 
will not trigger a hash change
-               document.location.hash = '';
+               window.location.hash = '';
+               mw.mediaViewer.hash();
 
                // Try again with an URI-encoded imageSrc containing a space
-               document.location.hash = 'mediaviewer/File:' + 
encodeURIComponent( imageSrc );
+               window.location.hash = 'mediaviewer/File:' + 
encodeURIComponent( imageSrc );
+               mw.mediaViewer.hash();
 
                mw.mediaViewer.lightbox = oldLightbox;
                mw.mediaViewer.loadImageByTitle = oldLoadImage;
 
-               document.location.hash = '';
+               window.location.hash = '';
        } );
 }( mediaWiki, jQuery ) );

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I2d23699bb444d9eb533d239e25bc47fa96c902a9
Gerrit-PatchSet: 3
Gerrit-Project: mediawiki/extensions/MultimediaViewer
Gerrit-Branch: master
Gerrit-Owner: Gilles <gdu...@wikimedia.org>
Gerrit-Reviewer: Aarcos <aarcos.w...@gmail.com>
Gerrit-Reviewer: Gergő Tisza <gti...@wikimedia.org>
Gerrit-Reviewer: Gilles <gdu...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to