( function ( mw, $ ) { QUnit.module( 'mmv', QUnit.newMwEnvironment() ); QUnit.test( 'eachPrealoadableLightboxIndex()', 11, function ( assert ) { var viewer = new mw.mmv.MultimediaViewer(), expectedIndices, i; viewer.preloadDistance = 3; viewer.thumbs = []; // 0..10 for ( i = 0; i < 11; i++ ) { viewer.thumbs.push( { image : false } ); } viewer.currentIndex = 2; i = 0; expectedIndices = [2, 3, 1, 4, 0, 5]; viewer.eachPrealoadableLightboxIndex( function( index ) { assert.strictEqual( index, expectedIndices[i++], 'preload on left edge'); } ); viewer.currentIndex = 9; i = 0; expectedIndices = [9, 10, 8, 7, 6]; viewer.eachPrealoadableLightboxIndex( function( index ) { assert.strictEqual( index, expectedIndices[i++], 'preload on right edge'); } ); } ); QUnit.test( 'Hash handling', 7, function ( assert ) { var oldUnattach, viewer = new mw.mmv.MultimediaViewer(), ui = new mw.mmv.LightboxInterface(), imageSrc = 'Foo bar.jpg', image = { filePageTitle: new mw.Title( 'File:' + imageSrc ) }; window.location.hash = ''; viewer.setupEventHandlers(); oldUnattach = ui.unattach; ui.unattach = function () { assert.ok( true, 'Lightbox was unattached' ); oldUnattach.call( this ); }; viewer.ui = ui; viewer.close(); assert.ok( !viewer.isOpen, 'Viewer is closed' ); viewer.isOpen = true; // Verify that passing an invalid mmv hash when the mmv is open triggers unattach() window.location.hash = 'Foo'; viewer.hash(); // Verify that mmv doesn't reset a foreign hash assert.strictEqual( window.location.hash, '#Foo', 'Foreign hash remains intact' ); assert.ok( !viewer.isOpen, 'Viewer is closed' ); ui.unattach = function () { assert.ok( false, 'Lightbox was not unattached' ); oldUnattach.call( this ); }; // Verify that passing an invalid mmv hash when the mmv is closed doesn't trigger unattach() window.location.hash = 'Bar'; viewer.hash(); // Verify that mmv doesn't reset a foreign hash assert.strictEqual( window.location.hash, '#Bar', 'Foreign hash remains intact' ); viewer.ui = { images: [ image ] }; $( '#qunit-fixture' ).append( '' ); viewer.loadImageByTitle = function( title ) { assert.strictEqual( title.getPrefixedText(), 'File:' + imageSrc, 'The title matches' ); }; // Open a valid mmv hash link and check that the right image is requested. // imageSrc contains a space without any encoding on purpose window.location.hash = 'mediaviewer/File:' + imageSrc; viewer.hash(); // Reset the hash, because for some browsers switching from the non-URI-encoded to // the non-URI-encoded version of the same text with a space will not trigger a hash change window.location.hash = ''; viewer.hash(); // Try again with an URI-encoded imageSrc containing a space window.location.hash = 'mediaviewer/File:' + encodeURIComponent( imageSrc ); viewer.hash(); viewer.cleanupEventHandlers(); window.location.hash = ''; } ); QUnit.test( 'Progress', 4, function ( assert ) { var imageDeferred = $.Deferred(), viewer = new mw.mmv.MultimediaViewer(), i = 0; viewer.thumbs = []; viewer.displayPlaceholderThumbnail = $.noop; viewer.setImage = $.noop; viewer.scroll = $.noop; viewer.preloadFullscreenThumbnail = $.noop; viewer.fetchSizeIndependentLightboxInfo = function () { return $.Deferred().resolve(); }; viewer.ui = { setupForLoad : $.noop, canvas : { set : $.noop, getCurrentImageWidths : function () { return { real : 0 }; } }, panel : { setImageInfo : $.noop, animateMetadataOnce : $.noop, progressBar: { percent : function ( percent ) { if ( i === 0 ) { assert.strictEqual( percent, 0, 'Percentage correctly reset by loadImage' ); } else if ( i === 1 ) { assert.strictEqual( percent, 5, 'Percentage correctly animated to 5 by loadImage' ); } else if ( i === 2 ) { assert.strictEqual( percent, 45, 'Percentage correctly funneled to panel UI' ); } else { assert.strictEqual( percent, 100, 'Percentage correctly funneled to panel UI' ); } i++; } } }, open : $.noop }; viewer.imageProvider.get = function() { return imageDeferred.promise(); }; 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() ); imageDeferred.notify( 'response', 45 ); imageDeferred.resolve(); viewer.close(); } ); 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.ui = { canvas : { maybeDisplayPlaceholder : function() { return true; } } }; viewer.imageInfoProvider.get = this.sandbox.stub(); viewer.displayPlaceholderThumbnail( { originalWidth: 100, originalHeight: 100 }, undefined, undefined); 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( 'Placeholder first, then real thumbnail - missing size', 4, function ( assert ) { var viewer = new mw.mmv.MultimediaViewer(); viewer.currentIndex = 1; viewer.setImage = $.noop; viewer.ui = { canvas : { maybeDisplayPlaceholder : function() { return true; } } }; viewer.imageInfoProvider.get = this.sandbox.stub().returns( $.Deferred().resolve( {width: 100, height: 100 } ) ); viewer.displayPlaceholderThumbnail( { index: 1 }, undefined, undefined); 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.ui = { showImage : $.noop }; viewer.displayRealThumbnail(); assert.ok( viewer.realThumbnailShown, 'Real thumbnail state is correct' ); assert.ok( !viewer.blurredThumbnailShown, 'Placeholder state is correct' ); viewer.displayPlaceholderThumbnail( {}, undefined, undefined); 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.ui = { canvas : { 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.ui.canvas.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( 'New image loaded while another one is loading', 1, function ( assert ) { var imageDeferred, ligthboxInfoDeferred, viewer = new mw.mmv.MultimediaViewer(), firstImageDeferred = $.Deferred(), secondImageDeferred = $.Deferred(), firstLigthboxInfoDeferred = $.Deferred(), secondLigthboxInfoDeferred = $.Deferred(); viewer.preloadFullscreenThumbnail = $.noop; viewer.fetchSizeIndependentLightboxInfo = function () { return ligthboxInfoDeferred.promise(); }; viewer.ui = { setupForLoad : $.noop, canvas : { set : $.noop, getCurrentImageWidths : function () { return { real : 0 }; } }, panel : { setImageInfo : function () { assert.ok( false, 'Metadata of the first image should not be shown' ); }, progressBar: { percent : function ( response, percent ) { if ( percent === 45 ) { assert.ok( false, 'Progress of the first image should not be shown' ); } } }, empty: $.noop, animateMetadataOnce: $.noop }, open : $.noop, empty: $.noop }; viewer.eachPrealoadableLightboxIndex = $.noop; viewer.animateMetadataDivOnce = function () { assert.ok( false, 'Metadata of the first image should not be animated' ); return $.Deferred().reject(); }; viewer.imageProvider.get = function() { return imageDeferred.promise(); }; viewer.imageInfoProvider.get = function() { return $.Deferred().reject(); }; viewer.thumbnailInfoProvider.get = function() { return $.Deferred().resolve( {} ); }; imageDeferred = firstImageDeferred; ligthboxInfoDeferred = firstLigthboxInfoDeferred; viewer.loadImage( { filePageTitle : new mw.Title( 'File:Foo.jpg' ), index : 0 }, new Image() ); imageDeferred = secondImageDeferred; ligthboxInfoDeferred = secondLigthboxInfoDeferred; viewer.loadImage( { filePageTitle : new mw.Title( 'File:Bar.jpg' ), index : 1 }, new Image() ); viewer.displayRealThumbnail = function () { assert.ok( false, 'The first image being done loading should have no effect'); }; firstImageDeferred.notify( undefined, 45 ); firstImageDeferred.resolve(); firstLigthboxInfoDeferred.resolve(); viewer.ui.panel.setImageInfo = $.noop; viewer.animateMetadataDivOnce = function() { return $.Deferred().reject(); }; viewer.displayRealThumbnail = function () { assert.ok( true, 'The second image being done loading should result in the image being shown'); QUnit.start(); viewer.close(); }; QUnit.stop(); secondImageDeferred.resolve(); secondLigthboxInfoDeferred.resolve(); } ); QUnit.test( 'Events are not trapped after the viewer is closed', 0, function( assert ) { var i, j, k, eventParameters, viewer = new mw.mmv.MultimediaViewer(), $document = $( document ), $qf = $( '#qunit-fixture' ), eventTypes = [ 'keydown', 'keyup', 'keypress', 'click', 'mousedown', 'mouseup' ], modifiers = [ undefined, 'altKey', 'ctrlKey', 'shiftKey', 'metaKey' ], oldScrollTo = $.scrollTo; $.scrollTo = function () { return { scrollTop : $.noop, on : $.noop, off : $.noop }; }; viewer.setupEventHandlers(); viewer.imageProvider.get = function() { return $.Deferred().reject(); }; viewer.imageInfoProvider.get = function() { return $.Deferred().reject(); }; viewer.thumbnailInfoProvider.get = function() { return $.Deferred().reject(); }; viewer.imageUsageProvider.get = function() { return $.Deferred().reject(); }; viewer.fileRepoInfoProvider.get = function() { return $.Deferred().reject(); }; viewer.preloadFullscreenThumbnail = $.noop; viewer.initWithThumbs( [] ); viewer.loadImage( { filePageTitle : new mw.Title( 'File:Stuff.jpg' ), thumbnail : new mw.mmv.model.Thumbnail( 'foo', 10, 10 ) }, new Image() ); viewer.ui.$closeButton.click(); function eventHandler ( e ) { if ( e.isDefaultPrevented() ) { assert.ok( false, 'Event was incorrectly trapped : ' + e.which ); } e.preventDefault(); // Wait for the last event if ( e.which === 32 && e.type === 'mouseup' ) { QUnit.start(); $document.off( '.mmvtest' ); viewer.cleanupEventHandlers(); $.scrollTo = oldScrollTo; } } // Events are async, we need to wait for the last event to be caught before ending the test QUnit.stop(); for ( j = 0; j < eventTypes.length; j++ ) { $document.on( eventTypes[ j ] + '.mmvtest', eventHandler ); eventloop: for ( i = 0; i < 256; i++ ) { // Save some time by not testing unlikely values for mouse events if ( i > 32 ) { switch ( eventTypes[ j ] ) { case 'click': case 'mousedown': case 'mouseup': break eventloop; } } for ( k = 0; k < modifiers.length; k++ ) { eventParameters = { which : i }; if ( modifiers[ k ] !== undefined ) { eventParameters[ modifiers[ k ] ] = true; } $qf.trigger( $.Event( eventTypes[ j ], eventParameters ) ); } } } } ); QUnit.test( 'Refuse to load too-big thumbnails', 1, function ( assert ) { var viewer = new mw.mmv.MultimediaViewer(), intendedWidth = 50, title = mw.Title.newFromText( 'File:Foobar.svg' ); viewer.thumbnailInfoProvider.get = function ( fileTitle, width ) { assert.strictEqual( width, intendedWidth ); return $.Deferred().reject(); }; viewer.fetchThumbnail( title, 1000, null, intendedWidth, 60 ); } ); QUnit.test( 'fetchThumbnail()', 14, function ( assert ) { var viewer = new mw.mmv.MultimediaViewer(), oldUseThumbnailGuessing = mw.config.get( 'wgMultimediaViewer' ).useThumbnailGuessing, file = new mw.Title( 'File:Copyleft.svg' ), sampleURL = 'http://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Copyleft.svg/300px-Copyleft.svg.png', width = 100, originalWidth = 1000, originalHeight = 1000, apiURL = 'foo', guessedURL = 'bar'; mw.config.get( 'wgMultimediaViewer' ).useThumbnailGuessing = true; viewer.guessedThumbnailInfoProvider = { get: function() { assert.ok( false, 'Guessed thumbnail provider should not be called, because we lack sample URL and original dimensions' ); } }; viewer.thumbnailInfoProvider = { get: function() { assert.ok( true, 'Classic thumbnail info provider should be called, because we lack sample URL and original dimensions' ); return $.Deferred().reject(); } }; viewer.fetchThumbnail( file, width ); viewer.guessedThumbnailInfoProvider = { get: function() { assert.ok( true, 'Guessed thumbnail provider should be called' ); return $.Deferred().reject(); } }; viewer.thumbnailInfoProvider = { get: function() { assert.ok( true, 'API thumbnail info provider should be called as a fallback, since we could not guess the URL' ); return $.Deferred().reject(); } }; viewer.fetchThumbnail( file, width, sampleURL, originalWidth, originalHeight ); viewer.thumbnailInfoProvider = { get: function() { assert.ok( true, 'API thumbnail info provider should be called as a fallback, since we could not guess the URL' ); return $.Deferred().resolve( { url : apiURL } ); } }; viewer.imageProvider = { get: function( url ) { assert.strictEqual( url, apiURL, 'Image provider is called based on data provided by the API provider' ); return $.Deferred().resolve(); } }; viewer.fetchThumbnail( file, width, sampleURL, originalWidth, originalHeight ); viewer.guessedThumbnailInfoProvider = { get: function() { assert.ok( true, 'Guessed thumbnail provider should be called' ); return $.Deferred().resolve( { url : guessedURL } ); } }; viewer.imageProvider = { get: function( url ) { assert.strictEqual( url, guessedURL, 'Image provider is called based on data provided by the guessed provider' ); return $.Deferred().reject(); } }; viewer.thumbnailInfoProvider = { get: function() { assert.ok( true, 'API thumbnail info provider should be called as a fallback, since the URL we hit did not exist' ); return $.Deferred().reject(); } }; viewer.fetchThumbnail( file, width, sampleURL, originalWidth, originalHeight ); viewer.imageProvider = { get: function( url ) { assert.strictEqual( url, guessedURL, 'Image provider is called based on data provided by the guessed provider' ); return $.Deferred().resolve(); } }; viewer.thumbnailInfoProvider = { get: function() { assert.ok( false, 'API thumbnail info provider should not be called as a fallback' ); return $.Deferred().reject(); } }; QUnit.stop(); viewer.fetchThumbnail( file, width, sampleURL, originalWidth, originalHeight ).then( function() { assert.ok( true, 'Guessed URL is all that was needed to load the thumb' ); QUnit.start(); } ); mw.config.get( 'wgMultimediaViewer' ).useThumbnailGuessing = false; viewer.guessedThumbnailInfoProvider = { get: function() { assert.ok( false, 'Guessed thumbnail provider should not be called, since it was disabled in extension configuration' ); } }; viewer.thumbnailInfoProvider = { get: function() { assert.ok( true, 'Classic thumbnail info provider should be called, since guesser was disabled in extension configuration' ); return $.Deferred().reject(); } }; viewer.fetchThumbnail( file, width ); mw.config.get( 'wgMultimediaViewer' ).useThumbnailGuessing = oldUseThumbnailGuessing; } ); }( mediaWiki, jQuery ) );