Use packageFiles feature to replace special-purpose RL modules

For config vars, add a virtual package file called 'config.js', and use
require( './config.js' ) in the module. For most data modules, add a
virtual package file called 'data.js', use require( './data.js' ) in the
module. Where needed, add wrapper files that put the data in the
relevant global object and in module.exports.

LanguageDataModule is the only special-purpose module not being removed
in this commit, because it uses languageScripts, and those are not
compatible with packageFiles (yet).

Also merge mediawiki.ForeignStructuredUpload.config into
mediawiki.ForeignStructuredUpload, since that was the only thing that
used it.

Change-Id: I203d4e3ecdeeeb16729eba2dcf40d11a41d2e582
This commit is contained in:
Roan Kattouw 2018-11-05 14:20:05 -08:00 committed by Krinkle
parent a4eb30f013
commit 1c7c9bdf1f
14 changed files with 126 additions and 392 deletions

View file

@ -1239,11 +1239,8 @@ $wgAutoloadLocalClasses = [
'ResourceLoaderForeignApiModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderForeignApiModule.php',
'ResourceLoaderImage' => __DIR__ . '/includes/resourceloader/ResourceLoaderImage.php',
'ResourceLoaderImageModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderImageModule.php',
'ResourceLoaderJqueryMsgModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderJqueryMsgModule.php',
'ResourceLoaderLanguageDataModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderLanguageDataModule.php',
'ResourceLoaderLanguageNamesModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderLanguageNamesModule.php',
'ResourceLoaderLessVarFileModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderLessVarFileModule.php',
'ResourceLoaderMediaWikiUtilModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderMediaWikiUtilModule.php',
'ResourceLoaderModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderModule.php',
'ResourceLoaderOOUIFileModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderOOUIFileModule.php',
'ResourceLoaderOOUIImageModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderOOUIImageModule.php',
@ -1251,9 +1248,7 @@ $wgAutoloadLocalClasses = [
'ResourceLoaderSiteModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderSiteModule.php',
'ResourceLoaderSiteStylesModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderSiteStylesModule.php',
'ResourceLoaderSkinModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderSkinModule.php',
'ResourceLoaderSpecialCharacterDataModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderSpecialCharacterDataModule.php',
'ResourceLoaderStartUpModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderStartUpModule.php',
'ResourceLoaderUploadDialogModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUploadDialogModule.php',
'ResourceLoaderUserDefaultsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserDefaultsModule.php',
'ResourceLoaderUserModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserModule.php',
'ResourceLoaderUserOptionsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserOptionsModule.php',

View file

@ -1,79 +0,0 @@
<?php
/**
* ResourceLoader module for mediawiki.jqueryMsg that provides generated data.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
/**
* ResourceLoader module for mediawiki.jqueryMsg and its generated data
*/
class ResourceLoaderJqueryMsgModule extends ResourceLoaderFileModule {
/**
* @param ResourceLoaderContext $context
* @return string JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
$fileScript = parent::getScript( $context );
$tagData = Sanitizer::getRecognizedTagData();
$allowedHtmlElements = array_merge(
array_keys( $tagData['htmlpairs'] ),
array_diff(
array_keys( $tagData['htmlsingle'] ),
array_keys( $tagData['htmlsingleonly'] )
)
);
$magicWords = [
'SITENAME' => $this->getConfig()->get( 'Sitename' ),
];
Hooks::run( 'ResourceLoaderJqueryMsgModuleMagicWords', [ $context, &$magicWords ] );
$parserDefaults = [
'allowedHtmlElements' => $allowedHtmlElements,
'magic' => $magicWords,
];
$setDataScript = Xml::encodeJsCall( 'mw.jqueryMsg.setParserDefaults', [
$parserDefaults,
// Pass deep=true because mediawiki.jqueryMsg.js contains
// page-specific magic words that must not be overwritten.
true,
] );
return $fileScript . $setDataScript;
}
/**
* @param ResourceLoaderContext $context
* @return array
*/
public function getScriptURLsForDebug( ResourceLoaderContext $context ) {
// Bypass file module urls
return ResourceLoaderModule::getScriptURLsForDebug( $context );
}
/**
* @return bool
*/
public function enableModuleContentVersion() {
return true;
}
}

View file

@ -1,77 +0,0 @@
<?php
/**
* ResourceLoader module for providing language names.
*
* By default these names will be autonyms however other extensions may
* provided language names in the context language (e.g. cldr extension)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @author Ed Sanders
* @author Trevor Parscal
*/
/**
* ResourceLoader module for populating language specific data.
*/
class ResourceLoaderLanguageNamesModule extends ResourceLoaderFileModule {
protected $targets = [ 'desktop', 'mobile' ];
/**
* @param ResourceLoaderContext $context
* @return array
*/
protected function getData( ResourceLoaderContext $context ) {
return Language::fetchLanguageNames(
$context->getLanguage(),
'all'
);
}
/**
* @param ResourceLoaderContext $context
* @return string JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
return Xml::encodeJsCall(
'mw.language.setData',
[
$context->getLanguage(),
'languageNames',
$this->getData( $context )
],
ResourceLoader::inDebugMode()
);
}
/**
* @param ResourceLoaderContext|null $context
* @return array
*/
public function getDependencies( ResourceLoaderContext $context = null ) {
return [ 'mediawiki.language' ];
}
/**
* @return bool
*/
public function enableModuleContentVersion() {
return true;
}
}

View file

@ -1,53 +0,0 @@
<?php
/**
* ResourceLoader mediawiki.util module
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
/**
* ResourceLoader module for mediawiki.util
*
* @since 1.30
*/
class ResourceLoaderMediaWikiUtilModule extends ResourceLoaderFileModule {
/**
* @inheritDoc
*/
public function getScript( ResourceLoaderContext $context ) {
return ResourceLoader::makeConfigSetScript(
[ 'wgFragmentMode' => $this->getConfig()->get( 'FragmentMode' ) ]
)
. "\n"
. parent::getScript( $context );
}
/**
* @inheritDoc
*/
public function supportsURLLoading() {
return false;
}
/**
* @inheritDoc
*/
public function enableModuleContentVersion() {
return true;
}
}

View file

@ -1,102 +0,0 @@
<?php
/**
* ResourceLoader module for populating special characters data for some
* editing extensions to use.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
/**
* ResourceLoader module for populating special characters data for some
* editing extensions to use.
*/
class ResourceLoaderSpecialCharacterDataModule extends ResourceLoaderModule {
private $path = "resources/src/mediawiki.language/specialcharacters.json";
protected $targets = [ 'desktop', 'mobile' ];
/**
* Get all the dynamic data.
*
* @return array
*/
protected function getData() {
global $IP;
return json_decode( file_get_contents( "$IP/{$this->path}" ) );
}
/**
* @param ResourceLoaderContext $context
* @return string JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
return Xml::encodeJsCall(
'mw.language.setSpecialCharacters',
[
$this->getData()
],
ResourceLoader::inDebugMode()
);
}
/**
* @return bool
*/
public function enableModuleContentVersion() {
return true;
}
/**
* @param ResourceLoaderContext|null $context
* @return array
*/
public function getDependencies( ResourceLoaderContext $context = null ) {
return [ 'mediawiki.language' ];
}
/**
* @return array
*/
public function getMessages() {
return [
'special-characters-group-latin',
'special-characters-group-latinextended',
'special-characters-group-ipa',
'special-characters-group-symbols',
'special-characters-group-greek',
'special-characters-group-greekextended',
'special-characters-group-cyrillic',
'special-characters-group-arabic',
'special-characters-group-arabicextended',
'special-characters-group-persian',
'special-characters-group-hebrew',
'special-characters-group-bangla',
'special-characters-group-tamil',
'special-characters-group-telugu',
'special-characters-group-sinhala',
'special-characters-group-devanagari',
'special-characters-group-gujarati',
'special-characters-group-thai',
'special-characters-group-lao',
'special-characters-group-khmer',
'special-characters-group-canadianaboriginal',
'special-characters-title-endash',
'special-characters-title-emdash',
'special-characters-title-minus'
];
}
}

View file

@ -1,49 +0,0 @@
<?php
/**
* ResourceLoader module for the upload dialog configuration data.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
/**
* ResourceLoader module for the upload dialog configuration data.
*
* @since 1.27
*/
class ResourceLoaderUploadDialogModule extends ResourceLoaderModule {
protected $targets = [ 'desktop', 'mobile' ];
/**
* @param ResourceLoaderContext $context
* @return string JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
$config = $context->getResourceLoader()->getConfig();
return ResourceLoader::makeConfigSetScript( [
'wgUploadDialog' => $config->get( 'UploadDialog' ),
] );
}
/**
* @return bool
*/
public function enableModuleContentVersion() {
return true;
}
}

View file

@ -24,6 +24,8 @@ if ( !defined( 'MEDIAWIKI' ) ) {
die( 'Not an entry point.' );
}
global $wgResourceBasePath;
return [
/**
@ -1177,14 +1179,15 @@ return [
'upload-foreign-cant-upload',
]
],
'mediawiki.ForeignStructuredUpload.config' => [
'class' => ResourceLoaderUploadDialogModule::class,
],
'mediawiki.ForeignStructuredUpload' => [
'scripts' => 'resources/src/mediawiki.ForeignStructuredUpload.js',
'localBasePath' => "$IP/resources/src",
'remoteBasePath' => "$wgResourceBasePath/resources/src",
'packageFiles' => [
'mediawiki.ForeignStructuredUpload.js',
'config.json' => [ 'config' => [ 'UploadDialog' ] ],
],
'dependencies' => [
'mediawiki.ForeignUpload',
'mediawiki.ForeignStructuredUpload.config',
],
'messages' => [
'upload-foreign-cant-load-config',
@ -1327,8 +1330,12 @@ return [
]
],
'mediawiki.util' => [
'class' => ResourceLoaderMediaWikiUtilModule::class,
'scripts' => 'resources/src/mediawiki.util.js',
'localBasePath' => "$IP/resources/src",
'remoteBasePath' => "$wgResourceBasePath/resources/src",
'packageFiles' => [
'mediawiki.util.js',
'config.json' => [ 'config' => [ 'FragmentMode' ] ],
],
'dependencies' => [
'jquery.accessKeyLabel',
'mediawiki.RegExp',
@ -1570,9 +1577,31 @@ return [
],
'mediawiki.jqueryMsg' => [
// Add data for mediawiki.jqueryMsg, such as allowed tags
'class' => ResourceLoaderJqueryMsgModule::class,
'scripts' => 'resources/src/mediawiki.jqueryMsg/mediawiki.jqueryMsg.js',
'localBasePath' => "$IP/resources/src/mediawiki.jqueryMsg",
'remoteBasePath' => "$wgResourceBasePath/resources/src/mediawiki.jqueryMsg",
'packageFiles' => [
'mediawiki.jqueryMsg.js',
'parserDefaults.json' => [ 'callback' => function ( ResourceLoaderContext $context ) {
$tagData = Sanitizer::getRecognizedTagData();
$allowedHtmlElements = array_merge(
array_keys( $tagData['htmlpairs'] ),
array_diff(
array_keys( $tagData['htmlsingle'] ),
array_keys( $tagData['htmlsingleonly'] )
)
);
$magicWords = [
'SITENAME' => $context->getConfig()->get( 'Sitename' ),
];
Hooks::run( 'ResourceLoaderJqueryMsgModuleMagicWords', [ $context, &$magicWords ] );
return [
'allowedHtmlElements' => $allowedHtmlElements,
'magic' => $magicWords,
];
} ],
],
'dependencies' => [
'mediawiki.util',
'mediawiki.language',
@ -1592,10 +1621,54 @@ return [
)
],
'mediawiki.language.names' => [ 'class' => ResourceLoaderLanguageNamesModule::class ],
'mediawiki.language.names' => [
'localBasePath' => "$IP/resources/src/mediawiki.language",
'remoteBasePath' => "$wgResourceBasePath/resources/src/mediawiki.language",
'packageFiles' => [
'mediawiki.language.names.js',
'names.json' => [ 'callback' => function ( ResourceLoaderContext $context ) {
return Language::fetchLanguageNames( $context->getLanguage(), 'all' );
} ],
],
'dependencies' => 'mediawiki.language',
'targets' => [ 'desktop', 'mobile' ],
],
'mediawiki.language.specialCharacters' => [
'class' => ResourceLoaderSpecialCharacterDataModule::class
'localBasePath' => "$IP/resources/src/mediawiki.language",
'remoteBasePath' => "$wgResourceBasePath/resources/src/mediawiki.language",
'packageFiles' => [
'mediawiki.language.specialCharacters.js',
'specialcharacters.json'
],
'dependencies' => 'mediawiki.language',
'targets' => [ 'desktop', 'mobile' ],
'messages' => [
'special-characters-group-latin',
'special-characters-group-latinextended',
'special-characters-group-ipa',
'special-characters-group-symbols',
'special-characters-group-greek',
'special-characters-group-greekextended',
'special-characters-group-cyrillic',
'special-characters-group-arabic',
'special-characters-group-arabicextended',
'special-characters-group-persian',
'special-characters-group-hebrew',
'special-characters-group-bangla',
'special-characters-group-tamil',
'special-characters-group-telugu',
'special-characters-group-sinhala',
'special-characters-group-devanagari',
'special-characters-group-gujarati',
'special-characters-group-thai',
'special-characters-group-lao',
'special-characters-group-khmer',
'special-characters-group-canadianaboriginal',
'special-characters-title-endash',
'special-characters-title-emdash',
'special-characters-title-minus'
]
],
/* MediaWiki Libs */

View file

@ -25,7 +25,7 @@
// Config for uploads to local wiki.
// Can be overridden with foreign wiki config when #loadConfig is called.
this.config = mw.config.get( 'wgUploadDialog' );
this.config = require( './config.json' ).UploadDialog;
mw.ForeignUpload.call( this, target, apiconfig );
}

View file

@ -14,13 +14,14 @@
var oldParser,
slice = Array.prototype.slice,
parserDefaults = {
// Magic words and their expansions. Server-side data is added to this below.
magic: {
PAGENAME: mw.config.get( 'wgPageName' ),
PAGENAMEE: mw.util.wikiUrlencode( mw.config.get( 'wgPageName' ) )
},
// Whitelist for allowed HTML elements in wikitext.
// Self-closing tags are not currently supported.
// Can be populated via setParserDefaults().
// Filled in with server-side data below
allowedHtmlElements: [],
// Key tag name, value allowed attributes for that tag.
// See Sanitizer::setupAttributeWhitelist
@ -56,6 +57,9 @@
format: 'parse'
};
// Add in server-side data (allowedHtmlElements and magic words)
$.extend( true, parserDefaults, require( './parserDefaults.json' ) );
/**
* Wrapper around jQuery append that converts all non-objects to TextNode so append will not
* convert what it detects as an htmlString to an element.

View file

@ -0,0 +1,4 @@
( function () {
var names = require( './names.json' );
mw.language.setData( mw.config.get( 'wgUserLanguage' ), 'languageData', names );
}() );

View file

@ -0,0 +1,5 @@
( function () {
var specialCharacters = require( './specialcharacters.json' );
mw.language.setSpecialCharacters( specialCharacters );
module.exports = specialCharacters;
}() );

View file

@ -1,7 +1,9 @@
( function () {
'use strict';
var util;
var util,
config = require( './config.json' ),
origConfig = config;
/**
* Encode the string like PHP's rawurlencode
@ -50,6 +52,20 @@
/* Main body */
setOptionsForTest: function ( opts ) {
if ( !window.QUnit ) {
throw new Error( 'Modifying options not allowed outside unit tests' );
}
config = $.extend( {}, config, opts );
},
resetOptionsForTest: function () {
if ( !window.QUnit ) {
throw new Error( 'Resetting options not allowed outside unit tests' );
}
config = origConfig;
},
/**
* Encode the string like PHP's rawurlencode
*
@ -68,7 +84,7 @@
* @return {string} Encoded string
*/
escapeIdForAttribute: function ( str ) {
var mode = mw.config.get( 'wgFragmentMode' )[ 0 ];
var mode = config.FragmentMode[ 0 ];
return escapeIdInternal( str, mode );
},
@ -83,7 +99,7 @@
* @return {string} Encoded string
*/
escapeIdForLink: function ( str ) {
var mode = mw.config.get( 'wgFragmentMode' )[ 0 ];
var mode = config.FragmentMode[ 0 ];
return escapeIdInternal( str, mode );
},

View file

@ -148,10 +148,6 @@ class ResourceLoaderTest extends ResourceLoaderTestCase {
'SkinModule (FileModule subclass)' => [ true,
[ 'class' => ResourceLoaderSkinModule::class, 'scripts' => 'example.js' ]
],
'JqueryMsgModule (FileModule subclass)' => [ true, [
'class' => ResourceLoaderJqueryMsgModule::class,
'scripts' => 'example.js',
] ],
'WikiModule' => [ false, [
'class' => ResourceLoaderWikiModule::class,
'scripts' => [ 'MediaWiki:Example.js' ],

View file

@ -80,6 +80,7 @@
},
teardown: function () {
$.fn.updateTooltipAccessKeys.setTestMode( false );
mw.util.resetOptionsForTest();
},
messages: {
// Used by accessKeyLabel in test for addPortletLink
@ -114,7 +115,7 @@
// Distant future: no legacy fallbacks
[ allNew, text, html5Encoded ]
].forEach( function ( testCase ) {
mw.config.set( 'wgFragmentMode', testCase[ 0 ] );
mw.util.setOptionsForTest( { FragmentMode: testCase[ 0 ] } );
assert.strictEqual( util.escapeIdForAttribute( testCase[ 1 ] ), testCase[ 2 ] );
} );
@ -141,7 +142,7 @@
// Distant future: no legacy fallbacks
[ allNew, text, html5Encoded ]
].forEach( function ( testCase ) {
mw.config.set( 'wgFragmentMode', testCase[ 0 ] );
mw.util.setOptionsForTest( { FragmentMode: testCase[ 0 ] } );
assert.strictEqual( util.escapeIdForLink( testCase[ 1 ] ), testCase[ 2 ] );
} );
@ -210,22 +211,22 @@
href = util.getUrl( '#Fragment', { action: 'edit' } );
assert.strictEqual( href, '/w/index.php?action=edit#Fragment', 'empty title with query string and fragment' );
mw.config.set( 'wgFragmentMode', [ 'legacy' ] );
mw.util.setOptionsForTest( { FragmentMode: [ 'legacy' ] } );
href = util.getUrl( 'Foo:Sandbox \xC4#Fragment \xC4', { action: 'edit' } );
assert.strictEqual( href, '/w/index.php?title=Foo:Sandbox_%C3%84&action=edit#Fragment_.C3.84', 'title with query string, fragment, and special characters' );
mw.config.set( 'wgFragmentMode', [ 'html5' ] );
mw.util.setOptionsForTest( { FragmentMode: [ 'html5' ] } );
href = util.getUrl( 'Foo:Sandbox \xC4#Fragment \xC4', { action: 'edit' } );
assert.strictEqual( href, '/w/index.php?title=Foo:Sandbox_%C3%84&action=edit#Fragment_Ä', 'title with query string, fragment, and special characters' );
href = util.getUrl( 'Foo:%23#Fragment', { action: 'edit' } );
assert.strictEqual( href, '/w/index.php?title=Foo:%2523&action=edit#Fragment', 'title containing %23 (#), fragment, and a query string' );
mw.config.set( 'wgFragmentMode', [ 'legacy' ] );
mw.util.setOptionsForTest( { FragmentMode: [ 'legacy' ] } );
href = util.getUrl( '#+&=:;@$-_.!*/[]<>\'§', { action: 'edit' } );
assert.strictEqual( href, '/w/index.php?action=edit#.2B.26.3D:.3B.40.24-_..21.2A.2F.5B.5D.3C.3E.27.C2.A7', 'fragment with various characters' );
mw.config.set( 'wgFragmentMode', [ 'html5' ] );
mw.util.setOptionsForTest( { FragmentMode: [ 'html5' ] } );
href = util.getUrl( '#+&=:;@$-_.!*/[]<>\'§', { action: 'edit' } );
assert.strictEqual( href, '/w/index.php?action=edit#+&=:;@$-_.!*/[]<>\'§', 'fragment with various characters' );
} );