* SpecialPageFactory::setGroup was removed (deprecated in 1.21).
* SpecialPageFactory::getGroup was removed (deprecated in 1.21).
* DatabaseBase::ignoreErrors() is now protected.
+* BREAKING CHANGE: mediawiki.legacy.ajax has been removed, following
+ a lengthy deprecation period.
== Compatibility ==
'CurlHttpRequest' => __DIR__ . '/includes/HttpFunctions.php',
'DBAccessBase' => __DIR__ . '/includes/dao/DBAccessBase.php',
'DBAccessError' => __DIR__ . '/includes/db/LBFactory.php',
+ 'DBAccessObjectUtils' => __DIR__ . '/includes/dao/DBAccessObjectUtils.php',
'DBConnRef' => __DIR__ . '/includes/db/DBConnRef.php',
'DBConnectionError' => __DIR__ . '/includes/db/DatabaseError.php',
'DBError' => __DIR__ . '/includes/db/DatabaseError.php',
'SQLiteField' => __DIR__ . '/includes/db/DatabaseSqlite.php',
'SVGMetadataExtractor' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
'SVGReader' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
+ 'SamplingStatsdClient' => __DIR__ . '/includes/libs/SamplingStatsdClient.php',
'Sanitizer' => __DIR__ . '/includes/Sanitizer.php',
'SavepointPostgres' => __DIR__ . '/includes/db/DatabasePostgres.php',
'ScopedCallback' => __DIR__ . '/includes/libs/ScopedCallback.php',
$statsdHost = $statsdServer[0];
$statsdPort = isset( $statsdServer[1] ) ? $statsdServer[1] : 8125;
$statsdSender = new SocketSender( $statsdHost, $statsdPort );
- $statsdClient = new StatsdClient( $statsdSender, true, false );
+ $statsdClient = new SamplingStatsdClient( $statsdSender, true, false );
$statsdClient->send( $context->getStats()->getBuffer() );
} catch ( Exception $ex ) {
MWExceptionHandler::logException( $ex );
}
$ret .= $this->buildCssLinks() . "\n";
-
$ret .= $this->getHeadScripts() . "\n";
foreach ( $this->mHeadItems as $item ) {
}
/**
- * @todo Document
+ * Construct neccecary html and loader preset states to load modules on a page.
+ *
+ * Use getHtmlFromLoaderLinks() to convert this array to HTML.
+ *
* @param array|string $modules One or more module names
* @param string $only ResourceLoaderModule TYPE_ class constant
- * @param array $extraQuery Array with extra query parameters to add to each
- * request. array( param => value ).
- * @param bool $loadCall If true, output an (asynchronous) mw.loader.load()
- * call rather than a "<script src='...'>" tag.
- * @return string The html "<script>", "<link>" and "<style>" tags
- */
- public function makeResourceLoaderLink( $modules, $only, array $extraQuery = array(),
- $loadCall = false
- ) {
+ * @param array $extraQuery [optional] Array with extra query parameters for the request
+ * @return array A list of HTML strings and array of client loader preset states
+ */
+ public function makeResourceLoaderLink( $modules, $only, array $extraQuery = array() ) {
$modules = (array)$modules;
$links = array(
if ( ResourceLoader::inDebugMode() ) {
// Recursively call us for every item
foreach ( $modules as $name ) {
- $link = $this->makeResourceLoaderLink( $name, $only );
+ $link = $this->makeResourceLoaderLink( $name, $only, $extraQuery );
$links['html'] = array_merge( $links['html'], $link['html'] );
$links['states'] += $link['states'];
}
// Automatically select style/script elements
if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
$link = Html::linkedStyle( $url );
- } elseif ( $loadCall ) {
- $link = ResourceLoader::makeInlineScript(
- Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
- );
} else {
- $link = Html::linkedScript( $url );
- if ( !$context->getRaw() && !$isRaw ) {
- // Wrap only=script / only=combined requests in a conditional as
- // browsers not supported by the startup module would unconditionally
- // execute this module. Otherwise users will get "ReferenceError: mw is
- // undefined" or "jQuery is undefined" from e.g. a "site" module.
+ if ( $context->getRaw() || $isRaw ) {
+ // Startup module can't load itself, needs to use <script> instead of mw.loader.load
+ $link = Html::element( 'script', array(
+ // In SpecialJavaScriptTest, QUnit must load synchronous
+ 'async' => !isset( $extraQuery['sync'] ),
+ 'src' => $url
+ ) );
+ } else {
$link = ResourceLoader::makeInlineScript(
- Xml::encodeJsCall( 'document.write', array( $link ) )
+ Xml::encodeJsCall( 'mw.loader.load', array( $url ) )
);
}
-
- // For modules requested directly in the html via <link> or <script>,
- // tell mw.loader they are being loading to prevent duplicate requests.
- foreach ( $grpModules as $key => $module ) {
- // Don't output state=loading for the startup module..
- if ( $key !== 'startup' ) {
- $links['states'][$key] = 'loading';
- }
- }
}
if ( $group == 'noscript' ) {
* @return string HTML fragment
*/
function getHeadScripts() {
- // Startup - this will immediately load jquery and mediawiki modules
$links = array();
+
+ // Client profile classes for <html>. Allows for easy hiding/showing of UI components.
+ // Must be done synchronously on every page to avoid flashes of wrong content.
+ // Note: This class distinguishes MediaWiki-supported JavaScript from the rest.
+ // The "rest" includes browsers that support JavaScript but not supported by our runtime.
+ // For the performance benefit of the majority, this is added unconditionally here and is
+ // then fixed up by the startup module for unsupported browsers.
+ $links[] = Html::inlineScript(
+ 'document.documentElement.className = document.documentElement.className'
+ . '.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );'
+ );
+
+ // Startup - this provides the client with the module manifest and loads jquery and mediawiki base modules
$links[] = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS );
// Load config before anything else
);
if ( $this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) {
- $links[] = $this->getScriptsForBottomQueue( true );
+ $links[] = $this->getScriptsForBottomQueue();
}
return self::getHtmlFromLoaderLinks( $links );
* 'bottom', legacy scripts ($this->mScripts), user preferences, site JS
* and user JS.
*
- * @param bool $inHead If true, this HTML goes into the "<head>",
- * if false it goes into the "<body>".
+ * @param bool $unused Previously used to let this method change its output based
+ * on whether it was called by getHeadScripts() or getBottomScripts().
* @return string
*/
- function getScriptsForBottomQueue( $inHead ) {
+ function getScriptsForBottomQueue( $unused = null ) {
// Scripts "only" requests marked for bottom inclusion
// If we're in the <head>, use load() calls rather than <script src="..."> tags
$links = array();
$links[] = $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ),
- ResourceLoaderModule::TYPE_SCRIPTS, /* $extraQuery = */ array(),
- /* $loadCall = */ $inHead
+ ResourceLoaderModule::TYPE_SCRIPTS
);
$links[] = $this->makeResourceLoaderLink( $this->getModuleStyles( true, 'bottom' ),
- ResourceLoaderModule::TYPE_STYLES, /* $extraQuery = */ array(),
- /* $loadCall = */ $inHead
+ ResourceLoaderModule::TYPE_STYLES
);
// Modules requests - let the client calculate dependencies and batch requests as it likes
$modules = $this->getModules( true, 'bottom' );
if ( $modules ) {
$links[] = ResourceLoader::makeInlineScript(
- Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) )
+ Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
);
}
// We're on a preview of a JS subpage. Exclude this page from the user module (T28283)
// and include the draft contents as a raw script instead.
$links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_COMBINED,
- array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
+ array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() )
);
// Load the previewed JS
$links[] = ResourceLoader::makeInlineScript(
// the excluded subpage.
} else {
// Include the user module normally, i.e., raw to avoid it being wrapped in a closure.
- $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_COMBINED,
- /* $extraQuery = */ array(), /* $loadCall = */ $inHead
- );
+ $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_COMBINED );
}
// Group JS is only enabled if site JS is enabled.
- $links[] = $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED,
- /* $extraQuery = */ array(), /* $loadCall = */ $inHead
- );
+ $links[] = $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED );
return self::getHtmlFromLoaderLinks( $links );
}
// In case the skin wants to add bottom CSS
$this->getSkin()->setupSkinUserCss( $this );
- // Optimise jQuery ready event cross-browser.
- // This also enforces $.isReady to be true at </body> which fixes the
- // mw.loader bug in Firefox with using document.write between </body>
- // and the DOMContentReady event (bug 47457).
- $html = Html::inlineScript( 'if(window.jQuery)jQuery.ready();' );
-
- if ( !$this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) {
- $html .= $this->getScriptsForBottomQueue( false );
+ if ( $this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) {
+ // Already handled by getHeadScripts()
+ return '';
}
-
- return $html;
+ return $this->getScriptsForBottomQueue();
}
/**
# Double-quoted
return $set[3];
} elseif ( !isset( $set[2] ) ) {
- # In XHTML, attributes must have a value.
- # For 'reduced' form, return explicitly the attribute name here.
- return $set[1];
+ # In XHTML, attributes must have a value so return an empty string.
+ # See "Empty attribute syntax",
+ # http://www.w3.org/TR/html5/syntax.html#syntax-attribute-name
+ return "";
} else {
throw new MWException( "Tag conditions not met. This should never happen and is a bug." );
}
--- /dev/null
+<?php
+/**
+ * This file contains database access object related constants.
+ *
+ * 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
+ * @ingroup Database
+ */
+
+/**
+ * Helper class for DAO classes
+ *
+ * @since 1.26
+ */
+class DBAccessObjectUtils {
+ /**
+ * @param integer $bitfield
+ * @param integer $flags IDBAccessObject::READ_* constant
+ * @return bool Bitfield has flag $flag set
+ */
+ public static function hasFlags( $bitfield, $flags ) {
+ return ( $bitfield & $flags ) == $flags;
+ }
+
+ /**
+ * Get an appropriate DB index and options for a query
+ *
+ * @param integer $bitfield
+ * @return array (DB_MASTER/DB_SLAVE, SELECT options array)
+ */
+ public static function getDBOptions( $bitfield ) {
+ $index = self::hasFlags( $bitfield, IDBAccessObject::READ_LATEST )
+ ? DB_MASTER
+ : DB_SLAVE;
+
+ $options = array();
+ if ( self::hasFlags( $bitfield, IDBAccessObject::READ_EXCLUSIVE ) ) {
+ $options[] = 'FOR UPDATE';
+ } elseif ( self::hasFlags( $bitfield, IDBAccessObject::READ_LOCKING ) ) {
+ $options[] = 'LOCK IN SHARE MODE';
+ }
+
+ return array( $index, $options );
+ }
+}
\ No newline at end of file
* @return EnqueueJob
*/
public static function newFromJobsByWiki( array $jobsByWiki ) {
+ $deduplicate = true;
+
$jobMapsByWiki = array();
foreach ( $jobsByWiki as $wiki => $jobs ) {
$jobMapsByWiki[$wiki] = array();
} else {
throw new InvalidArgumentException( "Jobs must be of type JobSpecification." );
}
+ $deduplicate = $deduplicate && $job->ignoreDuplicates();
}
}
- return new self( Title::newMainPage(), array( 'jobsByWiki' => $jobMapsByWiki ) );
+ $eJob = new self( Title::newMainPage(), array( 'jobsByWiki' => $jobMapsByWiki ) );
+ // If *all* jobs to be pushed are to be de-duplicated (a common case), then
+ // de-duplicate this whole job itself to avoid build up in high traffic cases
+ $eJob->removeDuplicates = $deduplicate;
+
+ return $eJob;
}
public function run() {
* @file
*/
+use Liuggio\StatsdClient\Entity\StatsdData;
use Liuggio\StatsdClient\Entity\StatsdDataInterface;
use Liuggio\StatsdClient\Factory\StatsdDataFactory;
return $entity;
}
+ /**
+ * @return StatsdData[]
+ */
public function getBuffer() {
return $this->buffer;
}
--- /dev/null
+<?php
+/**
+ * Copyright 2015
+ *
+ * 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
+ */
+
+use Liuggio\StatsdClient\StatsdClient;
+use Liuggio\StatsdClient\Entity\StatsdData;
+use Liuggio\StatsdClient\Entity\StatsdDataInterface;
+
+/**
+ * A statsd client that applies the sampling rate to the data items before sending them.
+ *
+ * @since 1.26
+ */
+class SamplingStatsdClient extends StatsdClient {
+ /**
+ * Sets sampling rate for all items in $data.
+ * The sample rate specified in a StatsdData entity overrides the sample rate specified here.
+ *
+ * {@inheritDoc}
+ */
+ public function appendSampleRate( $data, $sampleRate = 1 ) {
+ if ( $sampleRate < 1 ) {
+ array_walk( $data, function( $item ) use ( $sampleRate ) {
+ /** @var $item StatsdData */
+ if ( $item->getSampleRate() === 1 ) {
+ $item->setSampleRate( $sampleRate );
+ }
+ });
+ }
+
+ return $data;
+ }
+
+ /**
+ * Sample the metrics according to their sample rate and send the remaining ones.
+ *
+ * {@inheritDoc}
+ */
+ public function send( $data, $sampleRate = 1 ) {
+ if ( !is_array( $data ) ) {
+ $data = array( $data );
+ }
+ if ( !$data ) {
+ return;
+ }
+ foreach ( $data as $item ) {
+ if ( !( $item instanceof StatsdDataInterface ) ) {
+ throw new InvalidArgumentException(
+ 'SamplingStatsdClient does not accept stringified messages' );
+ }
+ }
+
+ // add sampling
+ if ( $sampleRate < 1 ) {
+ $data = $this->appendSampleRate( $data, $sampleRate );
+ }
+ $data = $this->sampleData( $data );
+
+ $messages = array_map( 'strval', $data );
+
+ // reduce number of packets
+ if ( $this->getReducePacket() ) {
+ $data = $this->reduceCount( $data );
+ }
+ //failures in any of this should be silently ignored if ..
+ $written = 0;
+ try {
+ $fp = $this->getSender()->open();
+ if ( !$fp ) {
+ return;
+ }
+ foreach ( $messages as $message ) {
+ $written += $this->getSender()->write( $fp, $message );
+ }
+ $this->getSender()->close( $fp );
+ } catch ( Exception $e ) {
+ $this->throwException( $e );
+ }
+
+ return $written;
+ }
+
+ /**
+ * Throw away some of the data according to the sample rate.
+ * @param StatsdDataInterface[] $data
+ * @return array
+ * @throws LogicException
+ */
+ protected function sampleData( $data ) {
+ $newData = array();
+ $mt_rand_max = mt_getrandmax();
+ foreach ( $data as $item ) {
+ $samplingRate = $item->getSampleRate();
+ if ( $samplingRate <= 0.0 || $samplingRate > 1.0 ) {
+ throw new LogicException( 'Sampling rate shall be within ]0, 1]' );
+ }
+ if (
+ $samplingRate === 1 ||
+ ( mt_rand() / $mt_rand_max <= $samplingRate )
+ ) {
+ $newData[] = $item;
+ }
+ }
+ return $newData;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function throwException( Exception $exception ) {
+ if ( !$this->getFailSilently() ) {
+ throw $exception;
+ }
+ }
+}
return;
}
- if ( $from === self::READ_LOCKING ) {
- $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle, array( 'FOR UPDATE' ) );
- } elseif ( $from === self::READ_LATEST ) {
- $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
- } elseif ( $from === self::READ_NORMAL ) {
- $data = $this->pageDataFromTitle( wfGetDB( DB_SLAVE ), $this->mTitle );
+ if ( is_int( $from ) ) {
+ list( $index, $opts ) = DBAccessObjectUtils::getDBOptions( $from );
+ $data = $this->pageDataFromTitle( wfGetDB( $index ), $this->mTitle, $opts );
+
if ( !$data
+ && $index == DB_SLAVE
&& wfGetLB()->getServerCount() > 1
&& wfGetLB()->hasOrMadeRecentMasterChanges()
) {
$from = self::READ_LATEST;
- $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
+ list( $index, $opts ) = DBAccessObjectUtils::getDBOptions( $from );
+ $data = $this->pageDataFromTitle( wfGetDB( $index ), $this->mTitle, $opts );
}
} else {
// No idea from where the caller got this data, assume slave database.
}, array(
'$VARS.wgLegacyJavaScriptGlobals' => $this->getConfig()->get( 'LegacyJavaScriptGlobals' ),
'$VARS.configuration' => $this->getConfigSettings( $context ),
- '$VARS.baseModulesScript' => Html::linkedScript( self::getStartupModulesUrl( $context ) ),
+ '$VARS.baseModulesUri' => self::getStartupModulesUrl( $context ),
) );
$pairs['$CODE.registrations()'] = str_replace( "\n", "\n\t", trim( $this->getModuleRegistrations( $context ) ) );
// Add various resources if required
if ( $wgUseAjax ) {
- $modules['legacy'][] = 'mediawiki.legacy.ajax';
-
if ( $wgEnableAPI ) {
if ( $wgEnableWriteAPI && $wgAjaxWatch && $user->isLoggedIn()
&& $user->isAllowed( 'writeapi' )
$query['only'] = 'scripts';
$startupContext = new ResourceLoaderContext( $rl, new FauxRequest( $query ) );
+ $query['raw'] = true;
+
$modules = $rl->getTestModuleNames( 'qunit' );
+ // Disable autostart because we load modules asynchronously. By default, QUnit would start
+ // at domready when there are no tests loaded and also fire 'QUnit.done' which then instructs
+ // Karma to end the run before the tests even started.
+ $qunitConfig = 'QUnit.config.autostart = false;'
+ . 'if (window.__karma__) {'
+ // karma-qunit's use of autostart=false and QUnit.start conflicts with ours.
+ // Hack around this by replacing 'karma.loaded' with a no-op and call it ourselves later.
+ // See <https://github.com/karma-runner/karma-qunit/issues/27>.
+ . 'window.__karma__.loaded = function () {};'
+ . '}';
+
// The below is essentially a pure-javascript version of OutputPage::getHeadScripts.
$startup = $rl->makeModuleResponse( $startupContext, array(
'startup' => $rl->getModule( 'startup' ),
'user.options' => $rl->getModule( 'user.options' ),
'user.tokens' => $rl->getModule( 'user.tokens' ),
) );
- $code .= Xml::encodeJsCall( 'mw.loader.load', array( $modules ) );
+ // Catch exceptions (such as "dependency missing" or "unknown module") so that we
+ // always start QUnit. Re-throw so that they are caught and reported as global exceptions
+ // by QUnit and Karma.
+ $code .= '(function () {'
+ . 'var start = window.__karma__ ? window.__karma__.start : QUnit.start;'
+ . 'try {'
+ . 'mw.loader.using( ' . Xml::encodeJsVar( $modules ) . ' ).always( start );'
+ . '} catch ( e ) { start(); throw e; }'
+ . '}());';
header( 'Content-Type: text/javascript; charset=utf-8' );
header( 'Cache-Control: private, no-cache, must-revalidate' );
header( 'Pragma: no-cache' );
+ echo $qunitConfig;
echo $startup;
- echo "\n";
- // Note: The following has to be wrapped in a script tag because the startup module also
- // writes a script tag (the one loading mediawiki.js). Script tags are synchronous, block
- // each other, and run in order. But they don't nest. The code appended after the startup
- // module runs before the added script tag is parsed and executed.
- echo Xml::encodeJsCall( 'document.write', array( Html::inlineScript( $code ) ) );
+ // The following has to be deferred via RLQ because the startup module is asynchronous.
+ echo ResourceLoader::makeLoaderConditionalScript( $code );
}
private function plainQUnit() {
$out = $this->getOutput();
$out->disable();
- $url = $this->getPageTitle( 'qunit/export' )->getFullURL( array(
- 'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false',
- ) );
-
$styles = $out->makeResourceLoaderLink( 'jquery.qunit',
ResourceLoaderModule::TYPE_STYLES
);
- // Use 'raw' since this is a plain HTML page without ResourceLoader
+
+ // Use 'raw' because QUnit loads before ResourceLoader initialises (omit mw.loader.state call)
+ // Use 'test' to ensure OutputPage doesn't use the "async" attribute because QUnit must
+ // load before qunit/export.
$scripts = $out->makeResourceLoaderLink( 'jquery.qunit',
ResourceLoaderModule::TYPE_SCRIPTS,
- array( 'raw' => 'true' )
+ array( 'raw' => true, 'sync' => true )
);
$head = implode( "\n", array_merge( $styles['html'], $scripts['html'] ) );
$summary
<div id="qunit"></div>
HTML;
+
+ $url = $this->getPageTitle( 'qunit/export' )->getFullURL( array(
+ 'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false',
+ ) );
$html .= "\n" . Html::linkedScript( $url );
header( 'Content-Type: text/html; charset=utf-8' );
/* MediaWiki Legacy */
- 'mediawiki.legacy.ajax' => array(
- 'scripts' => 'resources/src/mediawiki.legacy/ajax.js',
- 'dependencies' => array(
- 'mediawiki.util',
- 'mediawiki.legacy.wikibits',
- ),
- 'position' => 'top',
- ),
'mediawiki.legacy.commonPrint' => array(
'position' => 'top',
'styles' => array(
+++ /dev/null
-/**
- * Remote Scripting Library
- * Copyright 2005 modernmethod, inc
- * Under the open source BSD license
- * http://www.modernmethod.com/sajax/
- */
-
-/*jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
-/*global alert */
-( function ( mw ) {
-
- /**
- * if sajax_debug_mode is true, this function outputs given the message into
- * the element with id = sajax_debug; if no such element exists in the document,
- * it is injected.
- */
- function debug( text ) {
- if ( !window.sajax_debug_mode ) {
- return false;
- }
-
- var b, m,
- e = document.getElementById( 'sajax_debug' );
-
- if ( !e ) {
- e = document.createElement( 'p' );
- e.className = 'sajax_debug';
- e.id = 'sajax_debug';
-
- b = document.getElementsByTagName( 'body' )[0];
-
- if ( b.firstChild ) {
- b.insertBefore( e, b.firstChild );
- } else {
- b.appendChild( e );
- }
- }
-
- m = document.createElement( 'div' );
- m.appendChild( document.createTextNode( text ) );
-
- e.appendChild( m );
-
- return true;
- }
-
- /**
- * Compatibility wrapper for creating a new XMLHttpRequest object.
- */
- function createXhr() {
- debug( 'sajax_init_object() called..' );
- var a;
- try {
- // Try the new style before ActiveX so we don't
- // unnecessarily trigger warnings in IE 7 when
- // set to prompt about ActiveX usage
- a = new XMLHttpRequest();
- } catch ( xhrE ) {
- try {
- a = new window.ActiveXObject( 'Msxml2.XMLHTTP' );
- } catch ( msXmlE ) {
- try {
- a = new window.ActiveXObject( 'Microsoft.XMLHTTP' );
- } catch ( msXhrE ) {
- a = null;
- }
- }
- }
- if ( !a ) {
- debug( 'Could not create connection object.' );
- }
-
- return a;
- }
-
- /**
- * Perform an AJAX call to MediaWiki. Calls are handled by AjaxDispatcher.php
- * func_name - the name of the function to call. Must be registered in $wgAjaxExportList
- * args - an array of arguments to that function
- * target - the target that will handle the result of the call. If this is a function,
- * if will be called with the XMLHttpRequest as a parameter; if it's an input
- * element, its value will be set to the resultText; if it's another type of
- * element, its innerHTML will be set to the resultText.
- *
- * Example:
- * sajax_do_call( 'doFoo', [1, 2, 3], document.getElementById( 'showFoo' ) );
- *
- * This will call the doFoo function via MediaWiki's AjaxDispatcher, with
- * (1, 2, 3) as the parameter list, and will show the result in the element
- * with id = showFoo
- */
- function doAjaxRequest( func_name, args, target ) {
- var i, x, uri, post_data;
- uri = mw.util.wikiScript() + '?action=ajax';
- if ( window.sajax_request_type === 'GET' ) {
- if ( uri.indexOf( '?' ) === -1 ) {
- uri = uri + '?rs=' + encodeURIComponent( func_name );
- } else {
- uri = uri + '&rs=' + encodeURIComponent( func_name );
- }
- for ( i = 0; i < args.length; i++ ) {
- uri = uri + '&rsargs[]=' + encodeURIComponent( args[i] );
- }
- // uri = uri + '&rsrnd=' + new Date().getTime();
- post_data = null;
- } else {
- post_data = 'rs=' + encodeURIComponent( func_name );
- for ( i = 0; i < args.length; i++ ) {
- post_data = post_data + '&rsargs[]=' + encodeURIComponent( args[i] );
- }
- }
- x = createXhr();
- if ( !x ) {
- alert( 'AJAX not supported' );
- return false;
- }
-
- try {
- x.open( window.sajax_request_type, uri, true );
- } catch ( e ) {
- if ( location.hostname === 'localhost' ) {
- alert( 'Your browser blocks XMLHttpRequest to "localhost", try using a real hostname for development/testing.' );
- }
- throw e;
- }
- if ( window.sajax_request_type === 'POST' ) {
- x.setRequestHeader( 'Method', 'POST ' + uri + ' HTTP/1.1' );
- x.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
- }
- x.setRequestHeader( 'Pragma', 'cache=yes' );
- x.setRequestHeader( 'Cache-Control', 'no-transform' );
- x.onreadystatechange = function () {
- if ( x.readyState !== 4 ) {
- return;
- }
-
- debug( 'received (' + x.status + ' ' + x.statusText + ') ' + x.responseText );
-
- // if ( x.status != 200 )
- // alert( 'Error: ' + x.status + ' ' + x.statusText + ': ' + x.responseText );
- // else
-
- if ( typeof target === 'function' ) {
- target( x );
- } else if ( typeof target === 'object' ) {
- if ( target.tagName === 'INPUT' ) {
- if ( x.status === 200 ) {
- target.value = x.responseText;
- }
- // else alert( 'Error: ' + x.status + ' ' + x.statusText + ' (' + x.responseText + ')' );
- } else {
- if ( x.status === 200 ) {
- target.innerHTML = x.responseText;
- } else {
- target.innerHTML = '<div class="error">Error: ' + x.status +
- ' ' + x.statusText + ' (' + x.responseText + ')</div>';
- }
- }
- } else {
- alert( 'Bad target for sajax_do_call: not a function or object: ' + target );
- }
- };
-
- debug( func_name + ' uri = ' + uri + ' / post = ' + post_data );
- x.send( post_data );
- debug( func_name + ' waiting..' );
-
- return true;
- }
-
- /**
- * @return {boolean} Whether the browser supports AJAX
- */
- function wfSupportsAjax() {
- var request = createXhr(),
- supportsAjax = request ? true : false;
-
- request = undefined;
- return supportsAjax;
- }
-
- // Expose + Mark as deprecated
- var deprecationNotice = 'Sajax is deprecated, use jQuery.ajax or mediawiki.api instead.';
-
- // Variables
- mw.log.deprecate( window, 'sajax_debug_mode', false, deprecationNotice );
- mw.log.deprecate( window, 'sajax_request_type', 'GET', deprecationNotice );
- // Methods
- mw.log.deprecate( window, 'sajax_debug', debug, deprecationNotice );
- mw.log.deprecate( window, 'sajax_init_object', createXhr, deprecationNotice );
- mw.log.deprecate( window, 'sajax_do_call', doAjaxRequest, deprecationNotice );
- mw.log.deprecate( window, 'wfSupportsAjax', wfSupportsAjax, deprecationNotice );
-
-}( mediaWiki ) );
var $spinner, href, rcid, apiRequest;
// Start preloading the notification module (normally loaded by mw.notify())
- mw.loader.load( ['mediawiki.notification'], null, true );
+ mw.loader.load( 'mediawiki.notification' );
// Hide the link and create a spinner to show it inside the brackets.
$spinner = $.createSpinner( {
( function ( mw, $ ) {
- mw.page = {};
+ // Support: MediaWiki < 1.26
+ // Cached HTML will not yet have this from OutputPage::getHeadScripts.
+ document.documentElement.className = document.documentElement.className
+ .replace( /(^|\s)client-nojs(\s|$)/, '$1client-js$2' );
- // Client profile classes for <html>
- // Allows for easy hiding/showing of JS or no-JS-specific UI elements
- $( document.documentElement )
- .addClass( 'client-js' )
- .removeClass( 'client-nojs' );
+ mw.page = {};
$( function () {
mw.util.init();
var action, api, $link;
// Start preloading the notification module (normally loaded by mw.notify())
- mw.loader.load( ['mediawiki.notification'], null, true );
+ mw.loader.load( 'mediawiki.notification' );
action = mwUriGetAction( this.href );
/**
* Creates an mw.widgets.CalendarWidget object.
*
+ * You will most likely want to use mw.widgets.DateInputWidget instead of CalendarWidget directly.
+ *
* @class
* @extends OO.ui.Widget
* @mixins OO.ui.mixin.TabIndexedElement
* @constructor
* @param {Object} [config] Configuration options
* @cfg {string} [precision='day'] Date precision to use, 'day' or 'month'
- * @cfg {string|null} [date=null] Day or month date (depending on `precision`), in the
- * format 'YYYY-MM-DD' or 'YYYY-MM'. When null, defaults to current date.
+ * @cfg {string|null} [date=null] Day or month date (depending on `precision`), in the format
+ * 'YYYY-MM-DD' or 'YYYY-MM'. When null, the calendar will show today's date, but not select
+ * it.
*/
mw.widgets.CalendarWidget = function MWWCalendarWidget( config ) {
// Config initialization
if (
this.displayLayer === this.previousDisplayLayer &&
+ this.date === this.previousDate &&
this.previousMoment &&
this.previousMoment.isSame( this.moment, this.precision === 'month' ? 'month' : 'day' )
) {
this.previousMoment = moment( this.moment );
this.previousDisplayLayer = this.displayLayer;
+ this.previousDate = this.date;
this.$body.on( 'click', this.onBodyClick.bind( this ) );
};
* Set the date.
*
* @param {string|null} [date=null] Day or month date, in the format 'YYYY-MM-DD' or 'YYYY-MM'.
- * When null, defaults to current date. When invalid, the date is not changed.
+ * When null, the calendar will show today's date, but not select it. When invalid, the date
+ * is not changed.
*/
mw.widgets.CalendarWidget.prototype.setDate = function ( date ) {
var mom = date !== null ? moment( date, this.getDateFormat() ) : moment();
if ( mom.isValid() ) {
this.moment = mom;
- this.setDateFromMoment();
+ if ( date !== null ) {
+ this.setDateFromMoment();
+ } else if ( this.date !== null ) {
+ this.date = null;
+ this.emit( 'change', this.date );
+ }
this.displayLayer = this.getDisplayLayers()[ 0 ];
this.updateUI();
}
* Get current date, in the format 'YYYY-MM-DD' or 'YYYY-MM', depending on precision. Digits will
* not be localised.
*
- * @returns {string} Date string
+ * @returns {string|null} Date string
*/
mw.widgets.CalendarWidget.prototype.getDate = function () {
return this.date;
}
.mw-widget-calendarWidget-day-today {
- border: 1px solid #3787fb;
+ box-shadow: inset 0 0 0 1px #3787fb;
border-radius: ((@calendarHeight / 7) / 2);
- margin: -1px;
}
.mw-widget-calendarWidget-item-selected {
&.mw-widget-calendarWidget-day-heading {
border-radius: ((@calendarHeight / 7) / 4);
// Hide the border from .mw-widget-calendarWidget-day-today
- border: 0;
- margin: 0;
+ box-shadow: none;
}
&.mw-widget-calendarWidget-month {
/**
* Creates an mw.widgets.DateInputWidget object.
*
+ * @example
+ * // Date input widget showcase
+ * var fieldset = new OO.ui.FieldsetLayout( {
+ * items: [
+ * new OO.ui.FieldLayout(
+ * new mw.widgets.DateInputWidget(),
+ * {
+ * align: 'top',
+ * label: 'Select date'
+ * }
+ * ),
+ * new OO.ui.FieldLayout(
+ * new mw.widgets.DateInputWidget( { precision: 'month' } ),
+ * {
+ * align: 'top',
+ * label: 'Select month'
+ * }
+ * ),
+ * new OO.ui.FieldLayout(
+ * new mw.widgets.DateInputWidget( {
+ * inputFormat: 'DD.MM.YYYY',
+ * displayFormat: 'Do [of] MMMM [anno Domini] YYYY'
+ * } ),
+ * {
+ * align: 'top',
+ * label: 'Select date (custom formats)'
+ * }
+ * )
+ * ]
+ * } );
+ * $( 'body' ).append( fieldset.$element );
+ *
+ * The value is stored in 'YYYY-MM-DD' or 'YYYY-MM' format:
+ *
+ * @example
+ * // Accessing values in a date input widget
+ * var dateInput = new mw.widgets.DateInputWidget();
+ * var $label = $( '<p>' );
+ * $( 'body' ).append( $label, dateInput.$element );
+ * dateInput.on( 'change', function () {
+ * // The value will always be a valid date or empty string, malformed input is ignored
+ * var date = dateInput.getValue();
+ * $label.text( 'Selected date: ' + ( date || '(none)' ) );
+ * } );
+ *
* @class
* @extends OO.ui.InputWidget
*
* @private
*/
mw.widgets.DateInputWidget.prototype.activate = function () {
- if ( this.getValue() === '' ) {
- // Setting today's date is probably more helpful than leaving the widget empty? We could just
- // display the placeholder and leave it there, but it's likely that at least the year will be
- // the same as today's.
-
- // Use English locale to avoid number formatting
- this.setValue( moment().locale( 'en' ).format( this.getInternalFormat() ) );
- }
-
this.$element.addClass( 'mw-widget-dateInputWidget-active' );
this.handle.toggle( false );
this.textInput.toggle( true );
border: 1px solid #ccc;
border-radius: 0.1em;
line-height: 1.275em;
+ background-color: white;
}
> .oo-ui-textInputWidget input {
}
/**
- * Adds a script tag to the DOM, either using document.write or low-level DOM manipulation,
- * depending on whether document-ready has occurred yet and whether we are in async mode.
+ * Load and execute a script with callback.
*
* @private
* @param {string} src URL to script, will be used as the src attribute in the script tag
* @param {Function} [callback] Callback which will be run when the script is done
- * @param {boolean} [async=false] Whether to load modules asynchronously.
- * Ignored (and defaulted to `true`) if the document-ready event has already occurred.
*/
- function addScript( src, callback, async ) {
- // Using isReady directly instead of storing it locally from a $().ready callback (bug 31895)
- if ( $.isReady || async ) {
- $.ajax( {
- url: src,
- dataType: 'script',
- // Force jQuery behaviour to be for crossDomain. Otherwise jQuery would use
- // XHR for a same domain request instead of <script>, which changes the request
- // headers (potentially missing a cache hit), and reduces caching in general
- // since browsers cache XHR much less (if at all). And XHR means we retreive
- // text, so we'd need to $.globalEval, which then messes up line numbers.
- crossDomain: true,
- cache: true,
- async: true
- } ).always( callback );
- } else {
- /*jshint evil:true */
- document.write( mw.html.element( 'script', { 'src': src }, '' ) );
- if ( callback ) {
- // Document.write is synchronous, so this is called when it's done.
- // FIXME: That's a lie. doc.write isn't actually synchronous.
- callback();
- }
- }
+ function addScript( src, callback ) {
+ $.ajax( {
+ url: src,
+ dataType: 'script',
+ // Force jQuery behaviour to be for crossDomain. Otherwise jQuery would use
+ // XHR for a same domain request instead of <script>, which changes the request
+ // headers (potentially missing a cache hit), and reduces caching in general
+ // since browsers cache XHR much less (if at all). And XHR means we retreive
+ // text, so we'd need to $.globalEval, which then messes up line numbers.
+ crossDomain: true,
+ cache: true
+ } ).always( callback );
}
/**
registry[module].state = 'ready';
handlePending( module );
};
- nestedAddScript = function ( arr, callback, async, i ) {
+ nestedAddScript = function ( arr, callback, i ) {
// Recursively call addScript() in its own callback
// for each element of arr.
if ( i >= arr.length ) {
}
addScript( arr[i], function () {
- nestedAddScript( arr, callback, async, i + 1 );
- }, async );
+ nestedAddScript( arr, callback, i + 1 );
+ } );
};
if ( $.isArray( script ) ) {
- nestedAddScript( script, markModuleReady, registry[module].async, 0 );
+ nestedAddScript( script, markModuleReady, 0 );
} else if ( $.isFunction( script ) ) {
// Pass jQuery twice so that the signature of the closure which wraps
// the script can bind both '$' and 'jQuery'.
mw.templates.set( module, registry[module].templates );
}
- if ( $.isReady || registry[module].async ) {
- // Make sure we don't run the scripts until all (potentially asynchronous)
- // stylesheet insertions have completed.
- ( function () {
- var pending = 0;
- checkCssHandles = function () {
- // cssHandlesRegistered ensures we don't take off too soon, e.g. when
- // one of the cssHandles is fired while we're still creating more handles.
- if ( cssHandlesRegistered && pending === 0 && runScript ) {
- runScript();
- runScript = undefined; // Revoke
+ // Make sure we don't run the scripts until all stylesheet insertions have completed.
+ ( function () {
+ var pending = 0;
+ checkCssHandles = function () {
+ // cssHandlesRegistered ensures we don't take off too soon, e.g. when
+ // one of the cssHandles is fired while we're still creating more handles.
+ if ( cssHandlesRegistered && pending === 0 && runScript ) {
+ runScript();
+ runScript = undefined; // Revoke
+ }
+ };
+ cssHandle = function () {
+ var check = checkCssHandles;
+ pending++;
+ return function () {
+ if ( check ) {
+ pending--;
+ check();
+ check = undefined; // Revoke
}
};
- cssHandle = function () {
- var check = checkCssHandles;
- pending++;
- return function () {
- if ( check ) {
- pending--;
- check();
- check = undefined; // Revoke
- }
- };
- };
- }() );
- } else {
- // We are in blocking mode, and so we can't afford to wait for CSS
- cssHandle = function () {};
- // Run immediately
- checkCssHandles = runScript;
- }
+ };
+ }() );
// Process styles (see also mw.loader.implement)
// * back-compat: { <media>: css }
* @param {string|string[]} dependencies Module name or array of string module names
* @param {Function} [ready] Callback to execute when all dependencies are ready
* @param {Function} [error] Callback to execute when any dependency fails
- * @param {boolean} [async=false] Whether to load modules asynchronously.
- * Ignored (and defaulted to `true`) if the document-ready event has already occurred.
*/
- function request( dependencies, ready, error, async ) {
+ function request( dependencies, ready, error ) {
// Allow calling by single module name
if ( typeof dependencies === 'string' ) {
dependencies = [dependencies];
return;
}
queue.push( module );
- if ( async ) {
- registry[module].async = true;
- }
}
} );
}
/**
- * Asynchronously append a script tag to the end of the body
- * that invokes load.php
+ * Load modules from load.php
* @private
* @param {Object} moduleMap Module map, see #buildModulesString
* @param {Object} currReqBase Object with other parameters (other than 'modules') to use in the request
* @param {string} sourceLoadScript URL of load.php
- * @param {boolean} async Whether to load modules asynchronously.
- * Ignored (and defaulted to `true`) if the document-ready event has already occurred.
*/
- function doRequest( moduleMap, currReqBase, sourceLoadScript, async ) {
+ function doRequest( moduleMap, currReqBase, sourceLoadScript ) {
var request = $.extend(
{ modules: buildModulesString( moduleMap ) },
currReqBase
);
request = sortQuery( request );
// Support: IE6
- // Append &* to satisfy load.php's WebRequest::checkUrlExtension test. This script
- // isn't actually used in IE6, but MediaWiki enforces it in general.
- addScript( sourceLoadScript + '?' + $.param( request ) + '&*', null, async );
+ // Append &* to satisfy load.php's WebRequest::checkUrlExtension test.
+ // This script isn't actually used in IE6, but MediaWiki enforces it in general.
+ addScript( sourceLoadScript + '?' + $.param( request ) + '&*' );
}
/**
var reqBase, splits, maxQueryLength, q, b, bSource, bGroup, bSourceGroup,
source, concatSource, origBatch, group, i, modules, sourceLoadScript,
currReqBase, currReqBaseLength, moduleMap, l,
- lastDotIndex, prefix, suffix, bytesAdded, async;
+ lastDotIndex, prefix, suffix, bytesAdded;
// Build a list of request parameters common to all requests.
reqBase = {
currReqBase.user = mw.config.get( 'wgUserName' );
}
currReqBaseLength = $.param( currReqBase ).length;
- async = true;
// We may need to split up the request to honor the query string length limit,
// so build it piece by piece.
l = currReqBaseLength + 9; // '&modules='.length == 9
if ( maxQueryLength > 0 && !$.isEmptyObject( moduleMap ) && l + bytesAdded > maxQueryLength ) {
// This request would become too long, create a new one
// and fire off the old one
- doRequest( moduleMap, currReqBase, sourceLoadScript, async );
+ doRequest( moduleMap, currReqBase, sourceLoadScript );
moduleMap = {};
- async = true;
l = currReqBaseLength + 9;
mw.track( 'resourceloader.splitRequest', { maxQueryLength: maxQueryLength } );
}
moduleMap[prefix] = [];
}
moduleMap[prefix].push( suffix );
- if ( !registry[modules[i]].async ) {
- // If this module is blocking, make the entire request blocking
- // This is slightly suboptimal, but in practice mixing of blocking
- // and async modules will only occur in debug mode.
- async = false;
- }
l += bytesAdded;
}
// If there's anything left in moduleMap, request that too
if ( !$.isEmptyObject( moduleMap ) ) {
- doRequest( moduleMap, currReqBase, sourceLoadScript, async );
+ doRequest( moduleMap, currReqBase, sourceLoadScript );
}
}
}
* @param {string} [type='text/javascript'] MIME type to use if calling with a URL of an
* external script or style; acceptable values are "text/css" and
* "text/javascript"; if no type is provided, text/javascript is assumed.
- * @param {boolean} [async] Whether to load modules asynchronously.
- * Ignored (and defaulted to `true`) if the document-ready event has already occurred.
- * Defaults to `true` if loading a URL, `false` otherwise.
*/
- load: function ( modules, type, async ) {
+ load: function ( modules, type ) {
var filtered, l;
// Validate input
// Allow calling with an external url or single dependency as a string
if ( typeof modules === 'string' ) {
if ( /^(https?:)?\/\//.test( modules ) ) {
- if ( async === undefined ) {
- // Assume async for bug 34542
- async = true;
- }
if ( type === 'text/css' ) {
// Support: IE 7-8
// Use properties instead of attributes as IE throws security
return;
}
if ( type === 'text/javascript' || type === undefined ) {
- addScript( modules, null, async );
+ addScript( modules );
return;
}
// Unknown type
return;
}
// Since some modules are not yet ready, queue up a request.
- request( filtered, undefined, undefined, async );
+ request( filtered, undefined, undefined );
},
/**
$CODE.registrations();
+ mw.config.set( $VARS.configuration );
+
+ // Must be after mw.config.set because these callbacks may use mw.loader which
+ // needs to have values 'skin', 'debug' etc. from mw.config.
window.RLQ = window.RLQ || [];
while ( RLQ.length ) {
RLQ.shift()();
fn();
}
};
-
- mw.config.set( $VARS.configuration );
}
// Conditional script injection
if ( isCompatible() ) {
- document.write( $VARS.baseModulesScript );
+ ( function () {
+ var script = document.createElement( 'script' );
+ script.src = $VARS.baseModulesUri;
+ document.getElementsByTagName( 'head' )[0].appendChild( script );
+ }() );
+} else {
+ // Undo class swapping in case of an unsupported browser.
+ // See OutputPage::getHeadScripts().
+ document.documentElement.className = document.documentElement.className
+ .replace( /(^|\s)client-js(\s|$)/, '$1client-nojs$2' );
}
|</noinclude>style="color:red;"||Bar
!! endarticle
+!! article
+Template:table_attribs_6
+!! text
+style="background: <nowiki>
+
+
+red;</nowiki>" |
+!! endarticle
+
+!! article
+Template:table_attribs_7
+!! text
+<noinclude>
+|</noinclude>style{{=}}"background:#f9f9f9;"|Foo<ref>foo</ref>
+!! endarticle
+
!! article
Template:table_header_cells
!! text
Non-word characters are valid in extension tags (T19663)
!! wikitext
<tåg>tåg</tåg>
-!! html
+!! html/php
<pre>
'tåg'
array (
!! test
Isolated close tags should be treated as literal text (bug 52760)
+!! options
+parsoid=wt2html
!! wikitext
</b>
<s.foo>s</s>
-!! html+tidy
+!! html/php+tidy
+<p><s.foo>s</p>
+!! html/parsoid
<p><s.foo>s</p>
!! end
<nowiki> unordered list
!! wikitext
<nowiki>* This is not an unordered list item.</nowiki>
-!! html
+!! html/php
<p>* This is not an unordered list item.
</p>
+!! html/parsoid
+<p><span typeof="mw:Nowiki">* This is not an unordered list item.</span></p>
!! end
!! test
:and a colon
</nowiki>
-!! html
+!! html/php
<p>Lorem ipsum dolor
sed abit.
:and a colon
</p>
+!! html/parsoid
+<p><span typeof="mw:Nowiki">Lorem ipsum dolor
+
+sed abit.
+ sed nullum.
+
+:and a colon
+</span></p>
!! end
!! test
*There is not nowiki.
*There is <nowiki>nowiki</nowiki>.
-!! html
+!! html/php
<dl><dd>There is not nowiki.</dd>
<dd>There is nowiki.</dd></dl>
<ol><li>There is not nowiki.</li>
<ul><li>There is not nowiki.</li>
<li>There is nowiki.</li></ul>
+!! html/parsoid
+<dl><dd data-parsoid='{}'>There is not nowiki.</dd>
+<dd data-parsoid='{}'>There is <span typeof="mw:Nowiki">nowiki</span>.</dd></dl>
+
+<ol><li data-parsoid='{}'>There is not nowiki.</li>
+<li data-parsoid='{}'>There is <span typeof="mw:Nowiki">nowiki</span>.</li></ol>
+
+<ul><li data-parsoid='{}'>There is not nowiki.</li>
+<li data-parsoid='{}'>There is <span typeof="mw:Nowiki">nowiki</span>.</li></ul>
!! end
!! test
!! html
<p>* </nowiki> tag</p>
!! wikitext
-<nowiki>* </nowiki></nowiki> tag
+<nowiki>*</nowiki> <nowiki></nowiki></nowiki> tag
!! end
!! test
</nowiki>
</pre>
-!! html
+!! html/php
<pre>
<nowiki>
</pre>
</pre>
</p>
+!! html/parsoid
+<pre data-parsoid='{"stx":"html","strippedNL":true}'><nowiki>
+</pre>
+<p><span typeof="mw:Placeholder" data-parsoid='{"src":"</nowiki>"}'></nowiki></span>
+</pre></p>
+
+<p><span typeof="mw:Nowiki">
+<pre>
+<nowiki>
+</pre>
+</span>
+</pre></p>
!! end
!! test
'' no-italic ''
[[ NoLink ]]
</pre>
-!! html
+!! html/php
<pre>
* foo
# bar
[[ NoLink ]]
</pre>
+!! html/parsoid
+<pre data-parsoid='{"stx":"html","strippedNL":true}'>* foo
+# bar
+= no-h =
+'' no-italic ''
+[[ NoLink ]]
+</pre>
!!end
###
</dl>
</dd>
</dl>
+!! html/parsoid
+<dl><dt>a</dt><dd data-parsoid='{"stx":"row"}'>b</dd>
+<dt><b>a:b</b></dt>
+<dt><i data-parsoid='{"stx":"html"}'>a:b</i></dt>
+<dt><span data-parsoid='{"stx":"html"}'>a:b</span></dt>
+<dt><div data-parsoid='{"stx":"html"}'>a:b</div></dt>
+<dt><div data-parsoid='{"stx":"html","autoInsertedEnd":true}'>a</div></dt>
+<dd>b</dd>
+<dt><span about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[{"k":"1","spc":["","","",""]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"a:b"}},"i":0}}]}'>a:b</span></dt>
+<dt><i about="#mwt2" typeof="mw:Transclusion" data-parsoid='{"pi":[[{"k":"1","spc":["","","",""]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"''a:b''"}},"i":0}}]}'>a:b</i>
+<dl><dt><dl><dt><i>a:b</i></dt></dl></dt></dl></dt></dl>
!! end
!! test
</tbody></table>
!! end
+## Edge case fix to prevent future regressions
+!! test
+T107652: <ref>s in templates that also generate table cell attributes should be rendered properly
+!! wikitext
+{|
+|{{table_attribs_7}}
+|}
+<references />
+!! html/parsoid
+<table>
+<tbody><tr><td style="background:#f9f9f9;" typeof="mw:Transclusion" about="#mwt1" data-mw='{"parts":["|",{"template":{"target":{"wt":"table_attribs_7","href":"./Template:Table_attribs_7"},"params":{},"i":0}}]}'>Foo<span class="mw-ref" id="cite_ref-1" rel="dc:references" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-1"},"attrs":{}}'><a href="#cite_note-1" style="counter-reset: mw-Ref 1;"><span class="mw-reflink-text">[1]</span></a></span></td></tr>
+</tbody></table>
+<ol class="mw-references" typeof="mw:Extension/references" about="#mwt5" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><a href="#cite_ref-1" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-1" class="mw-reference-text" data-parsoid="{}">foo</span></li></ol>
+!! end
+
!! test
Table with row followed by newlines and table heading
!! wikitext
<br title=bar/>
<br title=bar/ >
!! html/php
-<p><br title="title" />
-<br title="title" />
+<p><br title="" />
+<br title="" />
<br />
<br title="bar" />
<br title="bar" />
<meta property="mw:PageProp/noeditsection" data-parsoid='{"magicSrc":"__NOEDITSECTION__"}'/>
!! end
+!!test
+__proto__ is treated as normal wikitext (T105997)
+!!wikitext
+__proto__
+!!html
+<p>__proto__
+</p>
+!!end
+
###
### Magic links
###
msgnw keyword
!! wikitext
{{msgnw:MSGNW test}}
-!! html
+!! html/php
<p>''None'' of '''this''' should be
* interpreted
 but rather passed unmodified
</tbody></table>
!! end
+# T107622
+!! test
+4. Entities and nowikis inside templated attributes should be handled correctly inside templated tables
+!! wikitext
+{|
+| {{table_attribs_6}} hi
+|}
+!! html/php
+<table>
+<tr>
+<td style="background: red;"> hi
+</td></tr></table>
+
+!! html/parsoid
+<table>
+<tbody><tr><td style="background: red;" typeof="mw:Transclusion" about="#mwt1" data-parsoid='{"autoInsertedEnd":true,"pi":[[]]}' data-mw='{"parts":["| ",{"template":{"target":{"wt":"table_attribs_6","href":"./Template:Table_attribs_6"},"params":{},"i":0}}," hi"]}'> hi</td></tr>
+</tbody></table>
+!! end
+
!!test
Templates: HTML Tables: 1. Generating start of a HTML table
!! wikitext
!! test
pre-save transform: subst:
!! options
-PST
+pst
!! wikitext
{{subst:test}}
-!! html
+!! html/php
This is a test template
!! end
!! test
pre-save transform: normal template
!! options
-PST
+pst
!! wikitext
{{test}}
-!! html
+!! html/php
{{test}}
!! end
!! test
pre-save transform: nonexistent template
!! options
-PST
+pst
!! wikitext
{{thistemplatedoesnotexist}}
-!! html
+!! html/php
{{thistemplatedoesnotexist}}
!! end
-
!! test
pre-save transform: subst magic variables
!! options
-PST
+pst
!! wikitext
{{subst:SITENAME}}
-!! html
+!! html/php
MediaWiki
!! end
pst
!! wikitext
{{subst:paramtest|param="something else"}}
-!! html
+!! html/php
This is a test template with parameter "something else"
!! end
pst
!! wikitext
{{subst:nowikitest}}
-!! html
+!! html/php
<nowiki>'''not wiki'''</nowiki>
!! end
-
!! article
Template:commenttest
!! text
pst
!! wikitext
{{subst:commenttest}}
-!! html
+!! html/php
This template has <!-- a comment --> in it.
!! end
pst noxml
!! wikitext
<nowiki>'''not wiki'''
-!! html
+!! html/php
<nowiki>'''not wiki'''
!! end
pst noxml
!! wikitext
<NOwiki>'''not wiki'''</noWIKI>
-!! html
+!! html/php
<NOwiki>'''not wiki'''</noWIKI>
!! end
pst noxml
!! wikitext
wiki<nowiki>nowiki<!--nowiki</nowiki>wiki
-!! html
+!! html/php
wiki<nowiki>nowiki<!--nowiki</nowiki>wiki
!!end
pst
!! wikitext
<!-- <gallery>data</gallery> -->
-!! html
+!! html/php
<!-- <gallery>data</gallery> -->
!!end
pst
!! wikitext
<!-- <tag>data</tag> -->
-!! html
+!! html/php
<!-- <tag>data</tag> -->
!!end
pst
!! wikitext
<!-- <nowiki>data</nowiki> -->
-!! html
+!! html/php
<!-- <nowiki>data</nowiki> -->
!!end
pst
!! wikitext
{{subst:Includes}}
-!! html
+!! html/php
Foobar
!! end
pst
!! wikitext
{{subst:Includes2}}
-!! html
+!! html/php
Foo
!! end
pst
!! wikitext
{{subst:SafeSubstTest}}{{safesubst:SubstTest}}
-!! html
+!! html/php
FoobarFoobar
!! end
[[|Article (context)]]
[[Bar:X (Y) Z|]]
[[:Bar:X (Y) Z|]]
-!! html
+!! html/php
[[Article (context)|Article]]
[[Bar:Article|Article]]
[[:Bar:Article|Article]]
[[:interwiki:Article|]]
[[interwiki:Bar:Article|]]
[[:interwiki:Bar:Article|]]
-!! html
+!! html/php
[[interwiki:Article|Article]]
[[:interwiki:Article|Article]]
[[interwiki:Bar:Article|Bar:Article]]
pst title=[[Somearticle (context)]]
!! wikitext
[[|Article]]
-!! html
+!! html/php
[[Article (context)|Article]]
!! end
[[|Otherplace]]
[[Otherplace, Elsewhere|]]
[[Otherplace, Elsewhere, Anywhere|]]
-!! html
+!! html/php
[[Otherplace, Somewhere|Otherplace]]
[[Otherplace, Elsewhere|Otherplace]]
[[Otherplace, Elsewhere, Anywhere|Otherplace]]
!! wikitext
[[|Otherplace]]
[[Otherplace (place), Elsewhere|]]
-!! html
+!! html/php
[[Otherplace, Somewhere|Otherplace]]
[[Otherplace (place), Elsewhere|Otherplace]]
!! end
!! wikitext
[[|Yes, you.]]
[[Me, Myself, and I (1937 song)|]]
-!! html
+!! html/php
[[Yes, you. (context)|Yes, you.]]
[[Me, Myself, and I (1937 song)|Me, Myself, and I]]
!! end
pst title=[[Ns:Somearticle]]
!! wikitext
[[|Article]]
-!! html
+!! html/php
[[Ns:Article|Article]]
!! end
pst title=[[Ns:Somearticle (context)]]
!! wikitext
[[|Article]]
-!! html
+!! html/php
[[Ns:Article (context)|Article]]
!! end
pst title=[[Ns:Somearticle, Context, Whatever]]
!! wikitext
[[|Article]]
-!! html
+!! html/php
[[Ns:Article, Context, Whatever|Article]]
!! end
pst title=[[Ns:Somearticle, Context (context)]]
!! wikitext
[[|Article]]
-!! html
+!! html/php
[[Ns:Article (context)|Article]]
!! end
pst title=[[Ns:Somearticle (IGNORED), Context]]
!! wikitext
[[|Article]]
-!! html
+!! html/php
[[Ns:Article, Context|Article]]
!! end
[[|Article(context)]]
[[Bar:X(Y)Z|]]
[[:Bar:X(Y)Z|]]
-!! html
+!! html/php
[[Article(context)|Article]]
[[Bar:Article(context)|Article]]
[[:Bar:Article(context)|Article]]
[[|Article (context)]]
[[Bar:X (Y) Z|]]
[[:Bar:X (Y) Z|]]
-!! html
+!! html/php
[[Article (context)|Article]]
[[Bar:Article (context)|Article]]
[[:Bar:Article (context)|Article]]
[[|Article(context)]]
[[Bar:X(Y)Z|]]
[[:Bar:X(Y)Z|]]
-!! html
+!! html/php
[[Article(context)|Article]]
[[Bar:Article(context)|Article]]
[[:Bar:Article(context)|Article]]
[[Bar:Article (context),context|]]
[[:Bar:Article (context), context|]]
[[:Bar:Article (context),context|]]
-!! html
+!! html/php
[[Article (context), context|Article]]
[[Article (context),context|Article]]
[[Bar:Article (context), context|Article]]
-!! html
+!! html/php
Empty lines are trimmed
!! end
* <noinclude>~~~</noinclude>
* <includeonly>~~~</includeonly>
* <onlyinclude>~~~</onlyinclude>
-!! html
+!! html/php
* [[Special:Contributions/127.0.0.1|127.0.0.1]]
* <noinclude>[[Special:Contributions/127.0.0.1|127.0.0.1]]</noinclude>
* <includeonly>[[Special:Contributions/127.0.0.1|127.0.0.1]]</includeonly>
But not inside includeonly
<includeonly>{{subst:Foo}}</includeonly>
-!! html
+!! html/php
Shall not expand:
<nowiki>~~~~</nowiki>
</p>
!! end
+# Parsoid does not serialize to empty attribute syntax,
+# so wt2wt and html2wt cases are skipped
!! test
-Attribute test: no value
+Attribute test: no value (T54330)
+!! options
+parsoid=wt2html,html2html
!! wikitext
<font color>foo</font>
-!! html
-<p><font color="color">foo</font>
+!! html/php
+<p><font color="">foo</font>
</p>
+!! html/parsoid
+<p><font color="">foo</font></p>
!! end
!! test
Parser hook: empty input
!! wikitext
<tag></tag>
-!! html
+!! html/php
<pre>
''
array (
Parser hook: empty input using terminated empty elements
!! wikitext
<tag/>
-!! html
+!! html/php
<pre>
NULL
array (
Parser hook: empty input using terminated empty elements (space before)
!! wikitext
<tag />
-!! html
+!! html/php
<pre>
NULL
array (
Parser hook: basic input
!! wikitext
<tag>input</tag>
-!! html
+!! html/php
<pre>
'input'
array (
Parser hook: case insensitive
!! wikitext
<TAG>input</TAG>
-!! html
+!! html/php
<pre>
'input'
array (
Parser hook: case insensitive, redux
!! wikitext
<TaG>input</TAg>
-!! html
+!! html/php
<pre>
'input'
array (
noxml
!! wikitext
<tag><tag></tag></tag>
-!! html
+!! html/php
<pre>
'<tag>'
array (
Parser hook: basic arguments
!! wikitext
<tag width=200 height = "100" depth = '50' square></tag>
-!! html
+!! html/php
<pre>
''
array (
'width' => '200',
'height' => '100',
'depth' => '50',
- 'square' => 'square',
+ 'square' => '',
)
</pre>
Parser hook: argument containing a forward slash (bug 5344)
!! wikitext
<tag filename='/tmp/bla'></tag>
-!! html
+!! html/php
<pre>
''
array (
Parser hook: empty input using terminated empty elements (bug 2374)
!! wikitext
<tag foo=bar/>text
-!! html
+!! html/php
<pre>
NULL
array (
<tag width=200 height = "100" depth = '50' square/>
other stuff
</tag>
-!! html
+!! html/php
<pre>
NULL
array (
'width' => '200',
'height' => '100',
'depth' => '50',
- 'square' => 'square',
+ 'square' => '',
)
</pre>
<p>other stuff
!! wikitext
<statictag>hello, world</statictag>
<statictag action=flush/>
-!! html
+!! html/php
<p>hello, world
</p>
!! end
!! wikitext
<!-- <statictag>hello, world</statictag> -->
<statictag action=flush/>
-!! html
+!! html/php
<p><br />
</p>
!! end
!! test
Sanitizer: Closing of closed but not open tags
+!! options
+parsoid=wt2html
!! wikitext
</s>
-!! html
-<p></s>
-</p>
+!! html/php+tidy
+!! html/parsoid
!! end
!! test
Sanitizer: Closing of closed but not open table tags
+!! options
+parsoid=wt2html
!! wikitext
Table not started</td></tr></table>
-!! html
-<p>Table not started</td></tr></table>
-</p>
+!! html/php+tidy
+<p>Table not started</p>
+!! html/parsoid
+<p>Table not started</p>
!! end
!! test
<link rel="stylesheet" itemprop="hello" href="{{SERVER}}">
</div>
!! html
-<div itemscope="itemscope">
+<div itemscope="">
<p> <meta itemprop="hello" content="world" />
<meta http-equiv="refresh" content="5">
<meta itemprop="hello" content="5" />
===bc===
==c==
===ca===
-!! html
+!! html/php
start
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
==a==
===aa===
====aaa====
===bc===
==c==
===ca===
-!! html
+!! html/php
===aa===
====aaa====
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
====aaa====
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
==b==
===ba===
===bb===
===bc===
==c==
===ca===
-!! html
+!! html/php
===ba===
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
===bb===
====bba====
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
====bba====
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
===bc===
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
==c==
===ca===
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
===ca===
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
!! end
!! test
==a==
==bogus== not a legal section
==b==
-!! html
+!! html/php
==a==
==bogus== not a legal section
!! end
==a==
==bogus== not a legal section
==b==
-!! html
+!! html/php
==b==
!! end
==a==
==b== <!-- -->
==c==
-!! html
+!! html/php
==a==
!! end
==a==
==b== <!-- -->
==c==
-!! html
+!! html/php
==b== <!-- -->
!! end
==a==
==bogus== <nowiki>not a legal section</nowiki>
==b==
-!! html
+!! html/php
==a==
==bogus== <nowiki>not a legal section</nowiki>
!! end
==a==
==bogus== <nowiki>not a legal section</nowiki>
==b==
-!! html
+!! html/php
==b==
!! end
-
# Formerly testing for bug 2587, now resolved by the use of unmarked sections
# instead of respecting commented sections
!! test
!! wikitext
<!-- -->==sec1==
==sec2==
-!! html
+!! html/php
==sec2==
!!end
!! wikitext
<!-- -->==sec1==
==sec2==
-!! html
+!! html/php
!!end
-
# Formerly testing for bug 2607, now resolved by the use of unmarked sections
# instead of respecting HTML-style headings
!! test
one
==2==
two
-!! html
+!! html/php
==1==
one
!! end
one
==2==
two
-!! html
+!! html/php
==2==
two
!! end
!! wikitext
<noinclude>==unmarked==</noinclude>
==marked==
-!! html
+!! html/php
==marked==
!!end
=== <!--
--> <!-- -->
But just in case it doesn't...
-!! html
+!! html/php
=== <!--
--> <!-- -->
But just in case it doesn't...
===bc===
==c==
===ca===
-!! html
+!! html/php
xxx
==a==
===bc===
==c==
===ca===
-!! html
+!! html/php
start
xxx
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
xxx
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
===aa===
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
===aa===
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
===aa===
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
===aa===
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
===aa===
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
===aa===
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
===aa===
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
===aa===
Preformatted initial line
==a==
===a===
-!! html
+!! html/php
Preformatted initial line
==a==
xxx
!! wikitext
==a==
a
-!! html
+!! html/php
==a==
a
!! end
!! wikitext
==a==
a
-!! html
+!! html/php
==a==
a
!! end
== Section Two ==
stuff
-!! html
+!! html/php
== Section Two ==
stuff
!! end
== Section Two ==
stuff
-!! html
+!! html/php
== Section One ==
<pre>
=======
!! end
-
!! test
Handling of 
 in URLs
!! wikitext
MSGNW magic word
!! wikitext
{{MSGNW:msg}}
-!! html
+!! html/php
<p>[[:Template:Msg]]
</p>
!! end
wgRestrictDisplayTitle=false
!! wikitext
this is not the the title
-!! html
+!! html/php
Parser test
<p>this is not the the title
</p>
!! wikitext
this is not the the title
{{DISPLAYTITLE:whatever}}
-!! html
+!! html/php
whatever
<p>this is not the the title
</p>
!! wikitext
this is not the the title
{{DISPLAYTITLE:whatever}}
-!! html
+!! html/php
Screen
<p>this is not the the title
</p>
!! wikitext
this is not the the title
{{DISPLAYTITLE:screen}}
-!! html
+!! html/php
screen
<p>this is not the the title
</p>
!! wikitext
this is not the the title
{{DISPLAYTITLE:screen}}
-!! html
+!! html/php
Screen
<p>this is not the the title
<a href="/index.php?title=Template:DISPLAYTITLE:screen&action=edit&redlink=1" class="new" title="Template:DISPLAYTITLE:screen (page does not exist)">Template:DISPLAYTITLE:screen</a>
wgAllowDisplayTitle=false
!! wikitext
this is not the the title
-!! html
+!! html/php
Screen
<p>this is not the the title
</p>
!! wikitext
this is not the the title
{{DISPLAYTITLE:<span style="display: none;">s</span>creen}}
-!! html
+!! html/php
<span style="/* attempt to bypass $wgRestrictDisplayTitle */">s</span>creen
<p>this is not the the title
</p>
!! wikitext
this is not the the title
{{DISPLAYTITLE:<span style="color: red;">s</span>creen}}
-!! html
+!! html/php
<span style="color: red;">s</span>creen
<p>this is not the the title
</p>
showindicators
!! wikitext
<indicator name="empty" />
-<indicator name></indicator>
+<indicator name="name"></indicator>
!! html
empty=
name=
preload
!! wikitext
Hello <noinclude>cruel</noinclude><includeonly>kind</includeonly> world.
-!! html
+!! html/php
Hello kind world.
!! end
preload
!! wikitext
Goodbye <onlyinclude>Hello world</onlyinclude>
-!! html
+!! html/php
Hello world
!! end
preload
!! wikitext
<includeonly><</includeonly>includeonly>Hello world<includeonly><</includeonly>/includeonly>
-!! html
+!! html/php
<includeonly>Hello world</includeonly>
!! end
preload
!! wikitext
* <!-- Hello --> ''{{world}}'' {{<includeonly>subst:</includeonly>How are you}}{{ {{{|safesubst:}}} #if:1|2|3}}
-!! html
+!! html/php
* <!-- Hello --> ''{{world}}'' {{subst:How are you}}{{ {{{|safesubst:}}} #if:1|2|3}}
!! end
<small><figure class="mw-halign-right" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="34" width="300"/></a></figure></small>
!! end
+!! test
+3. Bad treebuilder fixup of formatting elt is cleaned up
+!! options
+parsoid=wt2html,wt2wt
+!! wikitext
+<small>'''foo[[File:Foobar.jpg|thumb|caption]]bar'''</small>
+!! html/parsoid
+<p><small><b>foo</b></small></p>
+<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption><small><b>caption</b></small></figcaption></figure>
+<p><small><b>bar</b></small></p>
+!! end
+
+!! test
+4. Bad treebuilder fixup of formatting elt is cleaned up: formatting tags around captionless images are ignored
+!! options
+parsoid=wt2html,wt2wt
+!! wikitext
+'''<small>[[Image:Foobar.jpg|right|300px]]</small>'''
+!! html/parsoid
+<p><b><small></small></b></p>
+<figure class="mw-halign-right" typeof="mw:Image"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="34" width="300"/></a></figure>
+<p></p>
+!! end
+
#### ----------------------------------------------------------------
#### Parsoid-only testing of Parsoid's impl of <ref> and <references>
#### tags. Parsoid's output for these tags differs from that of the
!! test
Headings: 6a. Heading chars in SOL context (with trailing spaces)
-!! options
-parsoid
!! wikitext
<nowiki>=a=</nowiki>
<nowiki>=a=</nowiki>
<nowiki>=a=</nowiki>
-!! html
-<p>=a=</p>
-<p>=a= </p>
-<p>=a= </p>
-<p>=a= </p>
+!! html/php
+<p>=a=
+</p><p>=a=
+</p><p>=a=
+</p><p>=a=
+</p>
+!! html/parsoid
+<p><span typeof="mw:Nowiki">=a=</span></p>
+
+<p><span typeof="mw:Nowiki">=a=</span></p>
+
+<p><span typeof="mw:Nowiki">=a=</span></p>
+
+<p><span typeof="mw:Nowiki">=a=</span></p>
!!end
!! test
Headings: 6b. Heading chars in SOL context (with trailing newlines)
-!! options
-parsoid
!! wikitext
<nowiki>=a=
b</nowiki>
<nowiki>=a=
b</nowiki>
-!! html
+!! html/php
<p>=a=
-b</p>
-<p>=a=
-b</p>
-<p>=a=
-b</p>
-<p>=a=
-b</p>
+b
+</p><p>=a=
+b
+</p><p>=a=
+b
+</p><p>=a=
+b
</p>
+!! html/parsoid
+<p><span typeof="mw:Nowiki">=a=
+b</span></p>
+
+<p><span typeof="mw:Nowiki">=a=
+b</span></p>
+
+<p><span typeof="mw:Nowiki">=a=
+b</span></p>
+
+<p><span typeof="mw:Nowiki">=a=
+b</span></p>
!!end
!! test
Headings: 6c. Heading chars in SOL context (leading newline break)
-!! options
-parsoid
!! wikitext
a
<nowiki>=b=</nowiki>
-!! html
+!! html/php
<p>a
-=b=</p>
+=b=
+</p>
+!! html/parsoid
+<p>a
+<span typeof="mw:Nowiki">=b=</span>
!!end
!! test
Headings: 6d. Heading chars in SOL context (with interspersed comments)
-!! options
-parsoid
!! wikitext
<!--c0--><nowiki>=a=</nowiki>
<!--c1--><nowiki>=a=</nowiki> <!--c2--> <!--c3-->
-!! html
-<p><!--c0-->=a=</p>
-<p><!--c1-->=a= <!--c2--> <!--c3--></p>
+!! html/php
+<p>=a=
+</p><p>=a=
+</p>
+!! html/parsoid
+<!--c0--><p><span typeof="mw:Nowiki">=a=</span></p>
+
+<!--c1--><p><span typeof="mw:Nowiki">=a=</span></p> <!--c2--> <!--c3-->
!!end
!! test
Headings: 6d. Heading chars in SOL context (No escaping needed)
!! options
parsoid=html2wt
-!! html
+!! html/parsoid
=a=<div>b</div>
!! wikitext
=a=<div>b</div>
Headings: 7. Insert a newline between new content and headings
!! options
parsoid=html2wt
-!! html
+!! html/parsoid
<h2>NEW</h2>
<p>new</p>
-<h2 data-parsoid='{"dsr":[0,5,2,2]}'>A</h2>
-<p data-parsoid='{"dsr":[6,7,0,0]}'>a</p>
+<h2 data-parsoid='{}'>A</h2>
+<p data-parsoid='{}'>a</p>
!! wikitext
== NEW ==
new
<nowiki>#</nowiki>foo
<nowiki>;Foo:</nowiki>bar
-!! html
+!! html/php
<p>*foo
</p><p>#foo
</p><p>;Foo:bar
</p>
+!! html/parsoid
+<p><span typeof="mw:Nowiki">*</span>foo</p>
+
+<p><span typeof="mw:Nowiki">#</span>foo</p>
+
+<p><span typeof="mw:Nowiki">;Foo:</span>bar</p>
!!end
!! test
!!test
Encapsulate protected attributes from wt
!! wikitext
-<div typeof="mw:placeholder stuff" data-parsoid="weird" data-parsoid-other="no" about="time" rel="mw:true">foo</div>
+<div typeof="mw:placeholder stuff" data-mw="whoo" data-parsoid="weird" data-parsoid-other="no" about="time" rel="mw:true">foo</div>
!! html/parsoid
-<body><div data-x-typeof="mw:placeholder stuff" data-x-data-parsoid="weird" data-x-data-parsoid-other="no" data-x-about="time" data-x-rel="mw:true">foo</div>
+<body><div data-x-typeof="mw:placeholder stuff" data-x-data-mw="whoo" data-x-data-parsoid="weird" data-x-data-parsoid-other="no" data-x-about="time" data-x-rel="mw:true">foo</div>
</body>
!!end
!! end
!! test
-Headings: Force sol-transparent links and behavior switches to serialize before/after
+1. Headings: Force sol-transparent links and behavior switches to serialize before/after
!! options
-parsoid=html2wt
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": false
+}
+!! html
+<h2>hello there<link href="Category:A1" rel="mw:PageProp/Category" /></h2>
+<h2><link href="Category:A2" rel="mw:PageProp/Category" />hi pal</h2>
+
+<h2><!--foo--> <link href="Category:A3" rel="mw:PageProp/Category" /> how goes it</h2>
+<h2>it goes well <link href="Category:A4" rel="mw:PageProp/Category" /> <!--bar--></h2>
+
+<h2 data-parsoid='{}'>howdy<link href="Category:A5" rel="mw:PageProp/Category" /></h2>
+
+<h2><meta property="mw:PageProp/toc" /> ok</h2>
+!! wikitext
+== hello there [[Category:A1]] ==
+
+== [[Category:A2]] hi pal ==
+
+== <!--foo--> [[Category:A3]] how goes it ==
+
+== it goes well [[Category:A4]] <!--bar--> ==
+
+==howdy [[Category:A5]] ==
+
+== __TOC__ ok ==
+!! end
+
+!! test
+2. Headings: Force sol-transparent links and behavior switches to serialize before/after
+!! options
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": true
+}
!! html
<h2>hello there<link href="Category:A1" rel="mw:PageProp/Category" /></h2>
<h2><link href="Category:A2" rel="mw:PageProp/Category" />hi pal</h2>
!! test
Headings: Don't hoist metas that come from templates
!! options
-parsoid=html2wt
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": true
+}
!! html
<h2><span about="#mwt1" typeof="mw:Transclusion" data-parsoid="{}" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo [[Category:Foo]]"}},"i":0}}]}'>foo </span><link rel="mw:PageProp/Category" href="./Category:Foo" about="#mwt1" data-parsoid="{}" /></h2>
!! wikitext
!! test
Headings: Category in ref isn't hoisted
!! options
-parsoid=html2wt
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": true
+}
!! html
<h2> foo <span about="#mwt2" class="reference" id="cite_ref-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-1"},"attrs":{}}'><a href="#cite_note-1">[1]</a></span> </h2>
baz</li>
<li>foo <b>bar</b>
baz</li></ul>
+
+<dl><dt>hi
+ho </dt><dd data-parsoid='{"stx":"row"}'> hi
+ho</dd></dl>
+
+<dl><dd> <table>
+<tbody><tr><td> ha
+ha
+ha</td></tr>
+</tbody></table></dd></dl>
!! wikitext
== testing 123 ==
* foo bar baz
* foo '''bar''' baz
+
+; hi ho : hi ho
+
+: {|
+| ha
+ha
+ha
+|}
!! end
!! test
x<nowiki/>http://cscott.net<nowiki/>x
!! end
+!! test
+WTS of edited autolink-like text (T103364)
+!! options
+parsoid={
+ "modes": ["wt2wt"],
+ "changes": [
+ [ "span[typeof]", "removeAttr", "typeof" ]
+ ]
+}
+!! wikitext
+Not a link: <nowiki>http://example.com</nowiki>.
+!! wikitext/edited
+Not a link: <span><nowiki>http://example.com</nowiki></span>.
+!! end
+
+!! test
+WTS of newly-authored autolink-like text (T103364)
+!! options
+parsoid=html2wt
+!! html/parsoid
+<p>http://example.com is not a link.</p>
+!! wikitext
+<nowiki>http://example.com is not a link.</nowiki>
+!! end
+
!! test
Edited Redirect link should emit a non-piped wikitext link
!! options
!! end
!! test
-Headings: Add space before/after == (T53744)
+1. Headings: Add space before/after == (T53744)
!! options
parsoid=html2wt
!! html
<h2> bar</h2>
<h2>baz </h2>
<h2><span> baz</span></h2>
-
-<!-- Even after hoisted content -->
-<h2> <link href="Category:A2" rel="mw:PageProp/Category" />ok</h2>
!! wikitext
== foo ==
== baz ==
== <span> baz</span> ==
+!! end
-<!-- Even after hoisted content -->
+!! test
+2. Headings: Add space before/after == even after hoisted content
+!! options
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": true
+}
+!! html
+<h2> <link href="Category:A2" rel="mw:PageProp/Category" />ok</h2>
+!! wikitext
[[Category:A2]]
== ok ==
}
!! html
<p> hi</p>
+<p> hello</p>
!! wikitext
hi
+
+hello
!! end
!! test
parsoid=html2wt
!! html
<p> hi</p>
+<p> hello</p>
!! wikitext
<nowiki> </nowiki>hi
+
+<nowiki> </nowiki> hello
+!! end
+
+!! test
+3. Indent Pre Nowiki: suppress whitespace after newlines in new paragraph or table cell
+!! options
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": true
+}
+!! html/parsoid
+<p>Foo
+ bar
+baz</p>
+
+<table><tr><td>Foo
+ bar
+ baz bang</td></tr></table>
+
+<p><!--boo--> foo
+ bar</p>
+
+<p> foo
+ bar<span>boo</span></p>
+!! wikitext
+Foo
+bar
+baz
+
+{|
+|Foo
+bar
+baz bang
+|}
+
+<!--boo-->foo
+bar
+
+foo
+bar<span>boo</span>
+!! end
+
+!! test
+1. New links that end in spaces
+!! options
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": false
+}
+!! html
+<p><a rel="mw:WikiLink" href="./Berlin" title="Berlin">Berlin </a>is the capital of Germany.</p>
+<p><a rel="mw:WikiLink" href="./Foo" title="Foo">Foo </a><b>bar</b></p>
+!! wikitext
+[[Berlin ]]<nowiki/>is the capital of Germany.
+
+[[Foo ]]'''bar'''
+!! end
+
+!! test
+2. New links that end in spaces
+!! options
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": true
+}
+!! html
+<p><a rel="mw:WikiLink" href="./Berlin" title="Berlin">Berlin </a>is the capital of Germany.</p>
+<p><a rel="mw:WikiLink" href="./Foo" title="Foo">Foo </a><b>bar</b></p>
+!! wikitext
+[[Berlin]] is the capital of Germany.
+
+[[Foo]] '''bar'''
+!! end
+
+!! test
+3. Existing links that end in spaces
+!! options
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": true
+}
+!! html
+<p><a rel="mw:WikiLink" href="./Berlin" title="Berlin" data-parsoid='{"stx":"simple","a":{"href":"./Berlin"},"sa":{"href":"Berlin "}}'>Berlin </a>is the capital of Germany.</p>
+
+<p><a rel="mw:WikiLink" href="./Foo" title="Foo" data-parsoid='{"stx":"simple","a":{"href":"./Foo"},"sa":{"href":"Foo "}}'>Foo </a><b>bar</b></p>
+!! wikitext
+[[Berlin ]]<nowiki/>is the capital of Germany.
+
+[[Foo ]]'''bar'''
!! end
# ---------------------------------------------------
array(
array( 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS ),
"<script>var RLQ = RLQ || []; RLQ.push( function () {\n"
- . 'document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?'
- . 'debug=false\u0026amp;lang=en\u0026amp;modules=test.foo\u0026amp;only'
- . '=scripts\u0026amp;skin=fallback\u0026amp;*\"\u003E\u003C/script\u003E");'
+ . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.foo\u0026only=scripts\u0026skin=fallback\u0026*");'
. "\n} );</script>"
),
array(
// Don't condition wrap raw modules (like the startup module)
array( 'test.raw', ResourceLoaderModule::TYPE_SCRIPTS ),
- '<script src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.raw&only=scripts&skin=fallback&*"></script>'
+ '<script async src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.raw&only=scripts&skin=fallback&*"></script>'
),
// Load module styles only
// This also tests the order the modules are put into the url
array(
array( array( 'test.group.foo', 'test.group.bar' ), ResourceLoaderModule::TYPE_COMBINED ),
"<script>var RLQ = RLQ || []; RLQ.push( function () {\n"
- . 'document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?debug=false\u0026amp;lang=en\u0026amp;modules=test.group.bar\u0026amp;skin=fallback\u0026amp;*\"\u003E\u003C/script\u003E");'
+ . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.group.bar\u0026skin=fallback\u0026*");'
. "\n} );</script>\n"
. "<script>var RLQ = RLQ || []; RLQ.push( function () {\n"
- . 'document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?debug=false\u0026amp;lang=en\u0026amp;modules=test.group.foo\u0026amp;skin=fallback\u0026amp;*\"\u003E\u003C/script\u003E");'
+ . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.group.foo\u0026skin=fallback\u0026*");'
. "\n} );</script>"
),
);
--- /dev/null
+<?php
+
+use Liuggio\StatsdClient\Entity\StatsdData;
+
+class SamplingStatsdClientTest extends PHPUnit_Framework_TestCase {
+ /**
+ * @dataProvider samplingDataProvider
+ */
+ public function testSampling( $data, $sampleRate, $seed, $expectWrite ) {
+ $sender = $this->getMock( 'Liuggio\StatsdClient\Sender\SenderInterface' );
+ $sender->expects( $this->any() )->method( 'open' )->will( $this->returnValue( true ) );
+ if ( $expectWrite ) {
+ $sender->expects( $this->once() )->method( 'write' )
+ ->with( $this->anything(), $this->equalTo( $data ) );
+ } else {
+ $sender->expects( $this->never() )->method( 'write' );
+ }
+ mt_srand( $seed );
+ $client = new SamplingStatsdClient( $sender );
+ $client->send( $data, $sampleRate );
+ }
+
+ public function samplingDataProvider() {
+ $unsampled = new StatsdData();
+ $unsampled->setKey( 'foo' );
+ $unsampled->setValue( 1 );
+
+ $sampled = new StatsdData();
+ $sampled->setKey( 'foo' );
+ $sampled->setValue( 1 );
+ $sampled->setSampleRate( '0.1' );
+
+ return array(
+ // $data, $sampleRate, $seed, $expectWrite
+ array( $unsampled, 1, 0 /*0.44*/, $unsampled ),
+ array( $sampled, 1, 0 /*0.44*/, null ),
+ array( $sampled, 1, 4 /*0.03*/, $sampled ),
+ array( $unsampled, 0.1, 4 /*0.03*/, $sampled ),
+ array( $sampled, 0.5, 0 /*0.44*/, null ),
+ array( $sampled, 0.5, 4 /*0.03*/, $sampled ),
+ );
+ }
+}