Refactor image load code, add unit tests.

The code to load and set the image was semi-duplicated and
scattered in various places. This was the source of defects
as this code is called asynchronously and was hard to debug.
This change attemps ot consolidate all the image load logic
in one place. It also adds sorely needed unit tests.

Change-Id: I92eb1c48e2ff0808134e56b4b150e22254eb2d6e
This commit is contained in:
Aaron Arcos 2014-01-28 15:57:05 -08:00 committed by Gilles Dubuc
parent 98e03d31c8
commit f7925a1a4f
2 changed files with 104 additions and 55 deletions

View file

@ -339,29 +339,9 @@
* @param {number} requestedWidth
*/
MMVP.loadResizedImage = function ( ui, imageData, targetWidth, requestedWidth ) {
var rpid, viewer, image, maybeThumb;
// Replace image only if data was returned.
if ( imageData ) {
viewer = this;
image = new Image();
image.onload = function () {
viewer.profileEnd( rpid );
};
rpid = this.profileStart( 'image-resize', {
width: imageData.width,
height: imageData.height,
fileSize: imageData.size
}, imageData.mimeType );
maybeThumb = imageData.getThumbUrl( requestedWidth );
image.src = maybeThumb || imageData.url;
if ( maybeThumb && requestedWidth > targetWidth || !maybeThumb && imageData.width > targetWidth ) {
image.width = targetWidth;
}
ui.replaceImageWith( image );
this.loadAndSetImage( ui, imageData, targetWidth, requestedWidth, 'image-resize' );
}
};
@ -597,6 +577,47 @@
);
};
/**
* @method
* Loads and sets the image specified in the imageData. It also updates the controls
* and collects profiling information.
*
* @param {mw.LightboxInterface} ui image container
* @param {mw.mmv.model.Image} imageData image information
* @param {number} targetWidth
* @param {number} requestedWidth
* @param {string} profileEvent profile event key
*/
MMVP.loadAndSetImage = function ( ui, imageData, targetWidth, requestedWidth, profileEvent ) {
var rpid,
maybeThumb,
viewer = this,
image = new Image();
image.onload = function () {
viewer.profileEnd( rpid );
};
rpid = this.profileStart( profileEvent, {
width: imageData.width,
height: imageData.height,
fileSize: imageData.size
}, imageData.mimeType );
// Use cached image if we have it.
maybeThumb = imageData.getThumbUrl( requestedWidth );
image.src = maybeThumb || imageData.url;
if ( maybeThumb && requestedWidth > targetWidth ||
!maybeThumb && imageData.width > targetWidth ) {
// Image bigger than the current area, resize before loading
image.width = targetWidth;
}
ui.replaceImageWith( image );
this.updateControls();
};
/**
* @method
* Loads a specified image.
@ -630,38 +651,19 @@
mdpid = this.profileStart( 'metadata-fetch' );
this.fetchImageInfo( image.filePageTitle ).done( function ( imageData, repoInfo, size, requestedWidth ) {
var pid,
repoData = mw.mmv.model.Repo.newFromRepoInfo( repoInfo[imageData.repo] ),
imageEle = new Image(),
targetWidth = size;
this.fetchImageInfo( image.filePageTitle ).done( function ( imageData, repoInfo, targetWidth, requestedWidth ) {
var repoData = mw.mmv.model.Repo.newFromRepoInfo( repoInfo[imageData.repo] );
viewer.profileEnd( mdpid );
viewer.stopListeningToScroll();
viewer.animateMetadataDivOnce()
// We need to wait until the animation is finished before we listen to scroll
.then( function() { viewer.startListeningToScroll(); } );
imageEle.onload = function () {
if ( imageEle.width > targetWidth ) {
imageEle.width = targetWidth;
}
viewer.profileEnd( pid );
viewer.updateControls();
};
viewer.profileEnd( mdpid );
pid = viewer.profileStart( 'image-load', {
width: imageData.width,
height: imageData.height,
fileSize: imageData.size
}, imageData.mimeType );
imageEle.src = imageData.getThumbUrl( requestedWidth ) || imageData.url;
viewer.loadAndSetImage( viewer.lightbox.iface, imageData, targetWidth, requestedWidth, 'image-load' );
viewer.lightbox.iface.$imageDiv.removeClass( 'empty' );
viewer.lightbox.iface.replaceImageWith( imageEle );
viewer.setImageInfo( image, imageData, repoData );
} );

View file

@ -461,25 +461,72 @@
ui.attach( '#qunit-fixture' );
// Fake viewport dimensions, width/height == 2.0, we are limited by height
ui.$imageWrapper.height(200);
ui.$imageWrapper.width(400);
ui.$imageWrapper.height( 200 );
ui.$imageWrapper.width( 400 );
widths = viewer.getImageSizeApiArgs(ui);
widths = viewer.getImageSizeApiArgs( ui );
assert.strictEqual(widths.target, 150/100*200, 'Correct target width was computed.');
assert.strictEqual(widths.requested, 320 * $.devicePixelRatio(), 'Correct requested width was computed.');
assert.strictEqual( widths.target, 150/100*200, 'Correct target width was computed.' );
assert.strictEqual( widths.requested, 320 * $.devicePixelRatio(), 'Correct requested width was computed.' );
// Fake viewport dimensions, width/height == 1.0, we are limited by width
ui.$imageWrapper.height(600);
ui.$imageWrapper.width(600);
ui.$imageWrapper.height( 600 );
ui.$imageWrapper.width( 600 );
widths = viewer.getImageSizeApiArgs(ui);
widths = viewer.getImageSizeApiArgs( ui );
assert.strictEqual(widths.target, 600, 'Correct target width was computed.');
assert.strictEqual(widths.requested, 640 * $.devicePixelRatio(), 'Correct requested width was computed.');
assert.strictEqual( widths.target, 600, 'Correct target width was computed.' );
assert.strictEqual( widths.requested, 640 * $.devicePixelRatio(), 'Correct requested width was computed.' );
ui.unattach();
} );
QUnit.asyncTest( 'loadAndSetImage(): Basic load', 9, function ( assert ) {
var targetWidth,
requestedWidth,
profileEvent,
pid = 4321,
viewer = new mw.MultimediaViewer(),
ui = new mw.LightboxInterface(),
size = 120,
width = 10,
height = 11,
imageUrl = 'http://en.wikipedia.org/w/skins/vector/images/search-ltr.png',
imageData = new mw.mmv.model.Image(
mw.Title.newFromText( 'File:Foobar.pdf.jpg' ),
size, width, height, 'image/jpeg',
imageUrl,
'http://example.com',
'example', 'tester', '2013-11-10', '2013-11-09', 'Blah blah blah',
'A person', 'Another person', 'CC-BY-SA-3.0', 0, 0
);
// Assert funtions are called with correct data
viewer.profileStart = function ( type, imgSize, fileType ) {
assert.strictEqual( type, profileEvent, 'Correct event type for profile start.' );
assert.strictEqual( imgSize.width, width, 'Correct width for profile start.' );
assert.strictEqual( imgSize.height, height, 'Correct height for profile start.' );
assert.strictEqual( imgSize.fileSize, size, 'Correct fileSize for profile start.' );
assert.strictEqual( fileType, 'image/jpeg', 'Correct fileType for profile start.' );
return pid;
};
viewer.profileEnd = function ( id ) {
assert.strictEqual( id, pid, 'Correct pid to end profiling. Image loaded correctly.' );
QUnit.start();
};
ui.replaceImageWith = function ( image ) {
assert.strictEqual( image.src, imageUrl, 'Image to replace has correct "src" attribute.' );
assert.strictEqual( image.width, targetWidth, 'Image to replace has correct "width" attribute.' );
};
viewer.updateControls = function () {
assert.ok( true, 'Controls updated.' );
};
// Test case when image loaded is bigger than current area
targetWidth = 8; // Current area < imageData.width
requestedWidth = 640;
profileEvent = 'image-load';
viewer.loadAndSetImage( ui, imageData, targetWidth, requestedWidth, profileEvent );
} );
}( mediaWiki, jQuery ) );