Merge "Refactor progressbar & blur handling"

This commit is contained in:
jenkins-bot 2014-05-01 23:12:22 +00:00 committed by Gerrit Code Review
commit 9e0df0dd4c
7 changed files with 315 additions and 160 deletions

View file

@ -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.<string, string>}
*/
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.<string, number>}
*/
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.<mw.mmv.model.Thumbnail, HTMLImageElement>} 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.<mw.mmv.model.Thumbnail, HTMLImageElement>}
* @returns {jQuery.Promise.<mw.mmv.model.Thumbnail, HTMLImageElement>} A promise resolving to
* a thumbnail model and an <img> 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];
} );
};
/**

View file

@ -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.<HTMLImageElement>} a promise which resolves to the image object
* @return {jQuery.Promise.<HTMLImageElement>} 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];
};
/**

View file

@ -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

View file

@ -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 ) );

View file

@ -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 ) {

View file

@ -287,7 +287,7 @@
canvas.$image = $( '<img>' );
canvas.unblur();
canvas.unblurWithAnimation();
assert.ok( ! canvas.$image.css( '-webkit-filter' ) || !canvas.$image.css( '-webkit-filter' ).length,
'Image has no filter left' );

View file

@ -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' );
} );