const dom = require( '../utils/dom' ), jQuery = require( '../utils/jQuery' ), mw = require( '../utils/mw' ), mustache = require( '../utils/mustache' ), oo = require( '../utils/oo' ), // These both have heavy dependencies on jQuery so must be loaded later. sinon = require( 'sinon' ), PARSER_OUTPUT = '
'; let fixture, PageHTMLParser, util; /** @type {sinon.SinonSandbox} */ let sandbox; /* eslint-disable jsdoc/valid-types */ /** @type {typeof import('../../../src/mobile.startup/Page')} */ let stubPage; /** @type {typeof import('../../../src/mobile.startup/Page')} */ let mobileTocPage; /** @type {typeof import('../../../src/mobile.startup/Page')} */ let desktopPage; /** @type {typeof import('../../../src/mobile.startup/Page')} */ let sectionPage; /* eslint-enable jsdoc/valid-types */ QUnit.module( 'MobileFrontend PageHTMLParser.js', { beforeEach: function () { if ( fixture ) { fixture.remove(); } sandbox = sinon.sandbox.create(); dom.setUp( sandbox, global ); mw.setUp( sandbox, global ); mustache.setUp( sandbox, global ); jQuery.setUp( sandbox, global ); oo.setUp( sandbox, global ); PageHTMLParser = require( '../../../src/mobile.startup/PageHTMLParser' ); util = require( '../../../src/mobile.startup/util' ); global.mw.util.percentDecodeFragment = function ( decoded ) { // We're not testing percentDecodeFragment here, so only test with decoded values return decoded; }; stubPage = new PageHTMLParser( util.parseHTML( PARSER_OUTPUT ).html( '

lead

a0
' ) ); mobileTocPage = new PageHTMLParser( util.parseHTML( PARSER_OUTPUT ).html( `
a0

lead

1

a1

1.1

a1.1
` ) ); desktopPage = new PageHTMLParser( util.parseHTML( PARSER_OUTPUT ).html( `

lead

a0

1

a1

1.1

a1.1
` ) ); sectionPage = new PageHTMLParser( util.parseHTML( PARSER_OUTPUT ).html( `

lead

a0

1

a1

1.1

a1.1

1.1.1

a1.1.1

1.1.2

a1.1.2

1.2

a1.1

2

a2

3

a3

Section with nested Ambox

nested-ambox-parent,

nested-ambox-1,
nested-ambox-2

Sub-section with nested Ambox

nested-ambox-parent,

nested-ambox-1,
nested-ambox-2

subsection heading

Another subsection heading

` ) // end .html() ); // end new Page(); }, afterEach: function () { jQuery.tearDown(); sandbox.restore(); } } ); QUnit.test( '#findInSectionLead', function ( assert ) { // check desktop page [ [ 0, 'a0', 'lead section' ], [ 1, 'a1', 'h2' ], [ 2, 'a1.1', 'h3' ], [ 3, '', 'h4', 'selector does not match', '.foo' ], [ 111, '', 'Non-existent section' ] ].forEach( function ( params, i ) { const section = params[0], expect = params[1], test = params[2], selector = params[3] || '.ambox'; assert.strictEqual( desktopPage.findChildInSectionLead( section, selector ).text(), expect, 'Found correct text in desktop test ' + i + ' case: ' + test ); } ); // check stub [ [ 0, 'a0', 'lead section' ], [ 3, '', 'h4', 'selector does not match', '.foo' ], [ 111, '', 'Non-existent section' ] ].forEach( function ( testcase ) { assert.strictEqual( stubPage.findChildInSectionLead( testcase[0], testcase[3] || '.ambox' ).text(), testcase[1], 'Stub: Found correct text in desktop test case:' + testcase[2] ); } ); // check mobile pages with section wrapping [ [ 0, 'a0', 'lead section' ], [ 1, 'a1', 'h2' ], [ 2, 'a1.1', 'h3' ], [ 3, 'a1.1.1', 'h4' ], [ 3, '', 'h4', 'selector does not match', '.foo' ], [ 7, 'a3', 'h2 later' ], [ 111, '', 'Non-existent section' ] ].forEach( function ( testcase ) { assert.strictEqual( sectionPage.findChildInSectionLead( testcase[0], testcase[3] || '.ambox' ).text(), testcase[1], 'Mobile: Found correct text in test case:' + testcase[2] ); } ); [ [ 0, 'a0', 'lead section' ], [ 1, 'a1', 'h2' ], [ 2, 'a1.1', 'h3' ], [ 111, '', 'Non-existent section' ] ].forEach( function ( testcase ) { assert.strictEqual( mobileTocPage.findChildInSectionLead( testcase[0], testcase[3] || '.ambox' ).text(), testcase[1], 'Mobile with table of contents: Found correct text in test case:' + testcase[2] ); } ); [ [ 8, '.ambox', /[\s]*nested-ambox-parent,[\s]*nested-ambox-1,\s*nested-ambox-2[\s]*/, 'Nested elements in section' ], [ 9, '.ambox', /[\s]*nested-ambox-parent,[\s]*nested-ambox-1,[\s]*nested-ambox-2[\s]*/, 'Nested elements in subsection' ] ].forEach( function ( testcase ) { const result = sectionPage.findChildInSectionLead( testcase[0], testcase[1] ); sinon.assert.match( result.not( result.children() ).text(), testcase[2] ); } ); } ); QUnit.test( '#getThumbnail', function ( assert ) { // Valid anchor. const $container = util.parseHTML( '
 
' ); const parser = new PageHTMLParser( $container ); const thumb = parser.getThumbnail( $container.find( PageHTMLParser.THUMB_SELECTOR ) ); assert.notStrictEqual( thumb, null, 'Thumbnail found if valid.' ); assert.strictEqual( thumb.getFileName(), 'File:Design_portal_logo.jpg', 'Thumbnail found if valid.' ); // Valid anchor with ?uselang=fa const $containerUseLang = util.parseHTML( '
 
' ); const parserUseLang = new PageHTMLParser( $containerUseLang ); const thumbUseLang = parserUseLang.getThumbnail( $containerUseLang.find( PageHTMLParser.THUMB_SELECTOR ) ); assert.notStrictEqual( thumbUseLang, null, 'Thumbnail found if valid.' ); assert.strictEqual( thumbUseLang.getFileName(), 'File:Design_portal_logo.jpg', 'Thumbnail found if valid.' ); // Valid anchor with legacy URL const $containerLegacy = util.parseHTML( '
 
' ); const parserLegacy = new PageHTMLParser( $containerLegacy ); const thumbLegacy = parserLegacy.getThumbnail( $containerLegacy.find( PageHTMLParser.THUMB_SELECTOR ) ); assert.notStrictEqual( thumbLegacy, null, 'Thumbnail found if valid.' ); assert.strictEqual( thumbLegacy.getFileName(), 'File:Design_portal_logo.jpg', 'Thumbnail found if valid.' ); // Anchor with 'metadata' class should be excluded. const $containerMetadata = util.parseHTML( '
 
' ); const parserMetadata = new PageHTMLParser( $containerMetadata ); const thumbMetadata = parserMetadata.getThumbnail( $containerMetadata.find( PageHTMLParser.THUMB_SELECTOR ) ); assert.strictEqual( thumbMetadata, null, 'Thumbnail not found if invalid.' ); } ); QUnit.test( '#getThumbnails', function ( assert ) { let thumbs; const p = new PageHTMLParser( util.parseHTML( '
Cyanolimnas cerverai by Allan Brooks cropped.jpg
' ) ); const textPage = new PageHTMLParser( util.parseHTML( '
' ) ); const pLegacyUrls = new PageHTMLParser( util.parseHTML( '
Cyanolimnas cerverai by Allan Brooks cropped.jpg
' ) ); thumbs = p.getThumbnails(); const pNoViewer = new PageHTMLParser( util.parseHTML( '
Cyanolimnas cerverai by Allan Brooks cropped.jpg
' ) ); const pMetadata = new PageHTMLParser( util.parseHTML( '
' ) ); const pMetadataNested = new PageHTMLParser( util.parseHTML( '
Cyanolimnas cerverai by Allan Brooks cropped.jpg
' ) ); const pLazyImages = new PageHTMLParser( util.parseHTML( '
 
' ) ); const pLazyImagesTypo = new PageHTMLParser( util.parseHTML( '
 
' ) ); const metadataTable = new PageHTMLParser( util.parseHTML( '
' ) ); assert.strictEqual( thumbs.length, 1, 'Found expected number of thumbnails.' ); assert.strictEqual( thumbs[0].getFileName(), 'File:Cyanolimnas_cerverai_by_Allan_Brooks_cropped.jpg', 'Found expected filename.' ); thumbs = textPage.getThumbnails(); assert.strictEqual( thumbs.length, 0, 'This page has no thumbnails.' ); thumbs = pLegacyUrls.getThumbnails(); assert.strictEqual( thumbs.length, 1, 'Found expected number of thumbnails.' ); assert.strictEqual( thumbs[0].getFileName(), 'File:Cyanolimnas_cerverai_by_Allan_Brooks_cropped.jpg', 'Found expected filename.' ); thumbs = pNoViewer.getThumbnails(); assert.strictEqual( thumbs.length, 0, 'This page has no thumbnails.' ); thumbs = pMetadata.getThumbnails(); assert.strictEqual( thumbs.length, 0, 'This page has no thumbnails.' ); thumbs = pMetadataNested.getThumbnails(); assert.strictEqual( thumbs.length, 0, 'Images inside a container with the class are not included. Images inside tables for example.' ); thumbs = pLazyImages.getThumbnails(); assert.strictEqual( thumbs.length, 0, 'Consider whether the class is on an image which has not been lazy loaded.' ); thumbs = metadataTable.getThumbnails(); assert.strictEqual( thumbs.length, 0, 'Consider whether the lazy loaded image is inside a .metadata container.' ); thumbs = pLazyImagesTypo.getThumbnails(); assert.strictEqual( thumbs.length, 1, 'Thumbnail found if there is a typo.' ); } ); QUnit.test( '#getLanguages', function ( assert ) { const html = `

Languages

`; fixture = document.createElement( 'div' ); fixture.innerHTML = html; global.document.body.appendChild( fixture ); const p = new PageHTMLParser( util.parseHTML( '' ) ); const langs = p.getLanguages( 'Foo' ); assert.strictEqual( langs.languages[ 0 ].title, 'Tyrannosaurus', 'Expected title' ); assert.strictEqual( langs.languages[ 0 ].langname, 'French', 'Expected language' ); assert.strictEqual( langs.languages[ 0 ].autonym, 'Français', 'Expected autonym' ); } ); QUnit.test( '#getLanguages (no hyphen)', function ( assert ) { const html = `

Languages

`; fixture = document.createElement( 'div' ); fixture.innerHTML = html; global.document.body.appendChild( fixture ); const p = new PageHTMLParser( util.parseHTML( '' ) ); const langs = p.getLanguages( 'Foo' ); assert.strictEqual( langs.languages[ 0 ].title, 'Tyrannosaurus', 'Expected title' ); assert.strictEqual( langs.languages[ 0 ].autonym, 'Français', 'Expected autonym' ); assert.strictEqual( langs.languages[ 0 ].langname, 'Français', 'Language falls back to autonym when not available' ); } ); QUnit.test( '#getLanguages (T349000)', function ( assert ) { const html = ` `; fixture = document.createElement( 'div' ); fixture.innerHTML = html; global.document.body.appendChild( fixture ); const p = new PageHTMLParser( util.parseHTML( '' ) ); const langs = p.getLanguages( 'Foo' ); assert.strictEqual( langs.languages.length, 37, 'Expected languages' ); assert.strictEqual( langs.variants.length, 0, 'Expected variants' ); assert.strictEqual( langs.languages.filter( ( lang ) => lang.lang === 'en' )[ 0 ].title, 'Mission: Impossible – Dead Reckoning Part One', 'Check the English title' ); } );