mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/MultimediaViewer
synced 2024-12-02 19:56:17 +00:00
4b6e44a2fc
Due to a jQuery bug, errors in local code (gadgets, user scripts) can cause onready handlers to not be executed. For MMV this causes catastrophic failure, with a black screen of death on exit. This change makes sure that the setup code necessary for Media Viewer to work is executed at latest when MV is invoked, even if some onready handlers were skipped. Opening MediaViewer via a hash-URL will still not work if the onready handler fails, but that's hard to avoid and it is not a catastrophic failure anymore. This change can be reverted when bug 70772 gets fixed. Bug: 70756 Change-Id: Ida3b780791bc9dfec29303567d33e3aa4f44dd81 Mingle: https://wikimedia.mingle.thoughtworks.com/projects/multimedia/cards/891
461 lines
15 KiB
JavaScript
461 lines
15 KiB
JavaScript
( function ( mw, $ ) {
|
|
QUnit.module( 'mmv.bootstrap', QUnit.newMwEnvironment( {
|
|
setup: function () {
|
|
mw.config.set( 'wgMediaViewer', true );
|
|
mw.config.set( 'wgMediaViewerOnClick', true );
|
|
this.sandbox.stub( mw.user, 'isAnon').returns( false );
|
|
this.clock = this.sandbox.useFakeTimers();
|
|
}
|
|
} ) );
|
|
|
|
function createGallery( imageSrc, caption ) {
|
|
var div = $( '<div>' ).addClass( 'gallery' ).appendTo( '#qunit-fixture' ),
|
|
galleryBox = $( '<div>' ).addClass( 'gallerybox' ).appendTo( div ),
|
|
thumbwrap = $( '<div>' ).addClass( 'thumb' ).appendTo( galleryBox ),
|
|
link = $( '<a>' ).addClass( 'image' ).appendTo( thumbwrap );
|
|
|
|
$( '<img>' ).attr( 'src', ( imageSrc || 'thumb.jpg' ) ).appendTo( link );
|
|
$( '<div>' ).addClass( 'gallerytext' ).text( caption || 'Foobar' ).appendTo( galleryBox );
|
|
|
|
return div;
|
|
}
|
|
|
|
function createThumb( imageSrc, caption ) {
|
|
var div = $( '<div>' ).addClass( 'thumb' ).appendTo( '#qunit-fixture' ),
|
|
link = $( '<a>' ).addClass( 'image' ).appendTo( div );
|
|
|
|
$( '<div>' ).addClass( 'thumbcaption' ).appendTo( div ).text( caption );
|
|
$( '<img>' ).attr( 'src', ( imageSrc || 'thumb.jpg' ) ).appendTo( link );
|
|
|
|
return div;
|
|
}
|
|
|
|
function createNormal( imageSrc, caption ) {
|
|
var link = $( '<a>' ).prop( 'title', caption ).addClass( 'image' ).appendTo( '#qunit-fixture' );
|
|
$( '<img>' ).prop( 'src', ( imageSrc || 'thumb.jpg' ) ).appendTo( link );
|
|
return link;
|
|
}
|
|
|
|
function createBootstrap( viewer ) {
|
|
var bootstrap = new mw.mmv.MultimediaViewerBootstrap();
|
|
|
|
// MultimediaViewerBootstrap.ensureEventHandlersAreSetUp() is a weird workaround for gadget bugs.
|
|
// MediaViewer should work without it, and so should the tests.
|
|
bootstrap.ensureEventHandlersAreSetUp = $.noop;
|
|
|
|
bootstrap.getViewer = function() { return viewer ? viewer : { initWithThumbs : $.noop }; };
|
|
|
|
return bootstrap;
|
|
}
|
|
|
|
function hashTest( bootstrap, assert ) {
|
|
var hash = 'mediaviewer/foo';
|
|
|
|
bootstrap.setupEventHandlers();
|
|
|
|
bootstrap.loadViewer = function () {
|
|
assert.ok( false, 'Viewer should not be loaded' );
|
|
return $.Deferred().reject();
|
|
};
|
|
|
|
window.location.hash = 'Foo';
|
|
|
|
bootstrap.loadViewer = function () {
|
|
QUnit.start();
|
|
assert.ok( true, 'Viewer should be loaded' );
|
|
bootstrap.cleanupEventHandlers();
|
|
window.location.hash = '';
|
|
|
|
return $.Deferred().reject();
|
|
};
|
|
|
|
QUnit.stop();
|
|
window.location.hash = hash;
|
|
}
|
|
|
|
QUnit.test( 'Promise does not hang on ResourceLoader errors', 3, function ( assert ) {
|
|
var bootstrap,
|
|
errorMessage = 'loading failed';
|
|
|
|
this.sandbox.stub( mw.loader, 'using' )
|
|
.callsArgWith( 2, new Error( errorMessage, ['mmv'] ) )
|
|
.withArgs( 'mediawiki.notification' ).returns( $.Deferred().reject() ); // needed for mw.notify
|
|
|
|
bootstrap = createBootstrap();
|
|
|
|
bootstrap.setupOverlay = function () {
|
|
assert.ok( true, 'Overlay was set up' );
|
|
};
|
|
|
|
bootstrap.cleanupOverlay = function () {
|
|
assert.ok( true, 'Overlay was cleaned up' );
|
|
};
|
|
|
|
QUnit.stop();
|
|
|
|
bootstrap.loadViewer().fail( function ( message ) {
|
|
assert.strictEqual( message, errorMessage, 'promise is rejected with the error message when loading fails' );
|
|
QUnit.start();
|
|
} );
|
|
} );
|
|
|
|
QUnit.test( 'Clicks are not captured once the loading fails', 4, function ( assert ) {
|
|
var event, returnValue,
|
|
bootstrap = new mw.mmv.MultimediaViewerBootstrap();
|
|
|
|
this.sandbox.stub( mw.loader, 'using' )
|
|
.callsArgWith( 2, new Error( 'loading failed', ['mmv'] ) )
|
|
.withArgs( 'mediawiki.notification' ).returns( $.Deferred().reject() ); // needed for mw.notify
|
|
bootstrap.ensureEventHandlersAreSetUp = $.noop;
|
|
|
|
event = new $.Event( 'click', { button: 0, which: 1 } );
|
|
returnValue = bootstrap.click( {}, event, 'foo' );
|
|
assert.ok( event.isDefaultPrevented(), 'First click is caught' );
|
|
assert.strictEqual( returnValue, false, 'First click is caught' );
|
|
|
|
event = new $.Event( 'click', { button: 0, which: 1 } );
|
|
returnValue = bootstrap.click( {}, event, 'foo' );
|
|
assert.ok( !event.isDefaultPrevented(), 'Click after loading failure is not caught' );
|
|
assert.notStrictEqual( returnValue, false, 'Click after loading failure is not caught' );
|
|
} );
|
|
|
|
QUnit.test( 'Check viewer invoked when clicking on legit image links', 9, function ( assert ) {
|
|
// TODO: Is <div class="gallery"><span class="image"><img/></span></div> valid ???
|
|
var div, link, link2, link3, link4, bootstrap,
|
|
viewer = { initWithThumbs : $.noop };
|
|
|
|
// Create gallery with legit link image
|
|
div = createGallery();
|
|
link = div.find( 'a.image' );
|
|
|
|
// Legit isolated thumbnail
|
|
link2 = $( '<a>' ).addClass( 'image' ).appendTo( '#qunit-fixture' );
|
|
$( '<img>' ).attr( 'src', 'thumb2.jpg' ).appendTo( link2 );
|
|
|
|
// Non-legit fragment
|
|
link3 = $( '<a>' ).addClass( 'noImage' ).appendTo( div );
|
|
$( '<img>' ).attr( 'src', 'thumb3.jpg' ).appendTo( link3 );
|
|
|
|
$( '<div>' ).addClass( 'fullMedia' ).appendTo( div );
|
|
$( '<img>' ).attr( 'src', 'thumb4.jpg' ).appendTo(
|
|
$( '<a>' )
|
|
.appendTo(
|
|
$( '<div>' )
|
|
.attr( 'id', 'file' )
|
|
.appendTo( '#qunit-fixture' )
|
|
)
|
|
);
|
|
|
|
// Create a new bootstrap object to trigger the DOM scan, etc.
|
|
bootstrap = createBootstrap( viewer );
|
|
|
|
link4 = $( '.fullMedia .mw-mmv-view-expanded' );
|
|
assert.ok( link4.length, 'Link for viewing expanded file was set up.' );
|
|
|
|
bootstrap.setupOverlay = function () {
|
|
assert.ok( true, 'Overlay was set up' );
|
|
};
|
|
|
|
viewer.loadImageByTitle = function() {
|
|
assert.ok( true, 'Image loaded' );
|
|
};
|
|
|
|
// Click on legit link
|
|
link.trigger( { type : 'click', which : 1 } );
|
|
|
|
// Click on legit link
|
|
link2.trigger( { type : 'click', which : 1 } );
|
|
|
|
// Click on legit link
|
|
link4.trigger( { type: 'click', which: 1 } );
|
|
|
|
// Click on legit link even when preference says not to
|
|
mw.config.set( 'wgMediaViewerOnClick', false );
|
|
link4.trigger( { type: 'click', which: 1 } );
|
|
mw.config.set( 'wgMediaViewerOnClick', true );
|
|
|
|
bootstrap.setupOverlay = function () {
|
|
assert.ok( false, 'Overlay was not set up' );
|
|
};
|
|
|
|
viewer.loadImageByTitle = function() {
|
|
assert.ok( false, 'Image should not be loaded' );
|
|
};
|
|
|
|
// Click on non-legit link
|
|
link3.trigger( { type : 'click', which : 1 } );
|
|
|
|
// Click on legit links with preference off
|
|
mw.config.set( 'wgMediaViewerOnClick', false );
|
|
link.trigger( { type : 'click', which : 1 } );
|
|
link2.trigger( { type : 'click', which : 1 } );
|
|
} );
|
|
|
|
QUnit.test( 'Skip images with invalid extensions', 0, function ( assert ) {
|
|
var div, link, bootstrap,
|
|
viewer = { initWithThumbs : $.noop };
|
|
|
|
// Create gallery with image that has invalid name extension
|
|
div = createGallery( 'thumb.badext' );
|
|
link = div.find( 'a.image' );
|
|
|
|
// Create a new bootstrap object to trigger the DOM scan, etc.
|
|
bootstrap = createBootstrap( viewer );
|
|
|
|
viewer.loadImageByTitle = function() {
|
|
assert.ok( false, 'Image should not be loaded' );
|
|
};
|
|
|
|
// Click on legit link with wrong image extension
|
|
link.trigger( { type : 'click', which : 1 } );
|
|
} );
|
|
|
|
QUnit.test( 'Accept only left clicks without modifier keys, skip the rest', 2, function ( assert ) {
|
|
var $div, $link, bootstrap,
|
|
viewer = { initWithThumbs : $.noop };
|
|
|
|
// Create gallery with image that has valid name extension
|
|
$div = createGallery();
|
|
|
|
// Create a new bootstrap object to trigger the DOM scan, etc.
|
|
bootstrap = createBootstrap( viewer );
|
|
|
|
$link = $div.find( 'a.image' );
|
|
|
|
bootstrap.setupOverlay = function () {
|
|
assert.ok( true, 'Overlay was set up' );
|
|
};
|
|
|
|
viewer.loadImageByTitle = function() {
|
|
assert.ok( true, 'Image loaded' );
|
|
};
|
|
|
|
// Handle valid left click, it should try to load the image
|
|
$link.trigger( { type : 'click', which : 1 } );
|
|
|
|
bootstrap.setupOverlay = function () {
|
|
assert.ok( false, 'Overlay was not set up' );
|
|
};
|
|
|
|
viewer.loadImageByTitle = function() {
|
|
assert.ok( false, 'Image should not be loaded' );
|
|
};
|
|
|
|
// Skip Ctrl-left-click, no image is loaded
|
|
$link.trigger( { type : 'click', which : 1, ctrlKey : true } );
|
|
|
|
// Skip invalid right click, no image is loaded
|
|
$link.trigger( { type : 'click', which : 2 } );
|
|
} );
|
|
|
|
QUnit.test( 'Ensure that the correct title is loaded when clicking', 2, function ( assert ) {
|
|
var bootstrap,
|
|
viewer = { initWithThumbs : $.noop },
|
|
$div = createGallery( 'foo.jpg' ),
|
|
$link = $div.find( 'a.image' );
|
|
|
|
viewer.loadImageByTitle = function ( loadedTitle ) {
|
|
assert.strictEqual( loadedTitle.getPrefixedDb(), 'File:Foo.jpg', 'Titles are identical' );
|
|
};
|
|
|
|
// Create a new bootstrap object to trigger the DOM scan, etc.
|
|
bootstrap = createBootstrap( viewer );
|
|
|
|
bootstrap.setupOverlay = function () {
|
|
assert.ok( true, 'Overlay was set up' );
|
|
};
|
|
|
|
$link.trigger( { type : 'click', which : 1 } );
|
|
} );
|
|
|
|
QUnit.test( 'Validate new LightboxImage object has sane constructor parameters', 7, function ( assert ) {
|
|
var bootstrap,
|
|
$div,
|
|
$link,
|
|
viewer = new mw.mmv.MultimediaViewer(),
|
|
fname = 'valid',
|
|
imgSrc = '/' + fname + '.jpg/300px-' + fname + '.jpg',
|
|
imgRegex = new RegExp( imgSrc + '$' );
|
|
|
|
$div = createThumb( imgSrc, 'Blah blah' );
|
|
$link = $div.find( 'a.image' );
|
|
|
|
viewer.loadImage = $.noop;
|
|
|
|
viewer.createNewImage = function ( fileLink, filePageLink, fileTitle, index, thumb, caption ) {
|
|
assert.ok( fileLink.match( imgRegex ), 'Thumbnail URL used in creating new image object' );
|
|
assert.strictEqual( filePageLink, '', 'File page link is sane when creating new image object' );
|
|
assert.strictEqual( fileTitle.title, fname, 'Filename is correct when passed into new image constructor' );
|
|
assert.strictEqual( index, 0, 'The only image we created in the gallery is set at index 0 in the images array' );
|
|
assert.strictEqual( thumb.outerHTML, '<img src="' + imgSrc + '">', 'The image element passed in is the thumbnail we want.' );
|
|
assert.strictEqual( caption, 'Blah blah', 'The caption passed in is correct' );
|
|
};
|
|
|
|
// Create a new bootstrap object to trigger the DOM scan, etc.
|
|
bootstrap = createBootstrap( viewer );
|
|
|
|
bootstrap.setupOverlay = function () {
|
|
assert.ok( true, 'Overlay was set up' );
|
|
};
|
|
|
|
$link.trigger( { type : 'click', which : 1 } );
|
|
} );
|
|
|
|
QUnit.test( 'Only load the viewer on a valid hash (modern browsers)', 1, function ( assert ) {
|
|
var bootstrap;
|
|
|
|
window.location.hash = '';
|
|
|
|
bootstrap = createBootstrap();
|
|
|
|
hashTest( bootstrap, assert );
|
|
} );
|
|
|
|
QUnit.test( 'Only load the viewer on a valid hash (old browsers)', 1, function ( assert ) {
|
|
var bootstrap;
|
|
|
|
window.location.hash = '';
|
|
|
|
bootstrap = createBootstrap();
|
|
bootstrap.browserHistory = undefined;
|
|
|
|
hashTest( bootstrap, assert );
|
|
} );
|
|
|
|
QUnit.test( 'internalHashChange', 1, function ( assert ) {
|
|
var bootstrap = createBootstrap(),
|
|
hash = '#mediaviewer/foo';
|
|
|
|
window.location.hash = '';
|
|
|
|
bootstrap.setupEventHandlers();
|
|
|
|
bootstrap.loadViewer = function () {
|
|
assert.ok( false, 'Viewer should not be loaded' );
|
|
return $.Deferred().reject();
|
|
};
|
|
|
|
bootstrap.internalHashChange( { hash: hash } );
|
|
|
|
assert.strictEqual( window.location.hash, hash, 'Window\'s hash has been updated correctly' );
|
|
|
|
bootstrap.cleanupEventHandlers();
|
|
|
|
window.location.hash = '';
|
|
} );
|
|
|
|
QUnit.test( 'isCSSReady', 3, function ( assert ) {
|
|
var bootstrap = createBootstrap(),
|
|
deferred = $.Deferred(),
|
|
CSSclass = 'foo-' + $.now(),
|
|
$style = $( '<style type="text/css" />' )
|
|
.text( '.' + CSSclass + ' { display: inline; }' );
|
|
|
|
bootstrap.readinessCSSClass = CSSclass;
|
|
bootstrap.isCSSReady( deferred );
|
|
|
|
assert.strictEqual( deferred.state(), 'pending', 'The style isn\'t on the page yet' );
|
|
|
|
QUnit.stop();
|
|
|
|
deferred.then( function() {
|
|
QUnit.start();
|
|
assert.ok( true, 'The style is on the page' );
|
|
assert.strictEqual( $( '.' + CSSclass ).length, 0, 'There are no leftover test elements' );
|
|
$style.remove();
|
|
} );
|
|
|
|
$style.appendTo( 'head' );
|
|
|
|
this.clock.tick( bootstrap.readinessWaitDuration );
|
|
} );
|
|
|
|
QUnit.test( 'Restoring article scroll position', 2, function ( assert ) {
|
|
var bootstrap = createBootstrap(),
|
|
scrollTop = 50,
|
|
scrollLeft = 60,
|
|
stubbedScrollTop = scrollTop,
|
|
stubbedScrollLeft = scrollLeft;
|
|
|
|
this.sandbox.stub( $, 'scrollTo', function ( target ) {
|
|
if ( target ) {
|
|
stubbedScrollTop = target.top;
|
|
stubbedScrollLeft = target.left;
|
|
} else {
|
|
return {
|
|
scrollTop : function () { return stubbedScrollTop; },
|
|
scrollLeft : function () { return stubbedScrollLeft; }
|
|
};
|
|
}
|
|
} );
|
|
|
|
bootstrap.setupOverlay();
|
|
// Calling this a second time because it can happen in history navigation context
|
|
bootstrap.setupOverlay();
|
|
bootstrap.cleanupOverlay();
|
|
|
|
assert.strictEqual( stubbedScrollTop, scrollTop, 'Scroll is correctly reset to original top position' );
|
|
assert.strictEqual( stubbedScrollLeft, scrollLeft, 'Scroll is correctly reset to original left position' );
|
|
} );
|
|
|
|
QUnit.test( 'Preload JS/CSS dependencies on thumb hover', 2, function ( assert ) {
|
|
var $div, bootstrap,
|
|
viewer = { initWithThumbs : $.noop };
|
|
|
|
// Create gallery with image that has valid name extension
|
|
$div = createThumb();
|
|
|
|
// Create a new bootstrap object to trigger the DOM scan, etc.
|
|
bootstrap = createBootstrap( viewer );
|
|
|
|
this.sandbox.stub( mw.loader, 'load' );
|
|
|
|
$div.mouseenter();
|
|
this.clock.tick( bootstrap.hoverWaitDuration - 50 );
|
|
$div.mouseleave();
|
|
|
|
assert.ok( !mw.loader.load.called, 'Dependencies should not be preloaded if the thumb is not hovered long enough' );
|
|
|
|
$div.mouseenter();
|
|
this.clock.tick( bootstrap.hoverWaitDuration + 50 );
|
|
$div.mouseleave();
|
|
|
|
assert.ok( mw.loader.load.called, 'Dependencies should be preloaded if the thumb is hovered long enough' );
|
|
} );
|
|
|
|
QUnit.test( 'isAllowedThumb', 5, function ( assert ) {
|
|
var $container = $( '<div>' ),
|
|
$thumb = $( '<img>' ).appendTo( $container ),
|
|
bootstrap = createBootstrap();
|
|
|
|
|
|
assert.ok( bootstrap.isAllowedThumb( $thumb ), 'Normal image in a div is allowed.' );
|
|
|
|
$container.addClass( 'metadata' );
|
|
assert.strictEqual( bootstrap.isAllowedThumb( $thumb ), false, 'Image in a metadata container is disallowed.' );
|
|
|
|
$container.prop( 'class', '' );
|
|
$container.addClass( 'noviewer' );
|
|
assert.strictEqual( bootstrap.isAllowedThumb( $thumb ), false, 'Image in a noviewer container is disallowed.' );
|
|
|
|
$container.prop( 'class', '' );
|
|
$container.addClass( 'noarticletext' );
|
|
assert.strictEqual( bootstrap.isAllowedThumb( $thumb ), false, 'Image in an empty article is disallowed.' );
|
|
|
|
$container.prop( 'class', '' );
|
|
$thumb.addClass( 'noviewer' );
|
|
assert.strictEqual( bootstrap.isAllowedThumb( $thumb ), false, 'Image with a noviewer class is disallowed.' );
|
|
} );
|
|
|
|
QUnit.test( 'findCaption', 3, function ( assert ) {
|
|
var gallery = createGallery( 'foo.jpg', 'Baz' ),
|
|
thumb = createThumb( 'foo.jpg', 'Quuuuux' ),
|
|
link = createNormal( 'foo.jpg', 'Foobar' ),
|
|
bootstrap = createBootstrap();
|
|
|
|
assert.strictEqual( bootstrap.findCaption( gallery.find( '.thumb' ), gallery.find( 'a.image' ) ), 'Baz', 'A gallery caption is found.' );
|
|
assert.strictEqual( bootstrap.findCaption( thumb, thumb.find( 'a.image' ) ), 'Quuuuux', 'A thumbnail caption is found.' );
|
|
assert.strictEqual( bootstrap.findCaption( $(), link ), 'Foobar', 'The caption is found even if the image is not a thumbnail.' );
|
|
} );
|
|
}( mediaWiki, jQuery ) );
|