mediawiki-extensions-Multim.../tests/qunit/mmv/mmv.bootstrap.test.js
Gilles Dubuc 09374fc9dd Restore article scroll after closing Media Viewer
There used to be a CSS trick with the order we added things to the
page and removed them from it, but it doesn't seem possible anymore
with the new order of execution, with the overlay appearing
immediately and being taken care of inside bootstrap.

The main cause of the bug, however, was the hash reset happening
after the interface was closed.

Doing the scroll restore with jQuery.scrollTo is more future-proof
and testable in QUnit.

Additions were also made to the cucumber E2E test because QUnit
alone wouldn't have caught the hash issue.

This also cleans up custom events a little and reintroduces
pushState on browsers that support the history API.

Change-Id: I63187383b632a2e8793f05380c18db2713856865
Mingle: https://wikimedia.mingle.thoughtworks.com/projects/multimedia/cards/439
Bug: 63892
2014-04-14 18:04:30 +00:00

356 lines
10 KiB
JavaScript

( function ( mw, $ ) {
QUnit.module( 'mmv.bootstrap', QUnit.newMwEnvironment() );
function createGallery( imageSrc ) {
var div = $( '<div>' ).addClass( 'gallery' ).appendTo( '#qunit-fixture' ),
link = $( '<a>' ).addClass( 'image' ).appendTo( div );
$( '<img>' ).attr( 'src', ( imageSrc || 'thumb.jpg' ) ).appendTo( link );
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 createBootstrap( viewer ) {
var bootstrap = new mw.mmv.MultimediaViewerBootstrap();
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 oldUsing = mw.loader.using,
bootstrap,
errorMessage = 'loading failed';
mw.loader.using = function ( module, success, error ) {
if ( $.isFunction( error ) ) {
error( new Error( errorMessage, ['mmv'] ) );
}
};
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();
mw.loader.using = oldUsing;
} );
} );
QUnit.test( 'Check viewer invoked when clicking on legit image links', 7, 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 } );
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 } );
} );
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 invalid 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, '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;
// This speeds up the test execution
// It's not zero because if the test fails, the browser would get hammered indefinitely
bootstrap.readinessWaitDuration = 30;
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' );
} );
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' );
} );
}( mediaWiki, jQuery ) );