Merge "Blurred thumbnail preview + progress bar"

This commit is contained in:
jenkins-bot 2014-02-25 21:37:38 +00:00 committed by Gerrit Code Review
commit 62ebfd450f
10 changed files with 598 additions and 35 deletions

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="gaussian-blur" x="0" y="0">
<feGaussianBlur color-interpolation-filters="sRGB" stdDeviation="3" />
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 353 B

View file

@ -188,7 +188,7 @@
*/
MMVP.setImage = function ( ui, thumbnail, image, imageWidths ) {
// we downscale larger images but do not scale up smaller ones, that would look ugly
if ( thumbnail.width > imageWidths.screen ) {
if ( thumbnail.width > imageWidths.css ) {
image.width = imageWidths.css;
}
@ -205,7 +205,9 @@
var imageWidths,
viewer = this,
imagePromise,
metadataPromise;
metadataPromise,
start,
$initialImage = $( initialImage );
// FIXME dependency injection happens in completely random order and location, needs cleanup
this.ui = this.lightbox.iface;
@ -223,7 +225,13 @@
this.lightbox.iface.empty();
}
this.lightbox.iface.setupForLoad();
this.lightbox.iface.showImage( image, initialImage );
// At this point we can't show the thumbnail because we don't
// know what size it should be. We still assign it to allow for
// size calculations in getCurrentImageWidths, which needs to know
// the aspect ratio
$initialImage.hide();
this.lightbox.iface.showImage( image, $initialImage );
this.preloadImagesMetadata();
this.preloadThumbnails();
@ -232,11 +240,23 @@
$( document.body ).addClass( 'mw-mlb-lightbox-open' );
imageWidths = this.ui.getCurrentImageWidths();
this.resetBlurredThumbnailStates();
start = $.now();
imagePromise = this.fetchThumbnail(
image.filePageTitle, imageWidths.real
).done( function( thumbnail, image ) {
viewer.setImage( viewer.lightbox.iface, thumbnail, image, imageWidths );
viewer.lightbox.iface.$imageDiv.removeClass( 'empty' );
).progress( function ( thumbnailInfoResponse, imageResponse ) {
if ( viewer.ui && viewer.ui.panel ) {
viewer.ui.panel.percent( imageResponse[ 1 ] );
}
} ).done( function ( thumbnail, image ) {
viewer.displayRealThumbnail( thumbnail, image, imageWidths, $.now() - start );
} );
this.imageInfoProvider.get( image.filePageTitle ).done( function ( imageInfo ) {
viewer.displayPlaceholderThumbnail( imageInfo, image, $initialImage, imageWidths );
} );
metadataPromise = this.fetchSizeIndependentLightboxInfo(
@ -278,6 +298,81 @@
} );
};
/**
* Resets the cross-request states needed to handle the blurred thumbnail logic
*/
MMVP.resetBlurredThumbnailStates = function () {
this.realThumbnailShown = false;
this.blurredThumbnailShown = false;
};
/**
* Display the real, full-resolution, thumbnail that was fetched with fetchThumbnail
* @param {mw.mmv.model.Thumbnail} thumbnail
* @param {HTMLImageElement} image
* @param {mw.mmv.model.ThumbnailWidth} imageWidths
* @param {number} loadTime Time it took to load the thumbnail
*/
MMVP.displayRealThumbnail = function ( thumbnail, image, imageWidths, loadTime ) {
this.realThumbnailShown = true;
this.setImage( this.lightbox.iface, thumbnail, image, imageWidths );
// We only animate unblur 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.lightbox.iface.unblur();
}
};
/**
* Display the blurred thumbnail from the page
* @param {mw.mmv.model.Image} imageInfo
* @param {HTMLImageElement} image
* @param {jQuery} $initialImage The thumbnail from the page
* @param {mw.mmv.model.ThumbnailWidth} imageWidths
*/
MMVP.displayPlaceholderThumbnail = function ( imageInfo, image, $initialImage, imageWidths ) {
var ratio,
targetWidth,
targetHeight,
blowupFactor;
// If the actual image has already been displayed, there's no point showing the blurry one
if ( this.realThumbnailShown ) {
return;
}
// If the image is smaller than the screen we need to adjust the placeholder's size
if ( imageInfo.width < imageWidths.css ) {
targetWidth = imageInfo.width;
targetHeight = imageInfo.height;
} else {
ratio = $initialImage.height() / $initialImage.width();
targetWidth = imageWidths.css;
targetHeight = Math.round( imageWidths.css * ratio );
}
blowupFactor = targetWidth / $initialImage.width();
// If the placeholder is too blown up, it's not worth showing it
if ( blowupFactor > 11 ) {
return;
}
$initialImage.width( targetWidth );
$initialImage.height( targetHeight );
// Only blur the placeholder if it's blown up significantly
if ( blowupFactor > 2 ) {
$initialImage.addClass( 'blurred' );
this.blurredThumbnailShown = true;
}
this.lightbox.iface.showImage( image, $initialImage.show() );
};
/**
* Preload this many prev/next images to speed up navigation.
* (E.g. preloadDistance = 3 means that the previous 3 and the next 3 images will be loaded.)
@ -502,6 +597,7 @@
imagePromise;
thumbnailPromise = this.thumbnailInfoProvider.get( fileTitle, width );
imagePromise = thumbnailPromise.then( function( thumbnail ) {
return viewer.imageProvider.get( thumbnail.url );
} );

View file

@ -21,6 +21,7 @@
@drag-height: 18px;
@bottom-height: (@title-height + @drag-height);
@metadata-background: rgb(251, 251, 251);
@progress-height: 3px;
.mlb-image-wrapper,
.mw-mlb-controls {
@ -307,6 +308,17 @@ body.mobile .mw-mlb-controls,
display: none;
}
.mlb-image img.blurred {
/**
* SVG is for Firefox
* Cannot be embedded because of the hash needed to access the filter inside the SVG
*/
filter: url(img/blur.svg#gaussian-blur);
filter: blur(3px);
-webkit-filter: blur(3px);
opacity: 0.8;
}
body.mw-mlb-lightbox-open {
overflow-y: auto;
}
@ -351,3 +363,20 @@ body.mw-mlb-lightbox-open #content {
.mlb-post-image:hover .mw-mlb-drag-icon {
opacity: 1;
}
.mw-mlb-progress {
width: 100%;
height: @progress-height;
background-color: rgb( 204, 204, 204 );
margin-top: -@progress-height;
}
.mw-mlb-progress.empty {
display: none;
}
.mw-mlb-progress-percent {
width: 0;
height: @progress-height;
background-color: rgb( 0, 113, 188 );
}

