diff --git a/resources/mmv/mmv.js b/resources/mmv/mmv.js index 52b349235..b561267c3 100755 --- a/resources/mmv/mmv.js +++ b/resources/mmv/mmv.js @@ -232,11 +232,11 @@ * Loads and sets the specified image. It also updates the controls. * @param {mw.mmv.LightboxInterface} ui image container * @param {mw.mmv.model.Thumbnail} thumbnail thumbnail information - * @param {HTMLImageElement} image + * @param {HTMLImageElement} imageElement * @param {mw.mmv.model.ThumbnailWidth} imageWidths */ - MMVP.setImage = function ( ui, thumbnail, image, imageWidths ) { - ui.canvas.setImageAndMaxDimensions( thumbnail, image, imageWidths ); + MMVP.setImage = function ( ui, thumbnail, imageElement, imageWidths ) { + ui.canvas.setImageAndMaxDimensions( thumbnail, imageElement, imageWidths ); this.updateControls(); }; @@ -255,7 +255,6 @@ this.currentIndex = image.index; - this.currentImageFilename = image.filePageTitle.getPrefixedText(); this.currentImageFileTitle = image.filePageTitle; if ( !this.isOpen ) { @@ -279,15 +278,17 @@ imageWidths = this.ui.canvas.getCurrentImageWidths(); - this.resetBlurredThumbnailStates(); start = $.now(); imagePromise = this.fetchThumbnailForLightboxImage( image, imageWidths.real ); - viewer.displayPlaceholderThumbnail( image, $initialImage, imageWidths ); + this.resetBlurredThumbnailStates(); + if ( imagePromise.state() === 'pending' ) { + this.displayPlaceholderThumbnail( image, $initialImage, imageWidths ); + } - this.setupProgressBar( image, imagePromise ); + this.setupProgressBar( image, imagePromise, imageWidths.real ); imagePromise.done( function ( thumbnail, imageElement ) { if ( viewer.currentIndex !== image.index ) { @@ -356,29 +357,52 @@ }; /** - * Resets the cross-request states needed to handle the blurred thumbnail logic + * @private + * Image loading progress. Keyed by image (database) name + '|' + thumbnail width in pixels, + * value is undefined, 'blurred' or 'real' (meaning respectively that no thumbnail is shown + * yet / the thumbnail that existed on the page is shown, enlarged and blurred / the real, + * correct-size thumbnail is shown). + * @property {Object.} + */ + MMVP.thumbnailStateCache = {}; + + /** + * Resets the cross-request states needed to handle the blurred thumbnail logic. */ MMVP.resetBlurredThumbnailStates = function () { + /** + * Stores whether the real image was loaded and displayed already. + * This is reset when paging, so it is not necessarily accurate. + * @property {boolean} + */ this.realThumbnailShown = false; + + /** + * Stores whether the a blurred placeholder is being displayed in place of the real image. + * When a placeholder is displayed, but it is not blurred, this is false. + * This is reset when paging, so it is not necessarily accurate. + * @property {boolean} + */ this.blurredThumbnailShown = false; }; /** * Display the real, full-resolution, thumbnail that was fetched with fetchThumbnail * @param {mw.mmv.model.Thumbnail} thumbnail - * @param {HTMLImageElement} image + * @param {HTMLImageElement} imageElement * @param {mw.mmv.model.ThumbnailWidth} imageWidths * @param {number} loadTime Time it took to load the thumbnail */ - MMVP.displayRealThumbnail = function ( thumbnail, image, imageWidths, loadTime ) { + MMVP.displayRealThumbnail = function ( thumbnail, imageElement, imageWidths, loadTime ) { this.realThumbnailShown = true; - this.setImage( this.ui, thumbnail, image, imageWidths ); + this.setImage( this.ui, thumbnail, imageElement, imageWidths ); - // We only animate unblur if the image wasn't loaded from the cache + // We only animate unblurWithAnimation if the image wasn't loaded from the cache // A load in < 10ms is considered to be a browser cache hit - // And of course we only unblur if there was a blur to begin with if ( this.blurredThumbnailShown && loadTime > 10 ) { + this.ui.canvas.unblurWithAnimation(); + } else { this.ui.canvas.unblur(); } @@ -390,13 +414,15 @@ * @param {mw.mmv.LightboxImage} image * @param {jQuery} $initialImage The thumbnail from the page * @param {mw.mmv.model.ThumbnailWidth} imageWidths - * @param {boolean} [recursion=false] for internal use, never set this + * @param {boolean} [recursion=false] for internal use, never set this when calling from outside */ MMVP.displayPlaceholderThumbnail = function ( image, $initialImage, imageWidths, recursion ) { var viewer = this, size = { width : image.originalWidth, height : image.originalHeight }; - // If the actual image has already been displayed, there's no point showing the blurry one + // If the actual image has already been displayed, there's no point showing the blurry one. + // This can happen if the API request to get the original image size needed to show the + // placeholder thumbnail takes longer then loading the actual thumbnail. if ( this.realThumbnailShown ) { return; } @@ -425,58 +451,71 @@ } }; + /** + * @private + * Image loading progress. Keyed by image (database) name + '|' + thumbnail width in pixels, + * value is a number between 0-100. + * @property {Object.} + */ + MMVP.progressCache = {}; + /** * Displays a progress bar for the image loading, if necessary, and sets up handling of * all the related callbacks. - * FIXME would be nice to pass a simple promise which only returns a single number - * and does not fire when the image is not visible * @param {mw.mmv.LightboxImage} image * @param {jQuery.Promise.} imagePromise + * @param {number} imageWidth needed for caching progress (FIXME) */ - MMVP.setupProgressBar = function ( image, imagePromise ) { - var viewer = this; - - // Reset the progress bar, it could be at any state if we're calling loadImage - // while another image is already loading - // FIXME we should probably jump to the current progress instead - viewer.ui.panel.progressBar.percent( 0 ); + MMVP.setupProgressBar = function ( image, imagePromise, imageWidth ) { + var viewer = this, + progressBar = viewer.ui.panel.progressBar, + key = image.filePageTitle.getPrefixedDb() + '|' + imageWidth; if ( imagePromise.state() !== 'pending' ) { - // image has already loaded (or failed to load) - nothing to do + // image has already loaded (or failed to load) - do not show the progress bar + progressBar.hide(); return; } - // FIXME this is all wrong, we might be navigating back to a half-loaded image + if ( !this.progressCache[key] ) { + // Animate progress bar to 5 to give a sense that something is happening, and make sure + // the progress bar is noticeable, even if we're sitting at 0% stuck waiting for + // server-side processing, such as thumbnail (re)generation + progressBar.jumpTo( 0 ); + progressBar.animateTo( 5 ); + viewer.progressCache[key] = 5; + } else { + progressBar.jumpTo( this.progressCache[key] ); + } - // Animate progress bar to 5 to give a sense to something is happening, even if we're - // stuck waiting for server-side processing, such as thumbnail (re)generation - viewer.ui.panel.progressBar.percent( 5 ); - - imagePromise.progress( function ( thumbnailInfoResponse, imageResponse ) { - // FIXME this should be explained in a comment - var progress = imageResponse[1]; - - if ( viewer.currentIndex !== image.index ) { + // FIXME would be nice to have a "filtered" promise which does not fire when the image is not visible + imagePromise.progress( function ( progress ) { + // We pretend progress is always at least 5%, so progress events below 5% should be ignored + // 100 will be handled by the done handler, do not mix two animations + if ( progress < 5 || progress === 100 ) { return; } - // We started from 5, don't move backwards - if ( progress > 5 ) { - viewer.ui.panel.progressBar.percent( progress ); + viewer.progressCache[key] = progress; + + // Touch the UI only if the user is looking at this image + if ( viewer.currentIndex === image.index ) { + progressBar.animateTo( progress ); } } ).done( function () { - if ( viewer.currentIndex !== image.index ) { - return; - } + viewer.progressCache[key] = 100; - // Fallback in case the browser doesn't have fancy progress updates - viewer.ui.panel.progressBar.percent( 100 ); - } ).fail( function () { - if ( viewer.currentIndex !== image.index ) { - return; + if ( viewer.currentIndex === image.index ) { + // Fallback in case the browser doesn't have fancy progress updates + progressBar.animateTo( 100 ); + } + } ).fail( function () { + viewer.progressCache[key] = 100; + + if ( viewer.currentIndex === image.index ) { + // Hide progress bar on error + progressBar.hide(); } - // Hide progress bar on error - viewer.ui.panel.progressBar.percent( 0 ); } ); }; @@ -671,7 +710,9 @@ * @param {string} [sampleUrl] a thumbnail URL for the same file (but with different size) (might be missing) * @param {number} [originalWidth] the width of the original, full-sized file (might be missing) * @param {number} [originalHeight] the height of the original, full-sized file (might be missing) - * @returns {jQuery.Promise.} + * @returns {jQuery.Promise.} A promise resolving to + * a thumbnail model and an element. It might or might not have progress events which + * return a single number. */ MMVP.fetchThumbnail = function ( fileTitle, width, sampleUrl, originalWidth, originalHeight ) { var viewer = this, @@ -714,7 +755,10 @@ } ); } - return $.when( thumbnailPromise, imagePromise ); + return $.when( thumbnailPromise, imagePromise ).then( null, null, function ( thumbnailProgress, imageProgress ) { + // Make progress events have a nice format. + return imageProgress[1]; + } ); }; /** diff --git a/resources/mmv/provider/mmv.provider.Image.js b/resources/mmv/provider/mmv.provider.Image.js index fac28b92b..c6becc59b 100644 --- a/resources/mmv/provider/mmv.provider.Image.js +++ b/resources/mmv/provider/mmv.provider.Image.js @@ -37,10 +37,12 @@ } /** - * Loads an image and returns it. - * Includes performance metrics. + * Loads an image and returns it. Includes performance metrics via mw.mmv.Performance. + * When the browser supports it, the image is loaded as an AJAX request. * @param {string} url - * @return {jQuery.Promise.} a promise which resolves to the image object + * @return {jQuery.Promise.} A promise which resolves to the image object. + * When loaded via AJAX, it has progress events, which return an array with the content loaded + * so far and with the progress as a floating-point number between 0 and 100. */ Image.prototype.get = function ( url ) { var provider = this, @@ -59,11 +61,12 @@ provider.performance.recordEntry( 'image', $.now() - start, url ); } ); } + this.cache[cacheKey].fail( function ( error ) { + mw.log( provider.constructor.name + ' provider failed to load: ', error ); + } ); } - return this.cache[cacheKey].fail( function ( error ) { - mw.log( provider.constructor.name + ' provider failed to load: ', error ); - } ); + return this.cache[cacheKey]; }; /** diff --git a/resources/mmv/ui/mmv.ui.canvas.js b/resources/mmv/ui/mmv.ui.canvas.js index 5b0b2721d..2d04c27ba 100644 --- a/resources/mmv/ui/mmv.ui.canvas.js +++ b/resources/mmv/ui/mmv.ui.canvas.js @@ -113,11 +113,11 @@ * Sets contained image and also the max dimensions. Called while resizing the viewer. * Assumes set function called before. * @param {mw.mmv.model.Thumbnail} thumbnail thumbnail information - * @param {HTMLImageElement} imageEle + * @param {HTMLImageElement} imageElement * @param {mw.mmv.model.ThumbnailWidth} imageWidths */ - C.setImageAndMaxDimensions = function( thumbnail, imageEle, imageWidths ) { - var $image = $( imageEle ); + C.setImageAndMaxDimensions = function( thumbnail, imageElement, imageWidths ) { + var $image = $( imageElement ); function makeMaxMatchParent ( $image ) { $image.css( { @@ -128,10 +128,10 @@ // we downscale larger images but do not scale up smaller ones, that would look ugly if ( thumbnail.width > imageWidths.cssWidth ) { - imageEle.width = imageWidths.cssWidth; + imageElement.width = imageWidths.cssWidth; } - if ( this.$image.is( imageEle ) ) { // http://bugs.jquery.com/ticket/4087 + if ( this.$image.is( imageElement ) ) { // http://bugs.jquery.com/ticket/4087 // We may be changing the width of the image when we resize, we should also // update the max dimensions otherwise the image is not scaled properly makeMaxMatchParent( this.$image ); @@ -233,7 +233,7 @@ /** * Animates the image into focus */ - C.unblur = function() { + C.unblurWithAnimation = function() { var self = this, animationLength = 300; @@ -251,15 +251,19 @@ 'filter' : 'blur(' + step + 'px)' } ); }, complete: function () { - // When the animation is complete, the blur value is 0 - // We apply empty CSS values to remove the inline styles applied by jQuery - // so that they don't get in the way of styles defined in CSS - self.$image.css( { '-webkit-filter' : '', 'opacity' : '' } ) - .removeClass( 'blurred' ); + // When the animation is complete, the blur value is 0, clean things up + self.unblur(); } } ); }; + C.unblur = function() { + // We apply empty CSS values to remove the inline styles applied by jQuery + // so that they don't get in the way of styles defined in CSS + this.$image.css( { '-webkit-filter' : '', 'opacity' : '' } ) + .removeClass( 'blurred' ); + }; + /** * Displays a message and error icon when loading the image fails. * @param {string} error error message diff --git a/resources/mmv/ui/mmv.ui.progressBar.js b/resources/mmv/ui/mmv.ui.progressBar.js index 266903e51..887723261 100644 --- a/resources/mmv/ui/mmv.ui.progressBar.js +++ b/resources/mmv/ui/mmv.ui.progressBar.js @@ -45,37 +45,46 @@ }; PBP.empty = function () { + this.hide(); + }; + + /** + * Hides the bar, resets it to 0 and stops any animation in progress. + */ + PBP.hide = function () { this.$progress.addClass( 'empty' ); + this.$percent.stop().css( { width : 0 } ); }; /** * Handles the progress display when a percentage of progress is received - * @param {number} percent + * @param {number} percent a number between 0 and 100 */ - PBP.percent = function ( percent ) { + PBP.animateTo = function ( percent ) { var panel = this; - if ( percent === 0 ) { - // When a 0% update comes in, we jump without animation to 0 and we hide the bar - this.$progress.addClass( 'empty' ); - this.$percent.stop().css( { width : 0 } ); - } else if ( percent === 100 ) { + this.$progress.removeClass( 'empty' ); + this.$percent.stop(); + + if ( percent === 100 ) { // When a 100% update comes in, we make sure that the bar is visible, we animate // fast to 100 and we hide the bar when the animation is done - this.$progress.removeClass( 'empty' ); - this.$percent.stop().animate( { width : percent + '%' }, 50, 'swing', - function () { - // Reset the position for good measure - panel.$percent.stop().css( { width : 0 } ); - panel.$progress.addClass( 'empty' ); - } ); + this.$percent.animate( { width : percent + '%' }, 50, 'swing', $.proxy( panel.hide, panel ) ); } else { // When any other % update comes in, we make sure the bar is visible // and we animate to the right position - this.$progress.removeClass( 'empty' ); - this.$percent.stop().animate( { width : percent + '%' } ); + this.$percent.animate( { width : percent + '%' } ); } }; + /** + * Goes to the given percent without animation + * @param {number} percent a number between 0 and 100 + */ + PBP.jumpTo = function ( percent ) { + this.$progress.removeClass( 'empty' ); + this.$percent.stop().css( { width : percent + '%' } ); + }; + mw.mmv.ui.ProgressBar = ProgressBar; }( mediaWiki, jQuery, OO ) ); diff --git a/tests/qunit/mmv/mmv.test.js b/tests/qunit/mmv/mmv.test.js index 829545e6b..ba6237049 100644 --- a/tests/qunit/mmv/mmv.test.js +++ b/tests/qunit/mmv/mmv.test.js @@ -102,8 +102,7 @@ QUnit.test( 'Progress', 4, function ( assert ) { var imageDeferred = $.Deferred(), - viewer = new mw.mmv.MultimediaViewer(), - i = 0; + viewer = new mw.mmv.MultimediaViewer(); viewer.thumbs = []; viewer.displayPlaceholderThumbnail = $.noop; @@ -114,28 +113,15 @@ viewer.ui = { setupForLoad : $.noop, canvas : { set : $.noop, + unblurWithAnimation: $.noop, + unblur: $.noop, getCurrentImageWidths : function () { return { real : 0 }; } }, panel : { setImageInfo : $.noop, animateMetadataOnce : $.noop, progressBar: { - percent : function ( percent ) { - if ( i === 0 ) { - assert.strictEqual( percent, 0, - 'Percentage correctly reset by loadImage' ); - } else if ( i === 1 ) { - assert.strictEqual( percent, 5, - 'Percentage correctly animated to 5 by loadImage' ); - } else if ( i === 2 ) { - assert.strictEqual( percent, 45, - 'Percentage correctly funneled to panel UI' ); - } else { - assert.strictEqual( percent, 100, - 'Percentage correctly funneled to panel UI' ); - } - - i++; - } + animateTo: this.sandbox.stub(), + jumpTo: this.sandbox.stub() } }, open : $.noop }; @@ -145,9 +131,116 @@ viewer.thumbnailInfoProvider.get = function() { return $.Deferred().resolve( {} ); }; viewer.loadImage( { filePageTitle : new mw.Title( 'File:Stuff.jpg' ) }, new Image() ); + assert.ok( viewer.ui.panel.progressBar.jumpTo.lastCall.calledWith( 0 ), + 'Percentage correctly reset by loadImage' ); + + assert.ok( viewer.ui.panel.progressBar.animateTo.lastCall.calledWith( 5 ), + 'Percentage correctly animated to 5 by loadImage' ); imageDeferred.notify( 'response', 45 ); + assert.ok( viewer.ui.panel.progressBar.animateTo.lastCall.calledWith( 45 ), + 'Percentage correctly funneled to panel UI' ); + imageDeferred.resolve(); + assert.ok( viewer.ui.panel.progressBar.animateTo.lastCall.calledWith( 100 ), + 'Percentage correctly funneled to panel UI' ); + + viewer.close(); + } ); + + QUnit.test( 'Progress when switching images', 11, function ( assert ) { + var firstImageDeferred = $.Deferred(), + secondImageDeferred = $.Deferred(), + firstImage = { index: 1, filePageTitle : new mw.Title( 'File:First.jpg' ) }, + secondImage = { index: 2, filePageTitle : new mw.Title( 'File:Second.jpg' ) }, + viewer = new mw.mmv.MultimediaViewer(); + + viewer.thumbs = []; + viewer.displayPlaceholderThumbnail = $.noop; + viewer.setImage = $.noop; + viewer.scroll = $.noop; + viewer.preloadFullscreenThumbnail = $.noop; + viewer.preloadImagesMetadata = $.noop; + viewer.preloadThumbnails = $.noop; + viewer.fetchSizeIndependentLightboxInfo = function () { return $.Deferred().resolve(); }; + viewer.ui = { + setupForLoad : $.noop, + canvas : { set : $.noop, + unblurWithAnimation: $.noop, + unblur: $.noop, + getCurrentImageWidths : function () { return { real : 0 }; } }, + panel : { + setImageInfo : $.noop, + animateMetadataOnce : $.noop, + progressBar: { + hide: this.sandbox.stub(), + animateTo: this.sandbox.stub(), + jumpTo: this.sandbox.stub() + } + }, + open : $.noop, + empty: $.noop }; + + viewer.imageInfoProvider.get = function() { return $.Deferred().resolve(); }; + viewer.thumbnailInfoProvider.get = function() { return $.Deferred().resolve( {} ); }; + + // load some image + viewer.imageProvider.get = this.sandbox.stub().returns( firstImageDeferred ); + viewer.loadImage( firstImage, new Image() ); + + assert.ok( viewer.ui.panel.progressBar.jumpTo.lastCall.calledWith( 0 ), + 'Percentage correctly reset for new first image' ); + assert.ok( viewer.ui.panel.progressBar.animateTo.lastCall.calledWith( 5 ), + 'Percentage correctly animated to 5 for first new image' ); + + firstImageDeferred.notify( 'response', 20 ); + + assert.ok( viewer.ui.panel.progressBar.animateTo.lastCall.calledWith( 20 ), + 'Percentage correctly animated when active image is loading' ); + + // change to another image + viewer.imageProvider.get = this.sandbox.stub().returns( secondImageDeferred ); + viewer.loadImage( secondImage, new Image() ); + + assert.ok( viewer.ui.panel.progressBar.jumpTo.lastCall.calledWith( 0 ), + 'Percentage correctly reset for second new image' ); + assert.ok( viewer.ui.panel.progressBar.animateTo.lastCall.calledWith( 5 ), + 'Percentage correctly animated to 5 for second new image' ); + + secondImageDeferred.notify( 'response', 30 ); + + assert.ok( viewer.ui.panel.progressBar.animateTo.lastCall.calledWith( 30 ), + 'Percentage correctly animated when active image is loading' ); + + // this is the most convenient way of checking for new calls - just reset() and check called + viewer.ui.panel.progressBar.animateTo.reset(); + viewer.ui.panel.progressBar.jumpTo.reset(); + + firstImageDeferred.notify( 'response', 40 ); + + assert.ok( !viewer.ui.panel.progressBar.animateTo.called, + 'Percentage not animated when inactive image is loading' ); + assert.ok( !viewer.ui.panel.progressBar.jumpTo.called, + 'Percentage not changed when inactive image is loading' ); + + secondImageDeferred.notify( 'response', 50 ); + + // change back to first image + viewer.loadImage( firstImage, new Image() ); + + assert.ok( viewer.ui.panel.progressBar.jumpTo.lastCall.calledWith( 40 ), + 'Percentage jumps to right value when changing images' ); + + secondImageDeferred.resolve(); + assert.ok( !viewer.ui.panel.progressBar.hide.called, + 'Progress bar not hidden when something finishes in the background' ); + + // change to second image which has finished loading + viewer.imageProvider.get = this.sandbox.stub().returns( secondImageDeferred ); + viewer.loadImage( secondImage, new Image() ); + + assert.ok( viewer.ui.panel.progressBar.hide.called, + 'Progress bar not hidden when switching to finished image' ); viewer.close(); } ); @@ -172,6 +265,8 @@ viewer.setImage = $.noop; viewer.ui = { canvas : { + unblurWithAnimation: $.noop, + unblur: $.noop, maybeDisplayPlaceholder : function() { return true; } } }; viewer.imageInfoProvider.get = this.sandbox.stub(); @@ -193,6 +288,8 @@ viewer.currentIndex = 1; viewer.setImage = $.noop; viewer.ui = { canvas : { + unblurWithAnimation: $.noop, + unblur: $.noop, maybeDisplayPlaceholder : function() { return true; } } }; viewer.imageInfoProvider.get = this.sandbox.stub().returns( $.Deferred().resolve( {width: 100, height: 100 } ) ); @@ -213,8 +310,11 @@ viewer.setImage = $.noop; viewer.ui = { - showImage : $.noop - }; + showImage : $.noop, + canvas : { + unblurWithAnimation: $.noop, + unblur: $.noop + } }; viewer.displayRealThumbnail(); @@ -227,94 +327,78 @@ assert.ok( !viewer.blurredThumbnailShown, 'Placeholder state is correct' ); } ); - QUnit.test( 'displayRealThumbnail', 1, function ( assert ) { + QUnit.test( 'displayRealThumbnail', 2, function ( assert ) { var viewer = new mw.mmv.MultimediaViewer(); viewer.setImage = $.noop; viewer.ui = { canvas : { - unblur : function () { assert.ok( false, 'Image should not be unblurred yet' ); } + unblurWithAnimation : this.sandbox.stub(), + unblur: $.noop } }; viewer.blurredThumbnailShown = true; - // Should not result in an unblur (image cache from cache) + // Should not result in an unblurWithAnimation animation (image cache from cache) viewer.displayRealThumbnail( undefined, undefined, undefined, 5 ); + assert.ok( !viewer.ui.canvas.unblurWithAnimation.called, 'There should not be an unblurWithAnimation animation' ); - viewer.ui.canvas.unblur = function () { - assert.ok( true, 'Image needs to be unblurred' ); - }; - - // Should result in an unblur (image didn't come from cache) + // Should result in an unblurWithAnimation (image didn't come from cache) viewer.displayRealThumbnail( undefined, undefined, undefined, 1000 ); + assert.ok( viewer.ui.canvas.unblurWithAnimation.called, 'There should be an unblurWithAnimation animation' ); } ); - QUnit.test( 'New image loaded while another one is loading', 1, function ( assert ) { - var imageDeferred, - ligthboxInfoDeferred, - viewer = new mw.mmv.MultimediaViewer(), + QUnit.test( 'New image loaded while another one is loading', 5, function ( assert ) { + var viewer = new mw.mmv.MultimediaViewer(), firstImageDeferred = $.Deferred(), secondImageDeferred = $.Deferred(), firstLigthboxInfoDeferred = $.Deferred(), secondLigthboxInfoDeferred = $.Deferred(); viewer.preloadFullscreenThumbnail = $.noop; - viewer.fetchSizeIndependentLightboxInfo = function () { return ligthboxInfoDeferred.promise(); }; + viewer.fetchSizeIndependentLightboxInfo = this.sandbox.stub(); viewer.ui = { setupForLoad : $.noop, canvas : { set : $.noop, getCurrentImageWidths : function () { return { real : 0 }; } }, - panel : { setImageInfo : function () { - assert.ok( false, 'Metadata of the first image should not be shown' ); - }, + panel : { + setImageInfo : this.sandbox.stub(), progressBar: { - percent : function ( response, percent ) { - if ( percent === 45 ) { - assert.ok( false, 'Progress of the first image should not be shown' ); - } - } + animateTo : this.sandbox.stub(), + jumpTo : this.sandbox.stub() }, empty: $.noop, animateMetadataOnce: $.noop }, open : $.noop, empty: $.noop }; + viewer.displayRealThumbnail = this.sandbox.stub(); viewer.eachPrealoadableLightboxIndex = $.noop; - viewer.animateMetadataDivOnce = function () { - assert.ok( false, 'Metadata of the first image should not be animated' ); - return $.Deferred().reject(); - }; - - viewer.imageProvider.get = function() { return imageDeferred.promise(); }; + viewer.animateMetadataDivOnce = this.sandbox.stub().returns( $.Deferred().reject() ); + viewer.imageProvider.get = this.sandbox.stub(); viewer.imageInfoProvider.get = function() { return $.Deferred().reject(); }; viewer.thumbnailInfoProvider.get = function() { return $.Deferred().resolve( {} ); }; - imageDeferred = firstImageDeferred; - ligthboxInfoDeferred = firstLigthboxInfoDeferred; + viewer.imageProvider.get.returns( firstImageDeferred.promise() ); + viewer.fetchSizeIndependentLightboxInfo.returns( firstLigthboxInfoDeferred.promise() ); viewer.loadImage( { filePageTitle : new mw.Title( 'File:Foo.jpg' ), index : 0 }, new Image() ); + assert.ok( !viewer.animateMetadataDivOnce.called, 'Metadata of the first image should not be animated' ); + assert.ok( !viewer.ui.panel.setImageInfo.called, 'Metadata of the first image should not be shown' ); - imageDeferred = secondImageDeferred; - ligthboxInfoDeferred = secondLigthboxInfoDeferred; + viewer.imageProvider.get.returns( secondImageDeferred.promise() ); + viewer.fetchSizeIndependentLightboxInfo.returns( secondLigthboxInfoDeferred.promise() ); viewer.loadImage( { filePageTitle : new mw.Title( 'File:Bar.jpg' ), index : 1 }, new Image() ); - viewer.displayRealThumbnail = function () { - assert.ok( false, 'The first image being done loading should have no effect'); - }; - + viewer.ui.panel.progressBar.animateTo.reset(); firstImageDeferred.notify( undefined, 45 ); + assert.ok( !viewer.ui.panel.progressBar.animateTo.reset.called, 'Progress of the first image should not be shown' ); + firstImageDeferred.resolve(); firstLigthboxInfoDeferred.resolve(); + assert.ok( !viewer.displayRealThumbnail.called, 'The first image being done loading should have no effect'); - viewer.ui.panel.setImageInfo = $.noop; - viewer.animateMetadataDivOnce = function() { return $.Deferred().reject(); }; - - viewer.displayRealThumbnail = function () { - assert.ok( true, 'The second image being done loading should result in the image being shown'); - QUnit.start(); - viewer.close(); - }; - - QUnit.stop(); + viewer.displayRealThumbnail = this.sandbox.spy( function () { viewer.close(); } ); secondImageDeferred.resolve(); secondLigthboxInfoDeferred.resolve(); + assert.ok( viewer.displayRealThumbnail.called, 'The second image being done loading should result in the image being shown'); } ); QUnit.test( 'Events are not trapped after the viewer is closed', 0, function( assert ) { diff --git a/tests/qunit/mmv/ui/mmv.ui.canvas.test.js b/tests/qunit/mmv/ui/mmv.ui.canvas.test.js index 689560253..a6875e3f1 100644 --- a/tests/qunit/mmv/ui/mmv.ui.canvas.test.js +++ b/tests/qunit/mmv/ui/mmv.ui.canvas.test.js @@ -287,7 +287,7 @@ canvas.$image = $( '' ); - canvas.unblur(); + canvas.unblurWithAnimation(); assert.ok( ! canvas.$image.css( '-webkit-filter' ) || !canvas.$image.css( '-webkit-filter' ).length, 'Image has no filter left' ); diff --git a/tests/qunit/mmv/ui/mmv.ui.progressBar.test.js b/tests/qunit/mmv/ui/mmv.ui.progressBar.test.js index 2f44e73f8..de3eeec4e 100644 --- a/tests/qunit/mmv/ui/mmv.ui.progressBar.test.js +++ b/tests/qunit/mmv/ui/mmv.ui.progressBar.test.js @@ -24,7 +24,7 @@ assert.ok( progressBar.$progress.hasClass( 'empty' ), 'ProgressBar starts empty' ); } ); - QUnit.test( 'Progress bar', 11, function ( assert ) { + QUnit.test( 'animateTo()', 8, function ( assert ) { var $qf = $( '#qunit-fixture' ), progress = new mw.mmv.ui.ProgressBar( $qf ); @@ -35,17 +35,10 @@ $( this ).css( target ); assert.strictEqual( target.width, '50%', 'Animation should go to 50%' ); } ); - progress.percent( 50 ); + progress.animateTo( 50 ); assert.ok( !progress.$progress.hasClass( 'empty' ), 'Progress bar is visible' ); assert.strictEqual( progress.$percent.width(), $qf.width() / 2, 'Progress bar\'s indicator is at half' ); - $.fn.animate.restore(); - this.sandbox.stub( $.fn, 'animate' ); - progress.percent( 0 ); - assert.ok( !$.fn.animate.called, 'Going to 0% should not animate' ); - assert.ok( progress.$progress.hasClass( 'empty' ), 'Progress bar is hidden' ); - assert.strictEqual( progress.$percent.width(), 0, 'Progress bar\'s indicator is at 0' ); - $.fn.animate.restore(); this.sandbox.stub( $.fn, 'animate', function ( target, duration, transition, callback ) { $( this ).css( target ); @@ -56,7 +49,25 @@ callback(); } } ); - progress.percent( 100 ); + progress.animateTo( 100 ); + assert.ok( progress.$progress.hasClass( 'empty' ), 'Progress bar is hidden' ); + assert.strictEqual( progress.$percent.width(), 0, 'Progress bar\'s indicator is at 0' ); + } ); + + QUnit.test( 'jumpTo()/hide()', 6, function ( assert ) { + var $qf = $( '#qunit-fixture' ), + progress = new mw.mmv.ui.ProgressBar( $qf ); + + assert.ok( progress.$progress.hasClass( 'empty' ), 'Progress bar is hidden' ); + assert.strictEqual( progress.$percent.width(), 0, 'Progress bar\'s indicator is at 0' ); + + progress.jumpTo( 50 ); + + assert.ok( !progress.$progress.hasClass( 'empty' ), 'Progress bar is visible' ); + assert.strictEqual( progress.$percent.width(), $qf.width() / 2, 'Progress bar\'s indicator is at half' ); + + progress.hide(); + assert.ok( progress.$progress.hasClass( 'empty' ), 'Progress bar is hidden' ); assert.strictEqual( progress.$percent.width(), 0, 'Progress bar\'s indicator is at 0' ); } );