mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-12-20 02:01:20 +00:00
6cbedbf9d2
* Remove need for manual hacking of sub groups via "msg" strings carefully prepended to every assertion. * Improve CI details, by reporting the specific case that failed, and local dev via ability to re-run each case, and reporting names directly in the HTML Reporter and CLI summary. * Reduce need for assert.async() and tracking of callbacks, especially to improve failure details in case of Promise rejection. Current logic was likely to cause a confusing timeout instead of a clear failure if the promise ends up rejected. QUnit propagates these as part of awaiting and asserting the test closure's promise value (as async fn) automatically. This approach also avoids the pitfal of a falsely passing test when an assertion inside a done() handler was never reached. * Use modern for-of where possible to remove need for closures and arrow functions. Thus reducing complexity of test code, where complexity should be kept lowest to avoid false confidence. * Use plain for-in instead of overly complex Object.keys().forEach(). Change-Id: I934a266e75e64371081f104cfb867fb2c282c84a
241 lines
6.3 KiB
JavaScript
241 lines
6.3 KiB
JavaScript
/*!
|
|
* VisualEditor DataModel MWInternalLinkAnnotation tests.
|
|
*
|
|
* @copyright See AUTHORS.txt
|
|
*/
|
|
|
|
QUnit.module( 've.dm.MWInternalLinkAnnotation', ve.test.utils.newMwEnvironment() );
|
|
|
|
QUnit.test( 'toDataElement', ( assert ) => {
|
|
// The expected data depends on site configuration, so we need to generate the cases several times.
|
|
const getCases = () => {
|
|
const
|
|
externalLink = ( href ) => {
|
|
const link = document.createElement( 'a' );
|
|
link.setAttribute( 'href', href );
|
|
return link;
|
|
},
|
|
internalLink = ( pageTitle, params ) => {
|
|
const link = document.createElement( 'a' );
|
|
link.setAttribute( 'href', mw.Title.newFromText( pageTitle ).getUrl( params ) );
|
|
return link;
|
|
},
|
|
parsoidLink = ( href ) => {
|
|
const link = document.createElement( 'a' );
|
|
if ( mw.config.get( 'wgArticlePath' ).includes( '?' ) ) {
|
|
href = href.replace( './', './index.php?title=' );
|
|
}
|
|
link.setAttribute( 'href', href );
|
|
return link;
|
|
};
|
|
return [
|
|
{
|
|
msg: 'Not an internal link',
|
|
element: externalLink( 'http://example.com/' ),
|
|
expected: {
|
|
type: 'link/mwExternal',
|
|
attributes: {
|
|
href: 'http://example.com/'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
msg: 'Simple',
|
|
element: internalLink( 'Foo' ),
|
|
expected: {
|
|
type: 'link/mwInternal',
|
|
attributes: {
|
|
lookupTitle: 'Foo',
|
|
normalizedTitle: 'Foo',
|
|
title: 'Foo'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
msg: 'Relative path',
|
|
element: parsoidLink( './Foo' ),
|
|
expected: {
|
|
type: 'link/mwInternal',
|
|
attributes: {
|
|
lookupTitle: 'Foo',
|
|
normalizedTitle: 'Foo',
|
|
title: 'Foo'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
msg: 'History link',
|
|
element: internalLink( 'Foo', { action: 'history' } ),
|
|
expected: {
|
|
type: 'link/mwExternal',
|
|
attributes: {
|
|
href: mw.Title.newFromText( 'Foo' ).getUrl( { action: 'history' } )
|
|
}
|
|
}
|
|
},
|
|
{
|
|
msg: 'Diff link',
|
|
element: internalLink( 'Foo', { diff: '3', oldid: '2' } ),
|
|
expected: {
|
|
type: 'link/mwExternal',
|
|
attributes: {
|
|
href: mw.Title.newFromText( 'Foo' ).getUrl( { diff: '3', oldid: '2' } )
|
|
}
|
|
}
|
|
},
|
|
{
|
|
msg: 'Red link',
|
|
element: internalLink( 'Foo', { action: 'edit', redlink: '1' } ),
|
|
expected: {
|
|
type: 'link/mwInternal',
|
|
attributes: {
|
|
lookupTitle: 'Foo',
|
|
normalizedTitle: 'Foo',
|
|
title: 'Foo'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
// Because percent-encoded URLs aren't valid titles, but what they decode to might be
|
|
msg: 'Percent encoded characters',
|
|
element: internalLink( 'Foo?' ),
|
|
expected: {
|
|
type: 'link/mwInternal',
|
|
attributes: {
|
|
lookupTitle: 'Foo?',
|
|
normalizedTitle: 'Foo?',
|
|
title: 'Foo?'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
// The fragment should make it into some parts of this, and not others
|
|
msg: 'Fragments',
|
|
element: internalLink( 'Foo#bar' ),
|
|
expected: {
|
|
type: 'link/mwInternal',
|
|
attributes: {
|
|
lookupTitle: 'Foo',
|
|
normalizedTitle: 'Foo#bar',
|
|
title: 'Foo#bar'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
// Question marks in the fragment shouldn't confuse this
|
|
msg: 'Question marks in fragments',
|
|
element: internalLink( 'Foo#bar?' ),
|
|
expected: {
|
|
type: 'link/mwInternal',
|
|
attributes: {
|
|
lookupTitle: 'Foo',
|
|
normalizedTitle: 'Foo#' + mw.util.escapeIdForLink( 'bar?' ),
|
|
title: 'Foo#' + mw.util.escapeIdForLink( 'bar?' )
|
|
}
|
|
}
|
|
}
|
|
];
|
|
};
|
|
|
|
const converter = new ve.dm.Converter( ve.dm.modelRegistry, ve.dm.nodeFactory, ve.dm.annotationFactory, ve.dm.metaItemFactory );
|
|
|
|
const articlePaths = [
|
|
{
|
|
// MediaWiki config settings:
|
|
// $wgScriptPath = '/w';
|
|
// $wgUsePathInfo = false;
|
|
msg: 'query string URL',
|
|
config: {
|
|
wgServer: 'http://example.org',
|
|
wgScript: '/w/index.php',
|
|
wgArticlePath: '/w/index.php?title=$1'
|
|
},
|
|
// Parsoid-generated <base href> given these MediaWiki config settings:
|
|
base: 'http://example.org/w/'
|
|
},
|
|
{
|
|
// MediaWiki config settings:
|
|
// $wgScriptPath = '/w';
|
|
// $wgUsePathInfo = true;
|
|
msg: 'short URL using pathinfo',
|
|
config: {
|
|
wgServer: 'http://example.org',
|
|
wgScript: '/w/index.php',
|
|
wgArticlePath: '/w/index.php/$1'
|
|
},
|
|
// Parsoid-generated <base href> given these MediaWiki config settings:
|
|
base: 'http://example.org/w/index.php/'
|
|
},
|
|
{
|
|
// MediaWiki config settings:
|
|
// $wgScriptPath = '/w';
|
|
// $wgArticlePath = '/wiki/$1';
|
|
msg: 'proper short URL',
|
|
config: {
|
|
wgServer: 'http://example.org',
|
|
wgScript: '/w/index.php',
|
|
wgArticlePath: '/wiki/$1'
|
|
},
|
|
// Parsoid-generated <base href> given these MediaWiki config settings:
|
|
base: 'http://example.org/wiki/'
|
|
}
|
|
];
|
|
|
|
for ( const pathData of articlePaths ) {
|
|
// Set up global state (site configuration)
|
|
mw.config.set( pathData.config );
|
|
|
|
const doc = ve.dm.mwExample.createExampleDocumentFromData( [], null, pathData.base );
|
|
// toDataElement is called during a converter run, so we need to fake up a bit of state to test it.
|
|
// This would normally be done by ve.dm.converter.getModelFromDom.
|
|
converter.doc = doc.getHtmlDocument();
|
|
converter.targetDoc = doc.getHtmlDocument();
|
|
converter.store = doc.getStore();
|
|
converter.internalList = doc.getInternalList();
|
|
converter.contextStack = [];
|
|
converter.fromClipboard = true;
|
|
|
|
// Generate test cases for this site configuration
|
|
for ( const caseItem of getCases() ) {
|
|
assert.deepEqual(
|
|
ve.dm.MWInternalLinkAnnotation.static.toDataElement( [ caseItem.element ], converter ),
|
|
caseItem.expected,
|
|
caseItem.msg + ': ' + pathData.msg
|
|
);
|
|
}
|
|
}
|
|
} );
|
|
|
|
QUnit.test.each( 'getFragment', {
|
|
'No fragment returns null': {
|
|
original: 'Foo',
|
|
expected: null
|
|
},
|
|
'Invalid title returns null': {
|
|
original: 'A%20B',
|
|
expected: null
|
|
},
|
|
'Blank fragment returns empty string': {
|
|
original: 'Foo#',
|
|
expected: ''
|
|
},
|
|
'Extant fragment returns same string': {
|
|
original: 'Foo#bar',
|
|
expected: 'bar'
|
|
},
|
|
'Hash-bang works returns full string': {
|
|
original: 'Foo#!bar',
|
|
expected: '!bar'
|
|
},
|
|
'Double-hash returns everything after the first hash': {
|
|
original: 'Foo##bar',
|
|
expected: '#bar'
|
|
},
|
|
'Multi-fragment returns everything after the first hash': {
|
|
original: 'Foo#bar#baz#bat',
|
|
expected: 'bar#baz#bat'
|
|
}
|
|
}, function ( assert, { original, expected } ) {
|
|
assert.strictEqual( ve.dm.MWInternalLinkAnnotation.static.getFragment( original ), expected );
|
|
} );
|