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( `
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( `
1
a1
1.1
a1.1
1.1.1
a1.1.1
1.1.2
a1.1.2
1.2
a1.1
2
3
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( '
' )
);
const textPage = new PageHTMLParser(
util.parseHTML( '
' )
);
const pLegacyUrls = new PageHTMLParser(
util.parseHTML( '
' )
);
thumbs = p.getThumbnails();
const pNoViewer = new PageHTMLParser(
util.parseHTML( '
' )
);
const pMetadata = new PageHTMLParser(
util.parseHTML( '
' )
);
const pMetadataNested = new PageHTMLParser(
util.parseHTML( '
' )
);
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 = `
`;
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 = `
`;
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'
);
} );