View file

@ -107,15 +107,6 @@
this.panel = new mw.mmv.ui.MetadataPanel( this.$postDiv, this.$controlBar );
this.buttons = new mw.mmv.ui.Buttons( this.$imageWrapper, this.$closeButton, this.$fullscreenButton );
this.initializeImage();
};
/**
* Initialize the image element.
*/
LIP.initializeImage = function () {
this.$imageDiv
.addClass( 'empty' );
};
/**
@ -126,7 +117,6 @@
this.panel.empty();
this.$imageDiv.addClass( 'empty' );
this.$imageDiv.empty();
if ( this.resizeListener ) {
@ -266,15 +256,17 @@
* Displays an already loaded image.
* This is an alternative to load() when we have an image element with the image already loaded.
* @param {mw.mmv.LightboxImage} image
* @param {HTMLImageElement } imageElement
* @param {jQuery} $imageElement
*/
LIP.showImage = function( image, imageElement ) {
LIP.showImage = function( image, $imageElement ) {
var iface = this;
this.currentImage = image;
image.globalMaxWidth = imageElement.width;
image.globalMaxHeight = imageElement.height;
this.$image = $( imageElement );
image.globalMaxWidth = $imageElement.width();
image.globalMaxHeight = $imageElement.height();
this.$image = $imageElement;
this.autoResizeImage();
@ -299,7 +291,7 @@
this.currentImage = image;
image.getImageElement().done( function( image, ele ) {
iface.showImage( image, ele );
iface.showImage( image, $( ele ) );
} );
};
@ -348,9 +340,8 @@
var $image = $( imageEle );
this.currentImage.src = imageEle.src;
this.$image.replaceWith( $image );
this.currentImage.src = imageEle.src;
this.$image = $image;
this.$image.css( {
@ -449,6 +440,36 @@
}
};
/**
* Animates the image into focus
*/
LIP.unblur = function () {
var self = this,
animationLength = 300;
// The blurred class has an opacity < 1. This animated the image to become fully opaque
this.$image
.addClass( 'blurred' )
.animate( { opacity: 1.0 }, animationLength );
// During the same amount of time (animationLength) we animate a blur value from 3.0 to 0.0
// We pass that value to an inline CSS Gaussian blur effect
$( { blur: 3.0 } ).animate( { blur: 0.0 }, {
duration: animationLength,
step: function ( step ) {
self.$image.css( { '-webkit-filter' : 'blur(' + step + 'px)',
'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' );
}
} );
};
/**
* Handle a fullscreen change event.
* @param {jQuery.Event} e The fullscreen change event.

View file

@ -57,10 +57,22 @@
try {
request = this.newXHR();
request.onprogress = function ( e ) {
var percent;
if ( e.lengthComputable ) {
percent = ( e.loaded / e.total ) * 100;
}
deferred.notify( request.response, percent );
};
request.onreadystatechange = function () {
var total = $.now() - start;
if ( request.readyState === 4 ) {
deferred.notify( request.response, 100 );
deferred.resolve( request.response );
perf.recordEntryDelayed( type, total, url, request );
}

View file

@ -77,6 +77,8 @@
this.$dragIcon.removeClass( 'pointing-down' );
this.$progress.addClass( 'empty' );
this.fileReuse.empty();
};
@ -90,6 +92,8 @@
MPP.initializeHeader = function () {
var panel = this;
this.initializeProgress();
this.$dragBar = $( '<div>' )
.addClass( 'mw-mlb-drag-affordance' )
.appendTo( this.$controlBar )
@ -322,6 +326,19 @@
.appendTo( this.$imageMetadata );
};
/**
* Initializes the progress display at the top of the panel.
*/
MPP.initializeProgress = function () {
this.$progress = $( '<div>' )
.addClass( 'mw-mlb-progress empty' )
.appendTo( this.$controlBar );
this.$percent = $( '<div>' )
.addClass( 'mw-mlb-progress-percent' )
.appendTo( this.$progress );
};
// *********************************
// ******** Setting methods ********
// *********************************
@ -716,5 +733,19 @@
}
};
/**
* Handles the progress display when a percentage of progress is received
* @param {number} percent
*/
MPP.percent = function ( percent ) {
if ( percent < 100 ) {
this.$percent.animate( { width : percent + '%' } );
this.$progress.removeClass( 'empty' );
} else {
this.$percent.css( { width : 0 } );
this.$progress.addClass( 'empty' );
}
};
mw.mmv.ui.MetadataPanel = MetadataPanel;
}( mediaWiki, jQuery, OO, moment ) );

