mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/MultimediaViewer
synced 2024-11-24 16:23:49 +00:00
Merge "Track the most recent upload time for performance events"
This commit is contained in:
commit
96281f4192
|
@ -1063,7 +1063,7 @@ $wgResourceModules += array(
|
|||
$wgHooks['EventLoggingRegisterSchemas'][] = function( array &$schemas ) {
|
||||
$schemas += array(
|
||||
'MediaViewer' => 10536413,
|
||||
'MultimediaViewerNetworkPerformance' => 7917896,
|
||||
'MultimediaViewerNetworkPerformance' => 10596581,
|
||||
'MultimediaViewerDuration' => 10427980,
|
||||
'MultimediaViewerAttribution' => 9758179,
|
||||
'MultimediaViewerDimensions' => 10014238,
|
||||
|
|
|
@ -62,9 +62,10 @@
|
|||
* cached by the browser, as it will consume unnecessary bandwidth for the user.
|
||||
* @param {string} type the type of request to be measured
|
||||
* @param {string} url URL to be measured
|
||||
* @param {jQuery.Deferred.<string>} [extraStatsDeferred] A promise which resolves to the extra stats.
|
||||
* @returns {jQuery.Promise} A promise that resolves when the contents of the URL have been fetched
|
||||
*/
|
||||
PL.record = function ( type, url ) {
|
||||
PL.record = function ( type, url, extraStatsDeferred ) {
|
||||
var deferred = $.Deferred(),
|
||||
request,
|
||||
perf = this,
|
||||
|
@ -89,7 +90,7 @@
|
|||
if ( request.readyState === 4 ) {
|
||||
deferred.notify( request.response, 100 );
|
||||
deferred.resolve( request.response );
|
||||
perf.recordEntryDelayed( type, total, url, request );
|
||||
perf.recordEntryDelayed( type, total, url, request, extraStatsDeferred );
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -111,9 +112,11 @@
|
|||
* @param {number} total the total load time tracked with a basic technique
|
||||
* @param {string} url URL of that was measured
|
||||
* @param {XMLHttpRequest} request HTTP request that just completed
|
||||
* @param {jQuery.Deferred.<string>} [extraStatsDeferred] A promise which resolves to extra stats to be included.
|
||||
*/
|
||||
PL.recordEntry = function ( type, total, url, request ) {
|
||||
PL.recordEntry = function ( type, total, url, request, extraStatsDeferred ) {
|
||||
var matches,
|
||||
logger = this,
|
||||
stats = { type: type,
|
||||
contentHost: window.location.host,
|
||||
isHttps: window.location.protocol === 'https:',
|
||||
|
@ -165,7 +168,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
this.log( stats );
|
||||
( extraStatsDeferred || $.Deferred().reject() ).done( function ( extraStats ) {
|
||||
stats = $.extend( stats, extraStats );
|
||||
} ).always( function () {
|
||||
logger.log( stats );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -305,14 +312,15 @@
|
|||
* @param {number} total the total load time tracked with a basic technique
|
||||
* @param {string} url URL of that was measured
|
||||
* @param {XMLHttpRequest} request HTTP request that just completed
|
||||
* @param {jQuery.Promise.<string>} extraStatsDeferred A promise which resolves to extra stats.
|
||||
*/
|
||||
PL.recordEntryDelayed = function ( type, total, url, request ) {
|
||||
PL.recordEntryDelayed = function ( type, total, url, request, extraStatsDeferred ) {
|
||||
var perf = this;
|
||||
|
||||
// The timeout is necessary because if there's an entry in window.performance,
|
||||
// it hasn't been added yet at this point
|
||||
setTimeout( function() {
|
||||
perf.recordEntry( type, total, url, request );
|
||||
perf.recordEntry( type, total, url, request, extraStatsDeferred );
|
||||
}, 0 );
|
||||
};
|
||||
|
||||
|
@ -402,6 +410,15 @@
|
|||
return new XMLHttpRequest();
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @inheritdoc
|
||||
*/
|
||||
PL.log = function ( data ) {
|
||||
mw.log( 'mw.mmv.logging.PerformanceLogger', data );
|
||||
return mw.mmv.logging.Logger.prototype.log.call( this, data );
|
||||
};
|
||||
|
||||
new PerformanceLogger().init();
|
||||
|
||||
mw.mmv.logging.PerformanceLogger = PerformanceLogger;
|
||||
|
|
|
@ -157,6 +157,8 @@
|
|||
thumb.thumb,
|
||||
thumb.caption
|
||||
);
|
||||
|
||||
thumb.extraStatsDeferred = $.Deferred();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -249,9 +251,9 @@
|
|||
imagePromise,
|
||||
metadataPromise,
|
||||
start,
|
||||
uploadTimestamp,
|
||||
viewer = this,
|
||||
$initialImage = $( initialImage );
|
||||
$initialImage = $( initialImage ),
|
||||
extraStatsDeferred = $.Deferred();
|
||||
|
||||
this.currentIndex = image.index;
|
||||
|
||||
|
@ -282,7 +284,8 @@
|
|||
start = $.now();
|
||||
|
||||
mw.mmv.dimensionLogger.logDimensions( imageWidths, canvasDimensions, 'show' );
|
||||
imagePromise = this.fetchThumbnailForLightboxImage( image, imageWidths.real );
|
||||
|
||||
imagePromise = this.fetchThumbnailForLightboxImage( image, imageWidths.real, extraStatsDeferred );
|
||||
|
||||
this.resetBlurredThumbnailStates();
|
||||
if ( imagePromise.state() === 'pending' ) {
|
||||
|
@ -302,19 +305,12 @@
|
|||
mw.mmv.durationLogger.stop( 'click-to-first-image' );
|
||||
|
||||
metadataPromise.done( function ( imageInfo ) {
|
||||
if ( !imageInfo || !imageInfo.uploadDateTime ) {
|
||||
if ( !imageInfo || !imageInfo.anonymizedUploadDateTime ) {
|
||||
return;
|
||||
}
|
||||
|
||||
uploadTimestamp = imageInfo.uploadDateTime.toString();
|
||||
// Convert to "timestamp" format commonly used in EventLogging
|
||||
uploadTimestamp = uploadTimestamp.replace( /[:\s]/g, '' );
|
||||
// Anonymise the timestamp to avoid making the file identifiable
|
||||
// We only need to know the day
|
||||
uploadTimestamp = uploadTimestamp.substr( 0, uploadTimestamp.length - 6 ) + '000000';
|
||||
|
||||
mw.mmv.durationLogger.record( 'click-to-first-image', {
|
||||
uploadTimestamp: uploadTimestamp
|
||||
uploadTimestamp: imageInfo.anonymizedUploadDateTime
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
@ -324,6 +320,8 @@
|
|||
} );
|
||||
|
||||
metadataPromise.done( function ( imageInfo, repoInfo, userInfo ) {
|
||||
extraStatsDeferred.resolve( { uploadTimestamp: imageInfo.anonymizedUploadDateTime } );
|
||||
|
||||
if ( viewer.currentIndex !== image.index ) {
|
||||
return;
|
||||
}
|
||||
|
@ -337,6 +335,8 @@
|
|||
// File reuse steals a bunch of information from the DOM, so do it last
|
||||
viewer.ui.setFileReuseData( imageInfo, repoInfo, image.caption );
|
||||
} ).fail( function ( error ) {
|
||||
extraStatsDeferred.reject();
|
||||
|
||||
if ( viewer.currentIndex !== image.index ) {
|
||||
return;
|
||||
}
|
||||
|
@ -579,13 +579,15 @@
|
|||
if ( this.currentIndex + i < this.thumbs.length ) {
|
||||
callback(
|
||||
this.currentIndex + i,
|
||||
this.thumbs[ this.currentIndex + i ].image
|
||||
this.thumbs[ this.currentIndex + i ].image,
|
||||
this.thumbs[ this.currentIndex + i ].extraStatsDeferred
|
||||
);
|
||||
}
|
||||
if ( i && this.currentIndex - i >= 0 ) { // skip duplicate for i==0
|
||||
callback(
|
||||
this.currentIndex - i,
|
||||
this.thumbs[ this.currentIndex - i ].image
|
||||
this.thumbs[ this.currentIndex - i ].image,
|
||||
this.thumbs[ this.currentIndex - i ].extraStatsDeferred
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -601,8 +603,8 @@
|
|||
MMVP.pushLightboxImagesIntoQueue = function( taskFactory ) {
|
||||
var queue = new mw.mmv.model.TaskQueue();
|
||||
|
||||
this.eachPrealoadableLightboxIndex( function( i, lightboxImage ) {
|
||||
queue.push( taskFactory( lightboxImage ) );
|
||||
this.eachPrealoadableLightboxIndex( function( i, lightboxImage, extraStatsDeferred ) {
|
||||
queue.push( taskFactory( lightboxImage, extraStatsDeferred ) );
|
||||
} );
|
||||
|
||||
return queue;
|
||||
|
@ -636,9 +638,15 @@
|
|||
|
||||
this.cancelImageMetadataPreloading();
|
||||
|
||||
this.metadataPreloadQueue = this.pushLightboxImagesIntoQueue( function( lightboxImage ) {
|
||||
this.metadataPreloadQueue = this.pushLightboxImagesIntoQueue( function( lightboxImage, extraStatsDeferred ) {
|
||||
return function() {
|
||||
return viewer.fetchSizeIndependentLightboxInfo( lightboxImage.filePageTitle );
|
||||
var metadatapromise = viewer.fetchSizeIndependentLightboxInfo( lightboxImage.filePageTitle );
|
||||
metadatapromise.done( function ( imageInfo ) {
|
||||
extraStatsDeferred.resolve( { uploadTimestamp: imageInfo.anonymizedUploadDateTime } );
|
||||
} ).fail( function () {
|
||||
extraStatsDeferred.reject();
|
||||
} );
|
||||
return metadatapromise;
|
||||
};
|
||||
} );
|
||||
|
||||
|
@ -655,7 +663,7 @@
|
|||
|
||||
this.cancelThumbnailsPreloading();
|
||||
|
||||
this.thumbnailPreloadQueue = this.pushLightboxImagesIntoQueue( function( lightboxImage ) {
|
||||
this.thumbnailPreloadQueue = this.pushLightboxImagesIntoQueue( function( lightboxImage, extraStatsDeferred ) {
|
||||
return function() {
|
||||
var imageWidths, canvasDimensions;
|
||||
|
||||
|
@ -670,7 +678,7 @@
|
|||
|
||||
mw.mmv.dimensionLogger.logDimensions( imageWidths, canvasDimensions, 'preload' );
|
||||
|
||||
return viewer.fetchThumbnailForLightboxImage( lightboxImage, imageWidths.real );
|
||||
return viewer.fetchThumbnailForLightboxImage( lightboxImage, imageWidths.real, extraStatsDeferred );
|
||||
};
|
||||
} );
|
||||
|
||||
|
@ -721,15 +729,17 @@
|
|||
* Loads size-dependent components of a lightbox - the thumbnail model and the image itself.
|
||||
* @param {mw.mmv.LightboxImage} image
|
||||
* @param {number} width the width of the requested thumbnail
|
||||
* @param {jQuery.Deferred.<string>} [extraStatsDeferred] Promise that resolves to the image's upload timestamp when the metadata is loaded
|
||||
* @returns {jQuery.Promise.<mw.mmv.model.Thumbnail, HTMLImageElement>}
|
||||
*/
|
||||
MMVP.fetchThumbnailForLightboxImage = function ( image, width ) {
|
||||
MMVP.fetchThumbnailForLightboxImage = function ( image, width, extraStatsDeferred ) {
|
||||
return this.fetchThumbnail(
|
||||
image.filePageTitle,
|
||||
width,
|
||||
image.src,
|
||||
image.originalWidth,
|
||||
image.originalHeight
|
||||
image.originalHeight,
|
||||
extraStatsDeferred
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -740,11 +750,12 @@
|
|||
* @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)
|
||||
* @param {jQuery.Deferred.<string>} [extraStatsDeferred] Promise that resolves to the image's upload timestamp when the metadata is loaded
|
||||
* @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 ) {
|
||||
MMVP.fetchThumbnail = function ( fileTitle, width, sampleUrl, originalWidth, originalHeight, extraStatsDeferred ) {
|
||||
var viewer = this,
|
||||
guessing = false,
|
||||
thumbnailPromise,
|
||||
|
@ -771,7 +782,7 @@
|
|||
}
|
||||
|
||||
imagePromise = thumbnailPromise.then( function ( thumbnail ) {
|
||||
return viewer.imageProvider.get( thumbnail.url );
|
||||
return viewer.imageProvider.get( thumbnail.url, extraStatsDeferred );
|
||||
} );
|
||||
|
||||
if ( guessing ) {
|
||||
|
@ -780,7 +791,7 @@
|
|||
// because thumbnailInfoProvider.get is already called above when guessedThumbnailInfoProvider.get fails.
|
||||
imagePromise = imagePromise.then( null, function () {
|
||||
return viewer.thumbnailInfoProvider.get( fileTitle, width ).then( function ( thumbnail ) {
|
||||
return viewer.imageProvider.get( thumbnail.url );
|
||||
return viewer.imageProvider.get( thumbnail.url, extraStatsDeferred );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
|
|
@ -54,23 +54,5 @@
|
|||
this.originalHeight = undefined;
|
||||
}
|
||||
|
||||
var LIP = LightboxImage.prototype;
|
||||
|
||||
/**
|
||||
* The URL of the image (in the size we intend use to display the it in the lightbox)
|
||||
* @type {String}
|
||||
* @protected
|
||||
*/
|
||||
LIP.src = null;
|
||||
|
||||
/**
|
||||
* The URL of a placeholder while the image loads. Typically a smaller version of the image, which is already
|
||||
* loaded in the browser.
|
||||
* @type {String}
|
||||
* @return {jQuery.Promise.<mw.mmv.LightboxImage, HTMLImageElement>}
|
||||
* @protected
|
||||
*/
|
||||
LIP.initialSrc = null;
|
||||
|
||||
mw.mmv.LightboxImage = LightboxImage;
|
||||
}( mediaWiki, jQuery ) );
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
* @param {string} repo The repository this image belongs to
|
||||
* @param {string} lastUploader The last person to upload a version of this image.
|
||||
* @param {string} uploadDateTime The time and date the last upload occurred
|
||||
* @param {string} anonymizedUploadDateTime Anonymized and EL-friendly version of uploadDateTime
|
||||
* @param {string} creationDateTime The time and date the original upload occurred
|
||||
* @param {string} description
|
||||
* @param {string} source
|
||||
|
@ -55,6 +56,7 @@
|
|||
repo,
|
||||
lastUploader,
|
||||
uploadDateTime,
|
||||
anonymizedUploadDateTime,
|
||||
creationDateTime,
|
||||
description,
|
||||
source,
|
||||
|
@ -98,6 +100,9 @@
|
|||
/** @property {string} uploadDateTime The date and time of the last upload */
|
||||
this.uploadDateTime = uploadDateTime;
|
||||
|
||||
/** @property {string} anonymizedUploadDateTime The anonymized date and time of the last upload */
|
||||
this.anonymizedUploadDateTime = anonymizedUploadDateTime;
|
||||
|
||||
/** @property {string} creationDateTime The date and time that the image was created */
|
||||
this.creationDateTime = creationDateTime;
|
||||
|
||||
|
@ -144,7 +149,7 @@
|
|||
* @returns {mw.mmv.model.Image}
|
||||
*/
|
||||
Image.newFromImageInfo = function ( title, imageInfo ) {
|
||||
var name, uploadDateTime, creationDateTime, imageData,
|
||||
var name, uploadDateTime, anonymizedUploadDateTime, creationDateTime, imageData,
|
||||
description, source, author, authorCount, license, permission,
|
||||
latitude, longitude,
|
||||
innerInfo = imageInfo.imageinfo[0],
|
||||
|
@ -152,7 +157,15 @@
|
|||
|
||||
if ( extmeta ) {
|
||||
creationDateTime = this.parseExtmeta( extmeta.DateTimeOriginal, 'plaintext' );
|
||||
uploadDateTime = this.parseExtmeta( extmeta.DateTime, 'plaintext' );
|
||||
uploadDateTime = this.parseExtmeta( extmeta.DateTime, 'plaintext' ).toString();
|
||||
|
||||
// Convert to "timestamp" format commonly used in EventLogging
|
||||
anonymizedUploadDateTime = uploadDateTime.replace( /[^\d]/g, '' );
|
||||
|
||||
// Anonymise the timestamp to avoid making the file identifiable
|
||||
// We only need to know the day
|
||||
anonymizedUploadDateTime = anonymizedUploadDateTime.substr( 0, anonymizedUploadDateTime.length - 6 ) + '000000';
|
||||
|
||||
name = this.parseExtmeta( extmeta.ObjectName, 'plaintext' );
|
||||
|
||||
description = this.parseExtmeta( extmeta.ImageDescription, 'string' );
|
||||
|
@ -172,6 +185,7 @@
|
|||
name = title.getNameText();
|
||||
}
|
||||
|
||||
|
||||
imageData = new Image(
|
||||
title,
|
||||
name,
|
||||
|
@ -184,6 +198,7 @@
|
|||
imageInfo.imagerepository,
|
||||
innerInfo.user,
|
||||
uploadDateTime,
|
||||
anonymizedUploadDateTime,
|
||||
creationDateTime,
|
||||
description,
|
||||
source,
|
||||
|
|
|
@ -44,11 +44,12 @@
|
|||
* Loads an image and returns it. Includes performance metrics via mw.mmv.logging.PerformanceLogger.
|
||||
* When the browser supports it, the image is loaded as an AJAX request.
|
||||
* @param {string} url
|
||||
* @param {jQuery.Deferred.<string>} extraStatsDeferred A promise which resolves to extra statistics.
|
||||
* @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 ) {
|
||||
Image.prototype.get = function ( url, extraStatsDeferred ) {
|
||||
var provider = this,
|
||||
cacheKey = url,
|
||||
extraParam = {},
|
||||
|
@ -65,12 +66,12 @@
|
|||
if ( !this.cache[cacheKey] ) {
|
||||
if ( this.imagePreloadingSupported() ) {
|
||||
rawGet = $.proxy( provider.rawGet, provider, url, true );
|
||||
this.cache[cacheKey] = this.performance.record( 'image', url ).then( rawGet, rawGet );
|
||||
this.cache[cacheKey] = this.performance.record( 'image', url, extraStatsDeferred ).then( rawGet, rawGet );
|
||||
} else {
|
||||
start = $.now();
|
||||
this.cache[cacheKey] = this.rawGet( url );
|
||||
this.cache[cacheKey].always( function () {
|
||||
provider.performance.recordEntry( 'image', $.now() - start, url );
|
||||
provider.performance.recordEntry( 'image', $.now() - start, url, undefined, extraStatsDeferred );
|
||||
} );
|
||||
}
|
||||
this.cache[cacheKey].fail( function ( error ) {
|
||||
|
|
|
@ -186,6 +186,49 @@
|
|||
assert.strictEqual( fakeEventLog.logEvent.getCall( 0 ).args[ 1 ].bandwidth, Math.round( bandwidth ), 'bandwidth is correct' );
|
||||
} );
|
||||
|
||||
QUnit.test( 'recordEntry: with async extra stats', 11, function ( assert ) {
|
||||
var performance = new mw.mmv.logging.PerformanceLogger(),
|
||||
fakeEventLog = { logEvent: this.sandbox.stub() },
|
||||
type = 'gender',
|
||||
total = 100,
|
||||
overriddenType = 'image',
|
||||
foo = 'bar',
|
||||
extraStatsPromise = $.Deferred();
|
||||
|
||||
this.sandbox.stub( performance, 'loadDependencies' ).returns( $.Deferred().resolve() );
|
||||
this.sandbox.stub( performance, 'isInSample' );
|
||||
performance.setEventLog( fakeEventLog );
|
||||
|
||||
performance.isInSample.returns( true );
|
||||
|
||||
performance.recordEntry( type, total, 'URL1', undefined, extraStatsPromise );
|
||||
|
||||
assert.strictEqual( fakeEventLog.logEvent.callCount, 0, 'Stats should not be logged if the promise hasn\'t completed yet' );
|
||||
|
||||
extraStatsPromise.reject();
|
||||
|
||||
assert.strictEqual( fakeEventLog.logEvent.callCount, 1, 'Stats should be logged' );
|
||||
|
||||
assert.strictEqual( fakeEventLog.logEvent.getCall( 0 ).args[ 0 ], 'MultimediaViewerNetworkPerformance', 'EventLogging schema is correct' );
|
||||
assert.strictEqual( fakeEventLog.logEvent.getCall( 0 ).args[ 1 ].type, type, 'type is correct' );
|
||||
assert.strictEqual( fakeEventLog.logEvent.getCall( 0 ).args[ 1 ].total, total, 'total is correct' );
|
||||
|
||||
extraStatsPromise = $.Deferred();
|
||||
|
||||
performance.recordEntry( type, total, 'URL2', undefined, extraStatsPromise );
|
||||
|
||||
assert.strictEqual( fakeEventLog.logEvent.callCount, 1, 'Stats should not be logged if the promise hasn\'t been resolved yet' );
|
||||
|
||||
extraStatsPromise.resolve( { type: overriddenType, foo: foo } );
|
||||
|
||||
assert.strictEqual( fakeEventLog.logEvent.callCount, 2, 'Stats should be logged' );
|
||||
|
||||
assert.strictEqual( fakeEventLog.logEvent.getCall( 1 ).args[ 0 ], 'MultimediaViewerNetworkPerformance', 'EventLogging schema is correct' );
|
||||
assert.strictEqual( fakeEventLog.logEvent.getCall( 1 ).args[ 1 ].type, overriddenType, 'type is correct' );
|
||||
assert.strictEqual( fakeEventLog.logEvent.getCall( 1 ).args[ 1 ].total, total, 'total is correct' );
|
||||
assert.strictEqual( fakeEventLog.logEvent.getCall( 1 ).args[ 1 ].foo, foo, 'extra stat is correct' );
|
||||
} );
|
||||
|
||||
QUnit.test( 'parseVarnishXCacheHeader', 15, function ( assert ) {
|
||||
var varnish1 = 'cp1061',
|
||||
varnish2 = 'cp3006',
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
options.licenseInternalName, options.licenseLongName, options.licenseUrl ) : undefined,
|
||||
imageInfo = new mw.mmv.model.Image( options.title, options.title.getNameText(), undefined,
|
||||
undefined, undefined, undefined, options.imgUrl, options.filePageUrl, 'repo', undefined,
|
||||
undefined, undefined, undefined, options.source, options.author, options.authorCount, license ),
|
||||
undefined, undefined, undefined, undefined, options.source, options.author, options.authorCount, license ),
|
||||
repoInfo = { displayName: options.siteName, getSiteLink:
|
||||
function () { return options.siteUrl; } };
|
||||
|
||||
|
|
|
@ -102,21 +102,25 @@
|
|||
|
||||
QUnit.test( 'Progress', 4, function ( assert ) {
|
||||
var imageDeferred = $.Deferred(),
|
||||
viewer = new mw.mmv.MultimediaViewer( { get : $.noop } );
|
||||
viewer = new mw.mmv.MultimediaViewer( { get : $.noop } ),
|
||||
fakeImage = {
|
||||
filePageTitle: new mw.Title( 'File:Stuff.jpg' ),
|
||||
extraStatsDeferred: $.Deferred().reject()
|
||||
};
|
||||
|
||||
viewer.thumbs = [];
|
||||
viewer.displayPlaceholderThumbnail = $.noop;
|
||||
viewer.setImage = $.noop;
|
||||
viewer.scroll = $.noop;
|
||||
viewer.preloadFullscreenThumbnail = $.noop;
|
||||
viewer.fetchSizeIndependentLightboxInfo = function () { return $.Deferred().resolve(); };
|
||||
viewer.fetchSizeIndependentLightboxInfo = function () { return $.Deferred().resolve( {} ); };
|
||||
viewer.ui = {
|
||||
setFileReuseData: $.noop,
|
||||
setupForLoad: $.noop,
|
||||
canvas: { set : $.noop,
|
||||
canvas: { set: $.noop,
|
||||
unblurWithAnimation: $.noop,
|
||||
unblur: $.noop,
|
||||
getCurrentImageWidths: function () { return { real : 0 }; },
|
||||
getCurrentImageWidths: function () { return { real: 0 }; },
|
||||
getDimensions: function () { return {}; }
|
||||
},
|
||||
panel: {
|
||||
|
@ -132,10 +136,10 @@
|
|||
open: $.noop };
|
||||
|
||||
viewer.imageProvider.get = function() { return imageDeferred.promise(); };
|
||||
viewer.imageInfoProvider.get = function() { return $.Deferred().resolve(); };
|
||||
viewer.imageInfoProvider.get = function() { return $.Deferred().resolve( {} ); };
|
||||
viewer.thumbnailInfoProvider.get = function() { return $.Deferred().resolve( {} ); };
|
||||
|
||||
viewer.loadImage( { filePageTitle : new mw.Title( 'File:Stuff.jpg' ) }, new Image() );
|
||||
viewer.loadImage( fakeImage, new Image() );
|
||||
assert.ok( viewer.ui.panel.progressBar.jumpTo.lastCall.calledWith( 0 ),
|
||||
'Percentage correctly reset by loadImage' );
|
||||
|
||||
|
@ -156,8 +160,16 @@
|
|||
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' ) },
|
||||
firstImage = {
|
||||
index: 1,
|
||||
filePageTitle: new mw.Title( 'File:First.jpg' ),
|
||||
extraStatsDeferred: $.Deferred().reject()
|
||||
},
|
||||
secondImage = {
|
||||
index: 2,
|
||||
filePageTitle: new mw.Title( 'File:Second.jpg' ),
|
||||
extraStatsDeferred: $.Deferred().reject()
|
||||
},
|
||||
viewer = new mw.mmv.MultimediaViewer( { get : $.noop } );
|
||||
|
||||
viewer.thumbs = [];
|
||||
|
@ -167,7 +179,7 @@
|
|||
viewer.preloadFullscreenThumbnail = $.noop;
|
||||
viewer.preloadImagesMetadata = $.noop;
|
||||
viewer.preloadThumbnails = $.noop;
|
||||
viewer.fetchSizeIndependentLightboxInfo = function () { return $.Deferred().resolve(); };
|
||||
viewer.fetchSizeIndependentLightboxInfo = function () { return $.Deferred().resolve( {} ); };
|
||||
viewer.ui = {
|
||||
setFileReuseData: $.noop,
|
||||
setupForLoad : $.noop,
|
||||
|
@ -191,7 +203,7 @@
|
|||
open : $.noop,
|
||||
empty: $.noop };
|
||||
|
||||
viewer.imageInfoProvider.get = function() { return $.Deferred().resolve(); };
|
||||
viewer.imageInfoProvider.get = function() { return $.Deferred().resolve( {} ); };
|
||||
viewer.thumbnailInfoProvider.get = function() { return $.Deferred().resolve( {} ); };
|
||||
|
||||
// load some image
|
||||
|
@ -361,7 +373,17 @@
|
|||
firstImageDeferred = $.Deferred(),
|
||||
secondImageDeferred = $.Deferred(),
|
||||
firstLigthboxInfoDeferred = $.Deferred(),
|
||||
secondLigthboxInfoDeferred = $.Deferred();
|
||||
secondLigthboxInfoDeferred = $.Deferred(),
|
||||
firstImage = {
|
||||
filePageTitle: new mw.Title( 'File:Foo.jpg' ),
|
||||
index: 0,
|
||||
extraStatsDeferred: $.Deferred().reject()
|
||||
},
|
||||
secondImage = {
|
||||
filePageTitle: new mw.Title( 'File:Bar.jpg' ),
|
||||
index: 1,
|
||||
extraStatsDeferred: $.Deferred().reject()
|
||||
};
|
||||
|
||||
viewer.preloadFullscreenThumbnail = $.noop;
|
||||
viewer.fetchSizeIndependentLightboxInfo = this.sandbox.stub();
|
||||
|
@ -395,25 +417,25 @@
|
|||
|
||||
viewer.imageProvider.get.returns( firstImageDeferred.promise() );
|
||||
viewer.fetchSizeIndependentLightboxInfo.returns( firstLigthboxInfoDeferred.promise() );
|
||||
viewer.loadImage( { filePageTitle : new mw.Title( 'File:Foo.jpg' ), index : 0 }, new Image() );
|
||||
viewer.loadImage( firstImage, 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' );
|
||||
|
||||
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.loadImage( secondImage, new Image() );
|
||||
|
||||
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();
|
||||
firstLigthboxInfoDeferred.resolve( {} );
|
||||
assert.ok( !viewer.displayRealThumbnail.called, 'The first image being done loading should have no effect');
|
||||
|
||||
viewer.displayRealThumbnail = this.sandbox.spy( function () { viewer.close(); } );
|
||||
secondImageDeferred.resolve();
|
||||
secondLigthboxInfoDeferred.resolve();
|
||||
secondLigthboxInfoDeferred.resolve( {} );
|
||||
assert.ok( viewer.displayRealThumbnail.called, 'The second image being done loading should result in the image being shown');
|
||||
} );
|
||||
|
||||
|
@ -438,8 +460,9 @@
|
|||
viewer.preloadFullscreenThumbnail = $.noop;
|
||||
viewer.initWithThumbs( [] );
|
||||
|
||||
viewer.loadImage( { filePageTitle : new mw.Title( 'File:Stuff.jpg' ),
|
||||
thumbnail : new mw.mmv.model.Thumbnail( 'foo', 10, 10 ) },
|
||||
viewer.loadImage( { filePageTitle: new mw.Title( 'File:Stuff.jpg' ),
|
||||
thumbnail: new mw.mmv.model.Thumbnail( 'foo', 10, 10 ),
|
||||
extraStatsDeferred: $.Deferred().reject() },
|
||||
new Image() );
|
||||
|
||||
viewer.ui.$closeButton.click();
|
||||
|
@ -534,7 +557,7 @@
|
|||
assert.ok( !guessedThumbnailInfoStub.called, 'When we lack sample URL and original dimensions, GuessedThumbnailInfoProvider is not called' );
|
||||
assert.ok( thumbnailInfoStub.calledOnce, 'When we lack sample URL and original dimensions, ThumbnailInfoProvider is called once' );
|
||||
assert.ok( imageStub.calledOnce, 'When we lack sample URL and original dimensions, ImageProvider is called once' );
|
||||
assert.ok( imageStub.calledWithExactly( 'apiURL' ), 'When we lack sample URL and original dimensions, ImageProvider is called with the API url' );
|
||||
assert.ok( imageStub.calledWithExactly( 'apiURL', undefined ), 'When we lack sample URL and original dimensions, ImageProvider is called with the API url' );
|
||||
assert.strictEqual( promise.state(), 'resolved', 'When we lack sample URL and original dimensions, fetchThumbnail resolves' );
|
||||
|
||||
// When the guesser bails out, the classic provider should be used
|
||||
|
@ -546,7 +569,7 @@
|
|||
assert.ok( guessedThumbnailInfoStub.calledOnce, 'When the guesser bails out, GuessedThumbnailInfoProvider is called once' );
|
||||
assert.ok( thumbnailInfoStub.calledOnce, 'When the guesser bails out, ThumbnailInfoProvider is called once' );
|
||||
assert.ok( imageStub.calledOnce, 'When the guesser bails out, ImageProvider is called once' );
|
||||
assert.ok( imageStub.calledWithExactly( 'apiURL' ), 'When the guesser bails out, ImageProvider is called with the API url' );
|
||||
assert.ok( imageStub.calledWithExactly( 'apiURL', undefined ), 'When the guesser bails out, ImageProvider is called with the API url' );
|
||||
assert.strictEqual( promise.state(), 'resolved', 'When the guesser bails out, fetchThumbnail resolves' );
|
||||
|
||||
// When the guesser returns an URL, that should be used
|
||||
|
@ -558,7 +581,7 @@
|
|||
assert.ok( guessedThumbnailInfoStub.calledOnce, 'When the guesser returns an URL, GuessedThumbnailInfoProvider is called once' );
|
||||
assert.ok( !thumbnailInfoStub.called, 'When the guesser returns an URL, ThumbnailInfoProvider is not called' );
|
||||
assert.ok( imageStub.calledOnce, 'When the guesser returns an URL, ImageProvider is called once' );
|
||||
assert.ok( imageStub.calledWithExactly( 'guessedURL' ), 'When the guesser returns an URL, ImageProvider is called with the guessed url' );
|
||||
assert.ok( imageStub.calledWithExactly( 'guessedURL', undefined ), 'When the guesser returns an URL, ImageProvider is called with the guessed url' );
|
||||
assert.strictEqual( promise.state(), 'resolved', 'When the guesser returns an URL, fetchThumbnail resolves' );
|
||||
|
||||
// When the guesser returns an URL, but that returns 404, image loading should be retried with the classic provider
|
||||
|
@ -571,8 +594,8 @@
|
|||
assert.ok( guessedThumbnailInfoStub.calledOnce, 'When the guesser returns an URL, but that returns 404, GuessedThumbnailInfoProvider is called once' );
|
||||
assert.ok( thumbnailInfoStub.calledOnce, 'When the guesser returns an URL, but that returns 404, ThumbnailInfoProvider is called once' );
|
||||
assert.ok( imageStub.calledTwice, 'When the guesser returns an URL, but that returns 404, ImageProvider is called twice' );
|
||||
assert.ok( imageStub.getCall( 0 ).calledWithExactly( 'guessedURL' ), 'When the guesser returns an URL, but that returns 404, ImageProvider is called first with the guessed url' );
|
||||
assert.ok( imageStub.getCall( 1 ).calledWithExactly( 'apiURL' ), 'When the guesser returns an URL, but that returns 404, ImageProvider is called second with the guessed url' );
|
||||
assert.ok( imageStub.getCall( 0 ).calledWithExactly( 'guessedURL', undefined ), 'When the guesser returns an URL, but that returns 404, ImageProvider is called first with the guessed url' );
|
||||
assert.ok( imageStub.getCall( 1 ).calledWithExactly( 'apiURL', undefined ), 'When the guesser returns an URL, but that returns 404, ImageProvider is called second with the guessed url' );
|
||||
assert.strictEqual( promise.state(), 'resolved', 'When the guesser returns an URL, but that returns 404, fetchThumbnail resolves' );
|
||||
|
||||
// When even the retry fails, fetchThumbnail() should reject
|
||||
|
@ -585,8 +608,8 @@
|
|||
assert.ok( guessedThumbnailInfoStub.calledOnce, 'When even the retry fails, GuessedThumbnailInfoProvider is called once' );
|
||||
assert.ok( thumbnailInfoStub.calledOnce, 'When even the retry fails, ThumbnailInfoProvider is called once' );
|
||||
assert.ok( imageStub.calledTwice, 'When even the retry fails, ImageProvider is called twice' );
|
||||
assert.ok( imageStub.getCall( 0 ).calledWithExactly( 'guessedURL' ), 'When even the retry fails, ImageProvider is called first with the guessed url' );
|
||||
assert.ok( imageStub.getCall( 1 ).calledWithExactly( 'apiURL' ), 'When even the retry fails, ImageProvider is called second with the guessed url' );
|
||||
assert.ok( imageStub.getCall( 0 ).calledWithExactly( 'guessedURL', undefined ), 'When even the retry fails, ImageProvider is called first with the guessed url' );
|
||||
assert.ok( imageStub.getCall( 1 ).calledWithExactly( 'apiURL', undefined ), 'When even the retry fails, ImageProvider is called second with the guessed url' );
|
||||
assert.strictEqual( promise.state(), 'rejected', 'When even the retry fails, fetchThumbnail rejects' );
|
||||
|
||||
mw.config.get( 'wgMultimediaViewer' ).useThumbnailGuessing = false;
|
||||
|
@ -600,7 +623,7 @@
|
|||
assert.ok( !guessedThumbnailInfoStub.called, 'When guessing is disabled, GuessedThumbnailInfoProvider is not called' );
|
||||
assert.ok( thumbnailInfoStub.calledOnce, 'When guessing is disabled, ThumbnailInfoProvider is called once' );
|
||||
assert.ok( imageStub.calledOnce, 'When guessing is disabled, ImageProvider is called once' );
|
||||
assert.ok( imageStub.calledWithExactly( 'apiURL' ), 'When guessing is disabled, ImageProvider is called with the API url' );
|
||||
assert.ok( imageStub.calledWithExactly( 'apiURL', undefined ), 'When guessing is disabled, ImageProvider is called with the API url' );
|
||||
assert.strictEqual( promise.state(), 'resolved', 'When guessing is disabled, fetchThumbnail resolves' );
|
||||
|
||||
mw.config.get( 'wgMultimediaViewer' ).useThumbnailGuessing = oldUseThumbnailGuessing;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
( function( mw ) {
|
||||
QUnit.module( 'mmv.model.Image', QUnit.newMwEnvironment() );
|
||||
|
||||
QUnit.test( 'Image model constructor sanity check', 21, function ( assert ) {
|
||||
QUnit.test( 'Image model constructor sanity check', 22, function ( assert ) {
|
||||
var
|
||||
title = mw.Title.newFromText( 'File:Foobar.jpg' ),
|
||||
name = 'Foo bar',
|
||||
|
@ -31,6 +31,7 @@
|
|||
repo = 'wikimediacommons',
|
||||
user = 'Kaldari',
|
||||
datetime = '2011-07-04T23:31:14Z',
|
||||
anondatetime = '20110704000000',
|
||||
origdatetime = '2010-07-04T23:31:14Z',
|
||||
description = 'This is a test file.',
|
||||
source = 'WMF',
|
||||
|
@ -42,7 +43,7 @@
|
|||
longitude = 100.983829,
|
||||
imageData = new mw.mmv.model.Image(
|
||||
title, name, size, width, height, mime, url,
|
||||
descurl, repo, user, datetime, origdatetime,
|
||||
descurl, repo, user, datetime, anondatetime, origdatetime,
|
||||
description, source, author, authorCount, license, permission,
|
||||
latitude, longitude );
|
||||
|
||||
|
@ -57,6 +58,7 @@
|
|||
assert.strictEqual( imageData.repo, repo, 'Repository name is set correctly' );
|
||||
assert.strictEqual( imageData.lastUploader, user, 'Name of last uploader is set correctly' );
|
||||
assert.strictEqual( imageData.uploadDateTime, datetime, 'Date and time of last upload is set correctly' );
|
||||
assert.strictEqual( imageData.anonymizedUploadDateTime, anondatetime, 'Anonymized date and time of last upload is set correctly' );
|
||||
assert.strictEqual( imageData.creationDateTime, origdatetime, 'Date and time of original upload is set correctly' );
|
||||
assert.strictEqual( imageData.description, description, 'Description is set correctly' );
|
||||
assert.strictEqual( imageData.source, source, 'Source is set correctly' );
|
||||
|
@ -74,13 +76,13 @@
|
|||
firstImageData = new mw.mmv.model.Image(
|
||||
mw.Title.newFromText( 'File:Foobar.pdf.jpg' ), 'Foo bar',
|
||||
10, 10, 10, 'image/jpeg', 'http://example.org', 'http://example.com',
|
||||
'example', 'tester', '2013-11-10', '2013-11-09', 'Blah blah blah',
|
||||
'example', 'tester', '2013-11-10', '20131110', '2013-11-09', 'Blah blah blah',
|
||||
'A person', 'Another person', 1, 'CC-BY-SA-3.0', 'Permitted'
|
||||
),
|
||||
secondImageData = new mw.mmv.model.Image(
|
||||
mw.Title.newFromText( 'File:Foobar.pdf.jpg' ), 'Foo bar',
|
||||
10, 10, 10, 'image/jpeg', 'http://example.org', 'http://example.com',
|
||||
'example', 'tester', '2013-11-10', '2013-11-09', 'Blah blah blah',
|
||||
'example', 'tester', '2013-11-10', '20131110', '2013-11-09', 'Blah blah blah',
|
||||
'A person', 'Another person', 1, 'CC-BY-SA-3.0', 'Permitted',
|
||||
'39.91820938', '78.09812938'
|
||||
);
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
assert.ok( imageInfoProvider );
|
||||
} );
|
||||
|
||||
QUnit.asyncTest( 'ImageInfo get test', 26, function ( assert ) {
|
||||
QUnit.asyncTest( 'ImageInfo get test', 27, function ( assert ) {
|
||||
var apiCallCount = 0,
|
||||
api = { get: function() {
|
||||
apiCallCount++;
|
||||
|
@ -142,6 +142,7 @@
|
|||
assert.strictEqual( image.repo, 'shared', 'repo is set correctly' );
|
||||
assert.strictEqual( image.lastUploader, 'Dylanbot11', 'lastUploader is set correctly' );
|
||||
assert.strictEqual( image.uploadDateTime, '2013-08-25T14:41:02Z', 'uploadDateTime is set correctly' );
|
||||
assert.strictEqual( image.anonymizedUploadDateTime, '20130825000000', 'anonymizedUploadDateTime is set correctly' );
|
||||
assert.strictEqual( image.creationDateTime, '18 February 2009\u00a0(according to EXIF data)', 'creationDateTime is set correctly' );
|
||||
assert.strictEqual( image.description, 'Wikis stuff', 'description is set correctly' );
|
||||
assert.strictEqual( image.source, 'Wikipedia', 'source is set correctly' );
|
||||
|
|
Loading…
Reference in a new issue