diff --git a/MultimediaViewer.php b/MultimediaViewer.php index f69e4aebb..e8f862836 100644 --- a/MultimediaViewer.php +++ b/MultimediaViewer.php @@ -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, diff --git a/resources/mmv/logging/mmv.logging.PerformanceLogger.js b/resources/mmv/logging/mmv.logging.PerformanceLogger.js index bfde46295..2ac6e1b5c 100644 --- a/resources/mmv/logging/mmv.logging.PerformanceLogger.js +++ b/resources/mmv/logging/mmv.logging.PerformanceLogger.js @@ -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.} [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.} [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.} 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; diff --git a/resources/mmv/mmv.js b/resources/mmv/mmv.js index 99ebb9207..00ed4af3b 100644 --- a/resources/mmv/mmv.js +++ b/resources/mmv/mmv.js @@ -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.} [extraStatsDeferred] Promise that resolves to the image's upload timestamp when the metadata is loaded * @returns {jQuery.Promise.} */ - 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.} [extraStatsDeferred] Promise that resolves to the image's upload timestamp when the metadata is loaded * @returns {jQuery.Promise.} A promise resolving to * a thumbnail model and an element. It might or might not have progress events which * return a single number. */ - MMVP.fetchThumbnail = function ( fileTitle, width, sampleUrl, originalWidth, originalHeight ) { + 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 ); } ); } ); } diff --git a/resources/mmv/mmv.lightboximage.js b/resources/mmv/mmv.lightboximage.js index 09167202c..ddb1418af 100644 --- a/resources/mmv/mmv.lightboximage.js +++ b/resources/mmv/mmv.lightboximage.js @@ -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.} - * @protected - */ - LIP.initialSrc = null; - mw.mmv.LightboxImage = LightboxImage; }( mediaWiki, jQuery ) ); diff --git a/resources/mmv/model/mmv.model.Image.js b/resources/mmv/model/mmv.model.Image.js index 59382ed92..c5c3b0630 100644 --- a/resources/mmv/model/mmv.model.Image.js +++ b/resources/mmv/model/mmv.model.Image.js @@ -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, diff --git a/resources/mmv/provider/mmv.provider.Image.js b/resources/mmv/provider/mmv.provider.Image.js index 2eccd43a5..fcd3ddf66 100644 --- a/resources/mmv/provider/mmv.provider.Image.js +++ b/resources/mmv/provider/mmv.provider.Image.js @@ -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.} extraStatsDeferred A promise which resolves to extra statistics. * @return {jQuery.Promise.} A promise which resolves to the image object. * When loaded via AJAX, it has progress events, which return an array with the content loaded * so far and with the progress as a floating-point number between 0 and 100. */ - Image.prototype.get = function ( url ) { + 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 ) { diff --git a/tests/qunit/mmv/logging/mmv.logging.PerformanceLogger.test.js b/tests/qunit/mmv/logging/mmv.logging.PerformanceLogger.test.js index be388794f..3d9745383 100644 --- a/tests/qunit/mmv/logging/mmv.logging.PerformanceLogger.test.js +++ b/tests/qunit/mmv/logging/mmv.logging.PerformanceLogger.test.js @@ -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', diff --git a/tests/qunit/mmv/mmv.EmbedFileFormatter.test.js b/tests/qunit/mmv/mmv.EmbedFileFormatter.test.js index 10d6acad1..b7875a0f8 100644 --- a/tests/qunit/mmv/mmv.EmbedFileFormatter.test.js +++ b/tests/qunit/mmv/mmv.EmbedFileFormatter.test.js @@ -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; } }; diff --git a/tests/qunit/mmv/mmv.test.js b/tests/qunit/mmv/mmv.test.js index 50b1e091e..28019f9ca 100644 --- a/tests/qunit/mmv/mmv.test.js +++ b/tests/qunit/mmv/mmv.test.js @@ -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; diff --git a/tests/qunit/mmv/model/mmv.model.Image.test.js b/tests/qunit/mmv/model/mmv.model.Image.test.js index 33fda9054..7686ffd06 100644 --- a/tests/qunit/mmv/model/mmv.model.Image.test.js +++ b/tests/qunit/mmv/model/mmv.model.Image.test.js @@ -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' ); diff --git a/tests/qunit/mmv/provider/mmv.provider.ImageInfo.test.js b/tests/qunit/mmv/provider/mmv.provider.ImageInfo.test.js index e9062b190..6dd3d74d2 100644 --- a/tests/qunit/mmv/provider/mmv.provider.ImageInfo.test.js +++ b/tests/qunit/mmv/provider/mmv.provider.ImageInfo.test.js @@ -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' );