View file

@ -62,9 +62,9 @@
lightbox.originalShowImage = lightbox.showImage;
// Mock showImage
lightbox.showImage = function ( image, ele ) {
lightbox.showImage = function ( image, $ele ) {
// Call original showImage
this.originalShowImage( image, ele );
this.originalShowImage( image, $ele );
// resizeListener should have been saved
assert.notStrictEqual( this.resizeListener, undefined, 'Saved listener !' );
@ -316,7 +316,8 @@
keydown.which = 38; // Up arrow
$document.trigger( keydown );
assert.strictEqual( Math.round( $.scrollTo().scrollTop() ), lightbox.panel.$imageMetadata.height() + 1,
assert.strictEqual( Math.round( $.scrollTo().scrollTop() ),
lightbox.panel.$imageMetadata.height() + 1,
'scrollTo scrollTop should be set to the metadata height + 1 after pressing up arrow' );
assert.ok( lightbox.panel.$dragIcon.hasClass( 'pointing-down' ),
'Chevron pointing down after pressing up arrow' );
@ -333,7 +334,8 @@
lightbox.panel.$dragIcon.click();
assert.strictEqual( Math.round( $.scrollTo().scrollTop() ), lightbox.panel.$imageMetadata.height() + 1,
assert.strictEqual( Math.round( $.scrollTo().scrollTop() ),
lightbox.panel.$imageMetadata.height() + 1,
'scrollTo scrollTop should be set to the metadata height + 1 after clicking the chevron once' );
assert.ok( lightbox.panel.$dragIcon.hasClass( 'pointing-down' ),
'Chevron pointing down after clicking the chevron once' );
@ -385,4 +387,44 @@
mw.mmv.mediaViewer.lightbox = oldMWLightbox;
mw.mmv.mediaViewer.ui = oldMWUI;
} );
QUnit.test( 'Unblur', 3, function ( assert ) {
var lightbox = new mw.mmv.LightboxInterface( mw.mediaViewer ),
oldAnimate = $.fn.animate;
$.fn.animate = function ( target, options ) {
var self = this,
lastValue;
$.each( target, function ( key, value ) {
lastValue = self.key = value;
} );
if ( options ) {
if ( options.step ) {
options.step.call( this, lastValue );
}
if ( options.complete ) {
options.complete.call( this );
}
}
};
// Attach lightbox to testing fixture to avoid interference with other tests.
lightbox.attach( '#qunit-fixture' );
lightbox.$image = $( '<img>' );
lightbox.unblur();
assert.ok( !lightbox.$image.css( '-webkit-filter' ) || !lightbox.$image.css( '-webkit-filter' ).length,
'Image has no filter left' );
assert.strictEqual( parseInt( lightbox.$image.css( 'opacity' ), 10 ), 1,
'Image is fully opaque' );
assert.ok( !lightbox.$image.hasClass( 'blurred' ), 'Image has no "blurred" class' );
lightbox.unattach();
$.fn.animate = oldAnimate;
} );
}( mediaWiki, jQuery ) );

