diff --git a/extension.json b/extension.json index a424484af..304b7b5f0 100644 --- a/extension.json +++ b/extension.json @@ -48,6 +48,8 @@ "formats/ext.srf.formats.eventcalendar.tests.js", "formats/ext.srf.formats.filtered.test.js", "formats/ext.srf.formats.gallery.test.js", + "formats/ext.srf.formats.gallery.overlay.test.js", + "formats/ext.srf.formats.gallery.redirect.test.js", "formats/ext.srf.formats.media.test.js", "formats/ext.srf.formats.tagcloud.test.js", "widgets/ext.srf.widgets.eventcalendar.tests.js", diff --git a/formats/gallery/resources/ext.srf.gallery.overlay.js b/formats/gallery/resources/ext.srf.gallery.overlay.js index 77844a3e1..7159a4e4a 100644 --- a/formats/gallery/resources/ext.srf.gallery.overlay.js +++ b/formats/gallery/resources/ext.srf.gallery.overlay.js @@ -76,7 +76,7 @@ $this.find( '.gallerybox' ).each( function () { var $this = $( this ), h = mw.html, - image = $this.find( 'a.image' ), + image = $this.find( 'a.mw-file-description' ), imageText = $.trim( $this.find( '.gallerytext p' ).text() ); // Group images diff --git a/formats/gallery/resources/ext.srf.gallery.redirect.js b/formats/gallery/resources/ext.srf.gallery.redirect.js index a8f2dfc51..ee28a017f 100644 --- a/formats/gallery/resources/ext.srf.gallery.redirect.js +++ b/formats/gallery/resources/ext.srf.gallery.redirect.js @@ -59,7 +59,7 @@ return context.find( '.gallerybox' ).each( function() { var $this = $( this ), h = mw.html, - image = $this.find( 'a.image' ); + image = $this.find( 'a.mw-file-description' ); // Avoid undefined error if ( image.attr( 'href' ) === undefined ) { diff --git a/resources/jquery/fancybox/jquery.fancybox-1.3.4.pack.js b/resources/jquery/fancybox/jquery.fancybox-1.3.4.pack.js index 1373ed083..1daa83fd0 100644 --- a/resources/jquery/fancybox/jquery.fancybox-1.3.4.pack.js +++ b/resources/jquery/fancybox/jquery.fancybox-1.3.4.pack.js @@ -28,7 +28,7 @@ d.titlePosition=="float"?'0&&n.show();j.css({width:i.width-d.padding*2,height:e.autoDimensions?"auto":i.height-y-d.padding*2}).html(m.contents());f.css(i).fadeIn(d.transitionIn=="none"?0:d.speedIn,S)}}}},Y=function(){if(d.enableEscapeButton||d.enableKeyboardNav)b(document).bind("keydown.fb",function(a){if(a.keyCode==27&&d.enableEscapeButton){a.preventDefault();b.fancybox.close()}else if((a.keyCode== -37||a.keyCode==39)&&d.enableKeyboardNav&&a.target.tagName!=="INPUT"&&a.target.tagName!=="TEXTAREA"&&a.target.tagName!=="SELECT"){a.preventDefault();b.fancybox[a.keyCode==37?"prev":"next"]()}});if(d.showNavArrows){if(d.cyclic&&l.length>1||p!==0)z.show();if(d.cyclic&&l.length>1||p!=l.length-1)A.show()}else{z.hide();A.hide()}},S=function(){if(!b.support.opacity){j.get(0).style.removeAttribute("filter");f.get(0).style.removeAttribute("filter")}e.autoDimensions&&j.css("height","auto");f.css("height","auto"); +37||a.keyCode==39)&&d.enableKeyboardNav&&a.target.tagName!=="INPUT"&&a.target.tagName!=="TEXTAREA"&&a.target.tagName!=="SELECT"){a.preventDefault();b.fancybox[a.keyCode==37?"prev":"next"]()}});if(d.showNavArrows){if(d.cyclic&&l.length>1||p!==0)z.show();if(d.cyclic&&l.length>1||p!=l.length-1)A.show()}else{z.hide();A.hide()}},S=function(){if(!b.support.opacity){j.css("filter",0);f.css("filter",0)}e.autoDimensions&&j.css("height","auto");f.css("height","auto"); s&&s.length&&n.show();d.showCloseButton&&E.show();Y();d.hideOnContentClick&&j.bind("click",b.fancybox.close);d.hideOnOverlayClick&&u.bind("click",b.fancybox.close);b(window).bind("resize.fb",b.fancybox.resize);d.centerOnScroll&&b(window).bind("scroll.fb",b.fancybox.center);if(d.type=="iframe")b('').appendTo(j); f.show();h=false;b.fancybox.center();d.onComplete(l,p,d);var a,c;if(l.length-1>p){a=l[p+1].href;if(typeof a!=="undefined"&&a.match(J)){c=new Image;c.src=a}}if(p>0){a=l[p-1].href;if(typeof a!=="undefined"&&a.match(J)){c=new Image;c.src=a}}},T=function(a){var c={width:parseInt(r.width+(i.width-r.width)*a,10),height:parseInt(r.height+(i.height-r.height)*a,10),top:parseInt(r.top+(i.top-r.top)*a,10),left:parseInt(r.left+(i.left-r.left)*a,10)};if(typeof i.opacity!=="undefined")c.opacity=a<0.5?0.5:a;f.css(c); j.css({width:c.width-d.padding*2,height:c.height-y*a-d.padding*2})},U=function(){return[b(window).width()-d.margin*2,b(window).height()-d.margin*2,b(document).scrollLeft()+d.margin,b(document).scrollTop()+d.margin]},X=function(){var a=U(),c={},g=d.autoScale,k=d.padding*2;c.width=d.width.toString().indexOf("%")>-1?parseInt(a[0]*parseFloat(d.width)/100,10):d.width+k;c.height=d.height.toString().indexOf("%")>-1?parseInt(a[1]*parseFloat(d.height)/100,10):d.height+k;if(g&&(c.width>a[0]||c.height>a[1]))if(e.type== diff --git a/tests/qunit/formats/ext.srf.formats.gallery.overlay.test.js b/tests/qunit/formats/ext.srf.formats.gallery.overlay.test.js new file mode 100644 index 000000000..53f488e65 --- /dev/null +++ b/tests/qunit/formats/ext.srf.formats.gallery.overlay.test.js @@ -0,0 +1,105 @@ +/** + * QUnit tests for the SRF gallery overlay functionality + * + * @file + * @since 5.1.0 + * + * @ingroup SRF + * + * @licence GPL-2.0-or-later + * @author GitHub Copilot Assistant + */ + +( function ( $, mw, srf ) { + 'use strict'; + + QUnit.module( 'ext.srf.formats.gallery.overlay', QUnit.newMwEnvironment() ); + + /** + * Test overlay initialization with mw-file-description selector + */ + QUnit.test( 'overlay handles mw-file-description selector', function ( assert ) { + assert.expect( 4 ); + + // Create mock HTML structure with new MediaWiki gallery format + var mockHtml = $( + '' + ); + + $( '#qunit-fixture' ).append( mockHtml ); + var context = $( '#test-gallery' ); + var gallery = new srf.formats.gallery(); + + // Test that the overlay function finds the correct selector + gallery.overlay( context, 'File' ); + + var imageLink = context.find( 'a.mw-file-description' ); + assert.ok( imageLink.length > 0, 'Found image link with mw-file-description class' ); + assert.equal( imageLink.attr( 'rel' ), 'test-gallery', 'Image link has correct rel attribute for grouping' ); + assert.equal( imageLink.attr( 'title' ), 'Test image description', 'Image link has correct title from gallery text' ); + assert.ok( imageLink.attr( 'href' ).indexOf( 'File:Test.jpg' ) > -1, 'Image link href points to correct file' ); + } ); + + /** + * Test overlay with empty gallery + */ + QUnit.test( 'overlay handles empty gallery gracefully', function ( assert ) { + assert.expect( 1 ); + + var emptyGallery = $( '' ); + $( '#qunit-fixture' ).append( emptyGallery ); + + var gallery = new srf.formats.gallery(); + + // Should not throw error with empty gallery + gallery.overlay( emptyGallery, 'File' ); + + assert.ok( true, 'Overlay handles empty gallery without errors' ); + } ); + + /** + * Test overlay with missing href attribute + */ + QUnit.test( 'overlay handles missing href gracefully', function ( assert ) { + assert.expect( 2 ); + + var mockHtml = $( + '' + ); + + $( '#qunit-fixture' ).append( mockHtml ); + var context = $( '#test-gallery-no-href' ); + var gallery = new srf.formats.gallery(); + + gallery.overlay( context, 'File' ); + + var galleryBox = context.find( '.gallerybox' ); + assert.ok( galleryBox.find( '.error' ).length > 0, 'Error message displayed for missing href' ); + assert.ok( galleryBox.html().indexOf( 'srf-gallery-image-url-error' ) > -1, 'Correct error message key used' ); + } ); + +}( jQuery, mediaWiki, semanticFormats ) ); \ No newline at end of file diff --git a/tests/qunit/formats/ext.srf.formats.gallery.redirect.test.js b/tests/qunit/formats/ext.srf.formats.gallery.redirect.test.js new file mode 100644 index 000000000..cec61076f --- /dev/null +++ b/tests/qunit/formats/ext.srf.formats.gallery.redirect.test.js @@ -0,0 +1,189 @@ +/** + * QUnit tests for the SRF gallery redirect functionality + * + * @file + * @since 5.1.0 + * + * @ingroup SRF + * + * @licence GPL-2.0-or-later + * @author GitHub Copilot Assistant + */ + +( function ( $, mw, srf ) { + 'use strict'; + + QUnit.module( 'ext.srf.formats.gallery.redirect', QUnit.newMwEnvironment() ); + + /** + * Mock srf.util for testing + */ + var mockUtil = function() { + return { + spinner: { + create: function() {}, + replace: function() {} + }, + getTitleURL: function( params, callback ) { + // Mock successful URL retrieval + if ( params.title === 'Valid_Title' ) { + callback( '/wiki/Valid_Title' ); + } else { + callback( false ); + } + } + }; + }; + + // Mock srf.util + var originalUtil = srf.util; + srf.util = mockUtil; + + /** + * Test redirect functionality with mw-file-description selector + */ + QUnit.test( 'redirect handles mw-file-description selector', function ( assert ) { + assert.expect( 3 ); + + var mockHtml = $( + '
' + + '' + + '
' + ); + + $( '#qunit-fixture' ).append( mockHtml ); + var context = mockHtml; + var gallery = new srf.formats.gallery(); + + gallery.redirect( context ); + + var imageLink = context.find( 'a.mw-file-description' ); + var redirectIcon = context.find( '.redirecticon' ); + + assert.ok( imageLink.length > 0, 'Found image link with mw-file-description class' ); + assert.equal( imageLink.attr( 'href' ), 'http://example.com/redirect-target', 'Image href updated to redirect URL from alt attribute' ); + assert.ok( redirectIcon.is( ':visible' ), 'Redirect icon is visible for URI type redirect' ); + } ); + + /** + * Test redirect with article title resolution + */ + QUnit.test( 'redirect resolves article titles', function ( assert ) { + var done = assert.async(); + assert.expect( 3 ); + + var mockHtml = $( + '
' + + '' + + '
' + ); + + $( '#qunit-fixture' ).append( mockHtml ); + var context = mockHtml; + var gallery = new srf.formats.gallery(); + + gallery.redirect( context ); + + // Wait for async URL resolution + setTimeout( function() { + var imageLink = context.find( 'a.mw-file-description' ); + var redirectIcon = context.find( '.redirecticon' ); + + assert.ok( imageLink.length > 0, 'Found image link with mw-file-description class' ); + assert.equal( imageLink.attr( 'href' ), '/wiki/Valid_Title', 'Image href updated to resolved article URL' ); + assert.ok( redirectIcon.is( ':visible' ), 'Redirect icon is visible after successful title resolution' ); + done(); + }, 100 ); + } ); + + /** + * Test redirect with missing href attribute + */ + QUnit.test( 'redirect handles missing href gracefully', function ( assert ) { + assert.expect( 2 ); + + var mockHtml = $( + '
' + + '' + + '
' + ); + + $( '#qunit-fixture' ).append( mockHtml ); + var context = mockHtml; + var gallery = new srf.formats.gallery(); + + gallery.redirect( context ); + + var galleryBox = context.find( '.gallerybox' ); + assert.ok( galleryBox.find( '.error' ).length > 0, 'Error message displayed for missing href' ); + assert.ok( galleryBox.html().indexOf( 'srf-gallery-image-url-error' ) > -1, 'Correct error message key used' ); + } ); + + /** + * Test redirect with failed title resolution + */ + QUnit.test( 'redirect handles failed title resolution', function ( assert ) { + var done = assert.async(); + assert.expect( 2 ); + + var mockHtml = $( + '
' + + '' + + '
' + ); + + $( '#qunit-fixture' ).append( mockHtml ); + var context = mockHtml; + var gallery = new srf.formats.gallery(); + + gallery.redirect( context ); + + // Wait for async URL resolution + setTimeout( function() { + var imageLink = context.find( 'a.mw-file-description' ); + var redirectIcon = context.find( '.redirecticon' ); + + assert.equal( imageLink.attr( 'href' ), '', 'Image href cleared on failed title resolution' ); + assert.ok( redirectIcon.is( ':hidden' ), 'Redirect icon hidden on failed title resolution' ); + done(); + }, 100 ); + } ); + + // Restore original srf.util after tests + QUnit.testDone( function() { + srf.util = originalUtil; + } ); + +}( jQuery, mediaWiki, semanticFormats ) ); \ No newline at end of file