View file

@ -152,4 +152,228 @@
window.location.hash = '';
} );
QUnit.test( 'Progress', 1, function ( assert ) {
var imageDeferred = $.Deferred(),
viewer = new mw.mmv.MultimediaViewer(),
oldImageGet = mw.mmv.provider.Image.prototype.get,
oldImageInfoGet = mw.mmv.provider.ImageInfo.prototype.get,
oldThumbnailInfoGet = mw.mmv.provider.ThumbnailInfo.prototype.get;
viewer.thumbs = [];
viewer.displayPlaceholderThumbnail = $.noop;
viewer.setImage = $.noop;
viewer.scroll = $.noop;
viewer.preloadFullscreenThumbnail = $.noop;
viewer.fetchSizeIndependentLightboxInfo = function () { return $.Deferred().resolve(); };
viewer.lightbox = { iface : {
setupForLoad : $.noop,
showImage : $.noop,
getCurrentImageWidths : function () { return { real : 0 }; },
panel : { setImageInfo : $.noop,
percent : function ( percent ) {
assert.strictEqual( percent, 45,
'Percentage correctly funneled to panel UI' );
} }
},
open : $.noop };
mw.mmv.provider.Image.prototype.get = function() { return imageDeferred.promise(); };
mw.mmv.provider.ImageInfo.prototype.get = function() { return $.Deferred().resolve(); };
mw.mmv.provider.ThumbnailInfo.prototype.get = function() { return $.Deferred().resolve( {} ); };
viewer.loadImage( { filePageTitle : new mw.Title( 'File:Stuff.jpg' ) }, new Image() );
imageDeferred.notify( 'response', 45 );
imageDeferred.resolve();
viewer.close();
mw.mmv.provider.Image.prototype.get = oldImageGet;
mw.mmv.provider.ImageInfo.prototype.get = oldImageInfoGet;
mw.mmv.provider.ThumbnailInfo.prototype.get = oldThumbnailInfoGet;
} );
QUnit.test( 'resetBlurredThumbnailStates', 4, function ( assert ) {
var viewer = new mw.mmv.MultimediaViewer();
assert.ok( !viewer.realThumbnailShown, 'Real thumbnail state is correct' );
assert.ok( !viewer.blurredThumbnailShown, 'Placeholder state is correct' );
viewer.realThumbnailShown = true;
viewer.blurredThumbnailShown = true;
viewer.resetBlurredThumbnailStates();
assert.ok( !viewer.realThumbnailShown, 'Real thumbnail state is correct' );
assert.ok( !viewer.blurredThumbnailShown, 'Placeholder state is correct' );
} );
QUnit.test( 'Placeholder first, then real thumbnail', 4, function ( assert ) {
var viewer = new mw.mmv.MultimediaViewer();
viewer.setImage = $.noop;
viewer.lightbox = { iface : {
showImage : $.noop
} };
viewer.displayPlaceholderThumbnail(
{ width : 300 },
undefined,
$( '<img>' ).width( 100 ),
{ css : 300, real : 300 }
);
assert.ok( viewer.blurredThumbnailShown, 'Placeholder state is correct' );
assert.ok( !viewer.realThumbnailShown, 'Real thumbnail state is correct' );
viewer.displayRealThumbnail();
assert.ok( viewer.realThumbnailShown, 'Real thumbnail state is correct' );
assert.ok( viewer.blurredThumbnailShown, 'Placeholder state is correct' );
} );
QUnit.test( 'Real thumbnail first, then placeholder', 4, function ( assert ) {
var viewer = new mw.mmv.MultimediaViewer();
viewer.setImage = $.noop;
viewer.lightbox = { iface : {
showImage : $.noop
} };
viewer.displayRealThumbnail();
assert.ok( viewer.realThumbnailShown, 'Real thumbnail state is correct' );
assert.ok( !viewer.blurredThumbnailShown, 'Placeholder state is correct' );
viewer.displayPlaceholderThumbnail(
{ width : 300 },
undefined,
$( '<img>' ).width( 100 ),
{ css : 300, real : 300 }
);
assert.ok( viewer.realThumbnailShown, 'Real thumbnail state is correct' );
assert.ok( !viewer.blurredThumbnailShown, 'Placeholder state is correct' );
} );
QUnit.test( 'displayRealThumbnail', 1, function ( assert ) {
var viewer = new mw.mmv.MultimediaViewer();
viewer.setImage = $.noop;
viewer.lightbox = { iface : {
unblur : function () { assert.ok( false, 'Image should not be unblurred yet' ); }
} };
viewer.blurredThumbnailShown = true;
// Should not result in an unblur (image cache from cache)
viewer.displayRealThumbnail( undefined, undefined, undefined, 5 );
viewer.lightbox.iface.unblur = function () {
assert.ok( true, 'Image needs to be unblurred' );
};
// Should result in an unblur (image didn't come from cache)
viewer.displayRealThumbnail( undefined, undefined, undefined, 1000 );
} );
QUnit.test( 'displayPlaceholderThumbnail: placeholder big enough that it doesn\'t need blurring, actual image bigger than the lightbox', 5, function ( assert ) {
var $image,
viewer = new mw.mmv.MultimediaViewer();
viewer.setImage = $.noop;
viewer.lightbox = { iface : {
showImage : function () { assert.ok ( true, 'Placeholder shown'); }
} };
$image = $( '<img>' ).width( 200 ).height( 100 );
viewer.displayPlaceholderThumbnail(
{ width : 1000, height : 500 },
undefined,
$image,
{ css : 300, real : 300 }
);
assert.strictEqual( $image.width(), 300, 'Placeholder has the right width' );
assert.strictEqual( $image.height(), 150, 'Placeholder has the right height' );
assert.ok( !$image.hasClass( 'blurred' ), 'Placeholder is not blurred' );
assert.ok( !viewer.blurredThumbnailShown, 'Placeholder state is correct' );
} );
QUnit.test( 'displayPlaceholderThumbnail: big-enough placeholder that needs blurring, actual image bigger than the lightbox', 5, function ( assert ) {
var $image,
viewer = new mw.mmv.MultimediaViewer();
viewer.setImage = $.noop;
viewer.lightbox = { iface : {
showImage : function () { assert.ok ( true, 'Placeholder shown'); }
} };
$image = $( '<img>' ).width( 100 ).height( 50 );
viewer.displayPlaceholderThumbnail(
{ width : 1000, height : 500 },
undefined,
$image,
{ css : 300, real : 300 }
);
assert.strictEqual( $image.width(), 300, 'Placeholder has the right width' );
assert.strictEqual( $image.height(), 150, 'Placeholder has the right height' );
assert.ok( $image.hasClass( 'blurred' ), 'Placeholder is blurred' );
assert.ok( viewer.blurredThumbnailShown, 'Placeholder state is correct' );
} );
QUnit.test( 'displayPlaceholderThumbnail: big-enough placeholder that needs blurring, actual image smaller than the lightbox', 5, function ( assert ) {
var $image,
viewer = new mw.mmv.MultimediaViewer(),
oldDevicePixelRatio = $.devicePixelRatio;
$.devicePixelRatio = function () { return 2; };
viewer.setImage = $.noop;
viewer.lightbox = { iface : {
showImage : function () { assert.ok ( true, 'Placeholder shown'); }
} };
$image = $( '<img>' ).width( 100 ).height( 50 );
viewer.displayPlaceholderThumbnail(
{ width : 1000, height : 500 },
undefined,
$image,
{ css : 1200, real : 1200 }
);
assert.strictEqual( $image.width(), 1000, 'Placeholder has the right width' );
assert.strictEqual( $image.height(), 500, 'Placeholder has the right height' );
assert.ok( $image.hasClass( 'blurred' ), 'Placeholder is blurred' );
assert.ok( viewer.blurredThumbnailShown, 'Placeholder state is correct' );
$.devicePixelRatio = oldDevicePixelRatio;
} );
QUnit.test( 'displayPlaceholderThumbnail: placeholder too small to be displayed, actual image bigger than the lightbox', 4, function ( assert ) {
var $image,
viewer = new mw.mmv.MultimediaViewer();
viewer.lightbox = { iface : {
showImage : function () { assert.ok ( false, 'Placeholder shown when it should not'); }
} };
$image = $( '<img>' ).width( 10 ).height( 5 );
viewer.displayPlaceholderThumbnail(
{ width : 1000, height : 500 },
undefined,
$image,
{ css : 300, real : 300 }
);
assert.strictEqual( $image.width(), 10, 'Placeholder has the right width' );
assert.strictEqual( $image.height(), 5, 'Placeholder has the right height' );
assert.ok( !$image.hasClass( 'blurred' ), 'Placeholder is not blurred' );
assert.ok( !viewer.blurredThumbnailShown, 'Placeholder state is correct' );
} );
}( mediaWiki, jQuery ) );

View file

@ -24,7 +24,7 @@
assert.ok( imageProvider );
} );
QUnit.test( 'Image load success test', 2, function ( assert ) {
QUnit.test( 'Image load success', 2, function ( assert ) {
var url = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0'
+ 'iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH'
+ '8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC',
@ -42,7 +42,7 @@
} );
} );
QUnit.test( 'Image caching test', 6, function ( assert ) {
QUnit.test( 'Image caching', 6, function ( assert ) {
var url = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0'
+ 'iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH'
+ '8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC',
@ -75,10 +75,78 @@
assert.strictEqual( image.src, url2, 'image src is correct');
QUnit.start();
} );
} );
QUnit.asyncTest( 'Image load fail test', 1, function ( assert ) {
QUnit.test( 'Image load XHR progress funneling', 7, function ( assert ) {
var i = 0,
imageProvider = new mw.mmv.provider.Image(),
oldPerformance = imageProvider.performance,
fakeURL = 'fakeURL',
response = 'response';
imageProvider.performance.delay = 0;
imageProvider.imagePreloadingSupported = function () { return true; };
imageProvider.rawGet = function () { return $.Deferred().resolve(); };
imageProvider.performance.newXHR = function () {
return { readyState: 4,
response: response,
send: function () {
var self = this;
// The timeout is necessary because without it notify() happens before
// the imageProvider has time to chain its progress() to the returned deferred
setTimeout( function () {
self.onprogress( { lengthComputable: true, loaded : 10, total : 20 } );
self.onreadystatechange();
} );
},
open: $.noop };
};
QUnit.stop();
imageProvider.performance.recordEntry = function ( type, total, url ) {
QUnit.start();
assert.strictEqual( type, 'image', 'Type matches' );
assert.strictEqual( url, fakeURL, 'URL matches' );
imageProvider.performance = oldPerformance;
return $.Deferred().resolve();
};
QUnit.stop();
imageProvider.get( fakeURL )
.fail( function () {
QUnit.start();
assert.ok( false, 'Image failed to (pretend to) load' );
} )
.then( function () {
QUnit.start();
assert.ok( true, 'Image was pretend-loaded' );
} )
.progress( function ( response, percent ) {
if ( i === 0 ) {
assert.strictEqual( percent, 50, 'Correctly propagated a 50% progress event' );
assert.strictEqual( response, response, 'Partial response propagated' );
} else if ( i === 1 ) {
assert.strictEqual( percent, 100, 'Correctly propagated a 100% progress event' );
assert.strictEqual( response, response, 'Partial response propagated' );
} else {
assert.ok( false, 'Only 2 progress events should propagate' );
}
i++;
} );
} );
QUnit.asyncTest( 'Image load fail', 1, function ( assert ) {
var imageProvider = new mw.mmv.provider.Image();
imageProvider.imagePreloadingSupported = function () { return false; };
@ -90,7 +158,7 @@
} );
} );
QUnit.test( 'Image load test with preloading supported', 1, function ( assert ) {
QUnit.test( 'Image load with preloading supported', 1, function ( assert ) {
var url = mw.config.get( 'wgScriptPath' ) + '/skins/vector/images/search-ltr.png',
imageProvider = new mw.mmv.provider.Image(),
endsWith = function ( a, b ) { return a.indexOf( b ) === a.length - b.length; };
@ -112,7 +180,7 @@
} );
} );
QUnit.test( 'Failed image load test with preloading supported', 1, function ( assert ) {
QUnit.test( 'Failed image load with preloading supported', 1, function ( assert ) {
var url = 'nosuchimage.png',
imageProvider = new mw.mmv.provider.Image();

View file

@ -15,7 +15,8 @@
'$usernameLi',
'$locationLi',
'$repoLi',
'$datetimeLi'
'$datetimeLi',
'$progress'
];
QUnit.module( 'mmv.ui.metadataPanel', QUnit.newMwEnvironment() );
@ -193,4 +194,34 @@
assert.strictEqual( result, date1, 'Invalid date is correctly ignored' );
} );
QUnit.test( 'Progress bar', 8, function ( assert ) {
var $qf = $( '#qunit-fixture' ),
panel = new mw.mmv.ui.MetadataPanel( $qf, $( '<div>' ).appendTo( $qf ) ),
oldAnimate = $.fn.animate;
$.fn.animate = function ( target ) {
$( this ).css( target );
};
assert.ok( panel.$progress.hasClass( 'empty' ), 'Progress bar is hidden' );
assert.strictEqual( panel.$percent.width(), 0, 'Progress bar\'s indicator is at 0' );
panel.percent( 0 );
assert.ok( !panel.$progress.hasClass( 'empty' ), 'Progress bar is visible' );
assert.strictEqual( panel.$percent.width(), 0, 'Progress bar\'s indicator is at 0' );
panel.percent( 50 );
assert.ok( !panel.$progress.hasClass( 'empty' ), 'Progress bar is visible' );
assert.strictEqual( panel.$percent.width(), $qf.width() / 2, 'Progress bar\'s indicator is at half' );
panel.percent( 100 );
assert.ok( panel.$progress.hasClass( 'empty' ), 'Progress bar is hidden' );
assert.strictEqual( panel.$percent.width(), 0, 'Progress bar\'s indicator is at 0' );
$.fn.animate = oldAnimate;
} );
}( mediaWiki, jQuery ) );