diff options
Diffstat (limited to 'tests')
227 files changed, 13620 insertions, 3600 deletions
diff --git a/tests/TestsAutoLoader.php b/tests/TestsAutoLoader.php index def23dad..8a81a644 100644 --- a/tests/TestsAutoLoader.php +++ b/tests/TestsAutoLoader.php @@ -66,8 +66,10 @@ $wgAutoloadClasses += array( 'TestRecentChangesHelper' => "$testDir/phpunit/includes/changes/TestRecentChangesHelper.php", # tests/phpunit/includes/content - 'DummyContentHandlerForTesting' => "$testDir/phpunit/includes/content/ContentHandlerTest.php", - 'DummyContentForTesting' => "$testDir/phpunit/includes/content/ContentHandlerTest.php", + 'DummyContentHandlerForTesting' => "$testDir/phpunit/mocks/content/DummyContentHandlerForTesting.php", + 'DummyContentForTesting' => "$testDir/phpunit/mocks/content/DummyContentForTesting.php", + 'DummyNonTextContentHandler' => "$testDir/phpunit/mocks/content/DummyNonTextContentHandler.php", + 'DummyNonTextContent' => "$testDir/phpunit/mocks/content/DummyNonTextContent.php", 'ContentHandlerTest' => "$testDir/phpunit/includes/content/ContentHandlerTest.php", 'JavaScriptContentTest' => "$testDir/phpunit/includes/content/JavaScriptContentTest.php", 'TextContentTest' => "$testDir/phpunit/includes/content/TextContentTest.php", @@ -82,6 +84,9 @@ $wgAutoloadClasses += array( # tests/phpunit/includes/diff 'FakeDiffOp' => "$testDir/phpunit/includes/diff/FakeDiffOp.php", + # tests/phpunit/includes/logging + 'LogFormatterTestCase' => "$testDir/phpunit/includes/logging/LogFormatterTestCase.php", + # tests/phpunit/includes/password 'PasswordTestCase' => "$testDir/phpunit/includes/password/PasswordTestCase.php", @@ -89,6 +94,9 @@ $wgAutoloadClasses += array( 'ResourceLoaderImageModuleTest' => "$testDir/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php", 'ResourceLoaderImageModuleTestable' => "$testDir/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php", + # tests/phpunit/includes/specials + 'SpecialPageTestBase' => "$testDir/phpunit/includes/specials/SpecialPageTestBase.php", + # tests/phpunit/languages 'LanguageClassesTestCase' => "$testDir/phpunit/languages/LanguageClassesTestCase.php", @@ -109,6 +117,7 @@ $wgAutoloadClasses += array( 'MockImageHandler' => "$testDir/phpunit/mocks/media/MockImageHandler.php", 'MockSvgHandler' => "$testDir/phpunit/mocks/media/MockSvgHandler.php", 'MockDjVuHandler' => "$testDir/phpunit/mocks/media/MockDjVuHandler.php", + 'MockWebRequest' => "$testDir/phpunit/mocks/MockWebRequest.php", # tests/parser 'NewParserTest' => "$testDir/phpunit/includes/parser/NewParserTest.php", diff --git a/tests/browser/environments.yml b/tests/browser/environments.yml index 8f8381ed..b2232e62 100644 --- a/tests/browser/environments.yml +++ b/tests/browser/environments.yml @@ -14,7 +14,7 @@ # export MEDIAWIKI_USER=Selenium_user2 # bundle exec cucumber # -mw-vagrant-host: +mw-vagrant-host: &default mediawiki_url: http://127.0.0.1:8080/wiki/ mediawiki_user: Selenium_user mediawiki_password: vagrant @@ -33,3 +33,5 @@ test2: mediawiki_url: http://test2.wikipedia.org/wiki/ mediawiki_user: Selenium_user # mediawiki_password: SET THIS IN THE ENVIRONMENT! + +default: *default diff --git a/tests/browser/features/create_account.feature b/tests/browser/features/create_account.feature index 0b4e83a5..35df8b4b 100644 --- a/tests/browser/features/create_account.feature +++ b/tests/browser/features/create_account.feature @@ -10,3 +10,8 @@ Feature: Create account | Special:CreateAccount | | Special:UserLogin/signup | | Special:UserLogin?type=signup | + + Scenario: If no username is entered then an error is displayed + Given I go to Create account page at Special:CreateAccount + When I submit the form + Then an error message is displayed diff --git a/tests/browser/features/step_definitions/create_account_steps.rb b/tests/browser/features/step_definitions/create_account_steps.rb index 03bff66f..98e0f2cb 100644 --- a/tests/browser/features/step_definitions/create_account_steps.rb +++ b/tests/browser/features/step_definitions/create_account_steps.rb @@ -16,3 +16,11 @@ end Then(/^form has Create account button$/) do expect(on(CreateAccountPage).create_account_element).to exist end + +When(/^I submit the form$/) do + on(CreateAccountPage).create_account +end + +Then(/^an error message is displayed$/) do + expect(on(CreateAccountPage).error_message_element.class_name).to eq 'errorbox' +end diff --git a/tests/browser/features/support/pages/create_account_page.rb b/tests/browser/features/support/pages/create_account_page.rb index 98b893a6..9aa00cd2 100644 --- a/tests/browser/features/support/pages/create_account_page.rb +++ b/tests/browser/features/support/pages/create_account_page.rb @@ -15,4 +15,5 @@ class CreateAccountPage page_url '<%=params[:page_title]%>' button(:create_account, id: 'wpCreateaccount') + div(:error_message, id: 'mw-createacct-status-area') end diff --git a/tests/browser/features/support/pages/main_page.rb b/tests/browser/features/support/pages/main_page.rb index 6d76b01c..3092ab5c 100644 --- a/tests/browser/features/support/pages/main_page.rb +++ b/tests/browser/features/support/pages/main_page.rb @@ -3,16 +3,16 @@ class MainPage page_url '' - a(:edit_link, href: /action=edit/) + a(:edit_link, css: '#ca-edit a') li(:help_link, id: 'n-help') div(:page_content, id: 'content') li(:page_information_link, id: 't-info') li(:permanent_link_link, id: 't-permalink') - a(:printable_version_link, href: /printable=yes/) + a(:printable_version_link, css: '#t-print a') li(:random_page_link, id: 'n-randompage') li(:recent_changes_link, id: 'n-recentchanges') li(:related_changes_link, id: 't-recentchangeslinked') li(:special_pages_link, id: 't-specialpages') - a(:view_history_link, href: /action=history/) + a(:view_history_link, css: '#ca-history a') li(:what_links_here_link, id: 't-whatlinkshere') end diff --git a/tests/browser/features/support/pages/preferences_appearance_page.rb b/tests/browser/features/support/pages/preferences_appearance_page.rb index 83c3952f..1e5ffaa1 100644 --- a/tests/browser/features/support/pages/preferences_appearance_page.rb +++ b/tests/browser/features/support/pages/preferences_appearance_page.rb @@ -27,7 +27,7 @@ class PreferencesAppearancePage radio_button(:monobook, id: 'mw-input-wpskin-monobook') radio_button(:no_preference_radio, id: 'mw-input-wpdate-default') text_field(:other_offset, id: 'mw-input-wptimecorrection-other') - a(:restore_default_link, href: /reset/) + a(:restore_default_link, id: 'mw-prefs-restoreprefs') select_list(:size_select, id: 'mw-input-wpimagesize') select_list(:threshold_select, id: 'mw-input-wpstubthreshold') select_list(:time_offset_select, id: 'mw-input-wptimecorrection') diff --git a/tests/browser/features/support/pages/view_history_page.rb b/tests/browser/features/support/pages/view_history_page.rb index bb9c5862..ee4d757a 100644 --- a/tests/browser/features/support/pages/view_history_page.rb +++ b/tests/browser/features/support/pages/view_history_page.rb @@ -1,6 +1,6 @@ class ViewHistoryPage include PageObject - a(:view_history_link, href: /action=history/) - a(:old_version_link, href: /oldid=/) + a(:view_history_link, css: '#ca-history a') + a(:old_version_link, css: '#pagehistory a.mw-changeslist-date') end diff --git a/tests/parser/parserTest.inc b/tests/parser/parserTest.inc index 1cffa20c..f429c30a 100644 --- a/tests/parser/parserTest.inc +++ b/tests/parser/parserTest.inc @@ -155,7 +155,7 @@ class ParserTest { static function setUp() { global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, - $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache, + $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgExtraNamespaces, $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo, $wgExtraInterlanguageLinkPrefixes, $wgLocalInterwikis, $parserMemc, $wgThumbnailScriptPath, $wgScriptPath, @@ -211,7 +211,6 @@ class ParserTest { $wgParserCacheType = CACHE_NONE; } - $wgEnableParserCache = false; DeferredUpdates::clearPendingUpdates(); $wgMemc = wfGetMainCache(); // checks $wgMainCacheType $messageMemc = wfGetMessageCacheStorage(); @@ -890,9 +889,9 @@ class ParserTest { 'wgDisableTitleConversion' => false, // Tidy options. 'wgUseTidy' => isset( $opts['tidy'] ), - 'wgAlwaysUseTidy' => false, + 'wgTidyConfig' => null, 'wgDebugTidy' => false, - 'wgTidyConf' => $IP . '/includes/tidy.conf', + 'wgTidyConf' => $IP . '/includes/tidy/tidy.conf', 'wgTidyOpts' => '', 'wgTidyInternal' => $this->tidySupport->isInternal(), ); @@ -937,6 +936,7 @@ class ParserTest { $wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp'; MagicWord::clearCache(); + MWTidy::destroySingleton(); return $context; } @@ -1219,6 +1219,7 @@ class ParserTest { FileBackendGroup::destroySingleton(); LockManagerGroup::destroySingletons(); LinkCache::singleton()->clear(); + MWTidy::destroySingleton(); foreach ( $this->savedGlobals as $var => $val ) { $GLOBALS[$var] = $val; diff --git a/tests/parser/parserTests.txt b/tests/parser/parserTests.txt index e9653529..c8c63f39 100644 --- a/tests/parser/parserTests.txt +++ b/tests/parser/parserTests.txt @@ -119,6 +119,13 @@ Template:echo_with_div !! endarticle !! article +Template:blank_param +!! text +{{{1}}} +{{{}}} +!! endarticle + +!! article Template:table_attribs !! text <noinclude> @@ -154,6 +161,22 @@ Template:table_attribs_5 !! 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 {{table_attribs}}!!style='color:red;'|''Bar''||style='color:brown;'|''Foo'' and Baz @@ -166,6 +189,13 @@ Template:table_cells !! endarticle !! article +Template:PartialTable +!! text +{| +|- +!! endarticle + +!! article Template:image_attribs !! text <noinclude> @@ -808,15 +838,12 @@ parsoid=wt2html !!end # same html as previous, but wikitext adjusted to match parsoid html2wt -# skipping wt2html and html2html because it wants to put <i> before <b> !! test Italics and bold: 5-quote opening sequence: (5,2+3) -!! options -parsoid=wt2wt,html2wt !! wikitext '''''foo''''' -!! html -<p><b><i>foo</i></b> +!! html/* +<p><i><b>foo</b></i> </p> !! end @@ -1300,7 +1327,7 @@ Non-word characters don't terminate tag names + tidy Non-word characters are valid in extension tags (T19663) !! wikitext <tåg>tåg</tåg> -!! html +!! html/php <pre> 'tåg' array ( @@ -1311,11 +1338,15 @@ 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 @@ -1349,9 +1380,11 @@ Bare pipe character from a template (bug 52363) <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 @@ -1364,7 +1397,7 @@ sed abit. :and a colon </nowiki> -!! html +!! html/php <p>Lorem ipsum dolor sed abit. @@ -1373,6 +1406,14 @@ 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 @@ -1386,7 +1427,7 @@ nowiki 3 *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> @@ -1394,6 +1435,15 @@ nowiki 3 <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 @@ -1407,11 +1457,11 @@ Entities inside <nowiki> !! test Entities inside template parameters -!! options -parsoid !! wikitext {{echo|–}} -!! html +!! html/php+tidy +<p>–</p> +!! html/parsoid <p><span typeof="mw:Transclusion mw:Entity" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&ndash;"}},"i":0}}]}'>–</span></p> !! end @@ -1419,17 +1469,27 @@ parsoid Properly escape nowiki when combined with other wiki markup !! options parsoid=html2wt -!! html +!! html/parsoid <p>* </nowiki> tag</p> !! wikitext -<nowiki>* </nowiki></nowiki> tag +<nowiki>*</nowiki> <nowiki></nowiki></nowiki> tag +!! end + +!! test +T93824: Put escaped HTML tags inside nowiki +!! options +parsoid=html2wt +!! html/parsoid +<p><h2>foo</h2></p> +!! wikitext +<nowiki><h2>foo</h2></nowiki> !! end !! test T71950: 1. Put nowiki as close to cause as possible, even with non-quote escapable chars !! options parsoid=html2wt -!! html +!! html/parsoid <p>This text: L'<a rel="mw:WikiLink" href="./Foo">Foo</a> This text: L''<a rel="mw:WikiLink" href="./Foo">Foo</a> This text: L'''<a rel="mw:WikiLink" href="./Foo">Foo</a>''</p> @@ -1444,7 +1504,7 @@ This text: L<nowiki>'''</nowiki>[[Foo]]<nowiki>''</nowiki> T71950: 2. Put nowiki as close to cause as possible, after ' :' !! options parsoid=html2wt -!! html +!! html/parsoid <p>This text : L''<a rel="mw:WikiLink" href="./Foo">Foo</a> </p> !! wikitext @@ -1483,6 +1543,30 @@ parsoid=html2wt {{echo|a <nowiki>}</nowiki>}} !! end +!! test +Cases where "!!" needs nowiki protection +!! options +parsoid=html2wt +!! html/parsoid +<table> +<tr><th>this needs protection !! here</th></tr> +</table> + +<table> +<tr><th>this does not need +protection !! here</th></tr> +</table> +!! wikitext +{| +!<nowiki>this needs protection !! here</nowiki> +|} + +{| +!this does not need +protection !! here +|} +!! end + ### ### Comments ### @@ -1587,8 +1671,10 @@ Comment whitespace Comment semantics and delimiters !! wikitext <!-- --><!----><!-----><!------> -!! html +!! html/php +!! html/parsoid +<!-- --><!----><!-----><!------> !! end !! test @@ -1596,8 +1682,11 @@ Comment semantics and delimiters, redux !! wikitext <!-- In SGML every "foo" here would actually show up in the text -- foo -- bar -- foo -- funky huh? ... --> -!! html +!! html/php +!! html/parsoid +<!-- In SGML every "foo" here would actually show up in the text -- foo -- bar +-- foo -- funky huh? ... --> !! end !! test @@ -1607,39 +1696,68 @@ Comment semantics and delimiters: directors cut everything starting with < followed by !-- until the first -- and > we see, that wouldn't be valid XML however, since in XML -- has to terminate a comment -->--> -!! html +!! html/php <p>--> </p> +!! html/parsoid +<!-- ... However we like to keep things simple and somewhat XML-ish so we eat +everything starting with < followed by !-- until the first -- and > we see, +that wouldn't be valid XML however, since in XML -- has to terminate a comment +--><p>--></p> !! end !! test Comment semantics: nesting !! wikitext <!--<!-- no, we're not going to do anything fancy here -->--> -!! html +!! html/php <p>--> </p> +!! html/parsoid +<!--<!-- no, we're not going to do anything fancy here --><p>--></p> !! end +# Parsoid closes the unclosed comment, even if it means a slight +# round-trip diff. !! test Comment semantics: unclosed comment at end +!! options +parsoid=wt2html,html2html !! wikitext <!--This comment will run out to the end of the document -!! html +!! html/php +!! html/parsoid +<!--This comment will run out to the end of the document--> !! end -# Bug 58184: document parsoid's behaviour !! test -Suppress comment closing tag in lenient browsers -!! options -parsoid=wt2html,html2html +Comment semantics: normalize comments to play nice with XML and browsers !! wikitext -<!-- Browsers--!> think this is closed --> +<!-- Browsers --!> think this is closed --> +<!--> This would normally be text --> +<!---> As would this --> +<!-- XML doesn't like trailing dashes --------> +<!-- Nor doubled hyphens -- anywhere in the data --> +But this is not a comment. !! html/php +<p>But this is not a comment. +</p> +!! html/parsoid +<!-- Browsers --!> think this is closed --> +<!--> This would normally be text --> +<!---> As would this --> +<!-- XML doesn't like trailing dashes --------> +<!-- Nor doubled hyphens -- anywhere in the data --> +<p>But this is not a comment.</p> +!! end +!! test +Comment semantics: round-trip even text which contains encoded --> +!! wikitext +<!-- hello & goodbye - > --> --&gt; --&xx --> !! html/parsoid -<!-- Browsers--¡> think this is closed --> +<!-- hello & goodbye - > --> --&gt; --&xx --> !! end !! test @@ -1682,10 +1800,15 @@ parsoid=wt2html,wt2wt !! wikitext <!--c1-->*a <!--c2--><!--c3--><!--c4-->*b -!! html -<ul> +!! html/php +<ul><li>a</li> +<li>b</li></ul> + +!! html/parsoid +<!--c1--><ul> <li>a </li> +<!--c2--><!--c3--><!--c4--> <li>b </li> </ul> @@ -1894,7 +2017,7 @@ a [[Category:A1]] [[Category:A2]] [[Category:A4]] !! html/parsoid <p>a</p> -<link href="Category:A1"/> <link href="Category:A2"/> <link href="Category:A3"/> <link href="Category:A4"/> +<link rel="mw:PageProp/Category" href="./Category:A1"/> <link rel="mw:PageProp/Category" href="./Category:A2"/> <link rel="mw:PageProp/Category" href="./Category:A3"/> <link rel="mw:PageProp/Category" href="./Category:A4"/> !! end !! test @@ -1904,7 +2027,7 @@ parsoid=wt2html !! wikitext [[Category:A1]]a !! html/parsoid -<link href="Category:A1"/><p>a</p> +<link rel="mw:PageProp/Category" href="./Category:A1"/><p>a</p> !! end ### @@ -1934,13 +2057,28 @@ Tabs don't trigger preformatted text preformatted text. This is preformatted text. So is this. -!! html +!! html/php <p> This is not preformatted text. </p> <pre>This is preformatted text. So is this. </pre> +!! html/parsoid +<p> This is not + preformatted text.</p> +<pre>This is preformatted text. + So is this.</pre> +!! end + +!! test +Space before tab needs nowiki pre protection +!! options +parsoid=html2wt +!! html/parsoid +<p> a</p> +!! wikitext +<nowiki> </nowiki> a !! end !! test @@ -2140,7 +2278,7 @@ Entities inside <pre> </nowiki> </pre> -!! html +!! html/php <pre> <nowiki> </pre> @@ -2153,6 +2291,18 @@ Entities inside <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 @@ -2209,13 +2359,17 @@ HTML pre followed by indent-pre </pre> !! end +# Note that tidy removes the empty <p> tags from the start and end. +# Parsoid does not, by design. !!test Block tag pre -!!options -parsoid !! wikitext <p><pre>foo</pre></p> -!! html +!! html/php+tidy +<pre> +foo +</pre> +!! html/parsoid <p data-parsoid='{"stx":"html","autoInsertedEnd":true}'></p><pre data-parsoid='{"stx":"html"}'>foo</pre><p data-parsoid='{"autoInsertedStart":true,"stx":"html"}'></p> !!end @@ -2339,6 +2493,19 @@ Templates: Indent-Pre: 1f: Wrapping should be based on expanded content !!end !! test +Pres with newline attributes +!! wikitext +<pre class="one +two">hi</pre> +!! html/php +<pre class="one two">hi</pre> + +!! html/parsoid +<pre class="one +two" data-parsoid='{"stx":"html"}'>hi</pre> +!! end + +!! test Things that look like <pre> tags aren't treated as such !! wikitext Barack Obama <President> of the United States @@ -2377,7 +2544,10 @@ Parsoid: handle pre with space after attribute parsoid=wt2html !! wikitext <pre style="width:50%;" >{{echo|foo}}</pre> -!! html +!! html/php +<pre style="width:50%;">{{echo|foo}}</pre> + +!! html/parsoid <pre style="width:50%;">{{echo|foo}}</pre> !! end @@ -2583,32 +2753,32 @@ Templates: Other wikitext in parameter names (bug 67657) #-------------------------------------------------------------------- !! test Templates: Parsoid parameter escaping test 1 -!! options -parsoid !! wikitext {{echo|[foo]|{{echo|[bar]}}}} -!! html +!! html/php+tidy +<p>[foo]</p> +!! html/parsoid <p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[foo]"},"2":{"wt":"{{echo|[bar]}}"}},"i":0}}]}'>[foo]</p> !! end !! test Parsoid: Pipes in external links in template parameter -!! options -parsoid !! wikitext {{echo|[{{echo|http://example.com}} link]}} -!! html +!! html/php+tidy +<p><a rel="nofollow" class="external text" href="http://example.com">link</a></p> +!! html/parsoid <p><a rel="mw:ExtLink" href="http://example.com" about="#mwt31" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[{{echo|http://example.com}} link]"}},"i":0}}]}'>link</a></p> !! end !! test Parsoid: pipe in transclusion parameter -!! options -parsoid !! wikitext {{echo|http://foo.com/a|b}} -!! html +!! html/php+tidy +<p><a rel="nofollow" class="external free" href="http://foo.com/a%7Cb">http://foo.com/a%7Cb</a></p> +!! html/parsoid <p><a rel="mw:ExtLink" href="http://foo.com/a|b" about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"http://foo.com/a&#124;b"}},"i":0}}]}'>http://foo.com/a|b</a></p> @@ -2620,7 +2790,9 @@ Parsoid: Pipe in external link target and content in template parameter parsoid=html2wt,wt2wt !! wikitext {{echo|[http://foo.com/a|b a|b]}} -!! html +!! html/php+tidy +<p><a rel="nofollow" class="external text" href="http://foo.com/a%7Cb">a|b</a></p> +!! html/parsoid <p><a rel="mw:ExtLink" href="http://foo.com/a|b" about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"}, @@ -2645,7 +2817,9 @@ parsoid=html2wt,wt2wt {{echo|foo<nowiki>|</nowiki>bar}} {{echo|<nowiki><div></nowiki>}} {{echo|<nowiki></nowiki>}} -!! html +!! html/php+tidy +<p>foo|bar <div></p> +!! html/parsoid <p><span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo<nowiki>|</nowiki>bar"}},"i":0}}]}'}'>foo</span><span typeof="mw:Nowiki" about="#mwt1">|</span><span about="#mwt1">bar</span> <span typeof="mw:Transclusion mw:Nowiki" about="#mwt2" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"<nowiki>&lt;div&gt;</nowiki>"}},"i":0}}]}'><span typeof="mw:Entity"><</span>div<span typeof="mw:Entity">></span></span> <span typeof="mw:Transclusion mw:Nowiki" about="#mwt3" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"<nowiki></nowiki>"}},"i":0}}]}'></span> @@ -2659,19 +2833,21 @@ Templates: '=' char in nested transclusions should not trigger nowiki escapes or parsoid=html2wt,wt2wt !! wikitext {{echo|{{echo|1=bar}}}} -!! html +!! html/php+tidy +<p>bar</p> +!! html/parsoid <p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"{{echo|1=bar}}"}},"i":0}}]}'>bar</p> !! end ## Bug 56733 !! test Templates parameters with special tokenizing behavior dont get modified because of arg escaping -!! options -parsoid !! wikitext {{echo|a : b}} -!! html -<p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"a : b"}},"i":0}}]}'>a<span typeof="mw:Placeholder" data-parsoid='{"isDisplayHack":true}'> </span>: b</p> +!! html/php+tidy +<p>a : b</p> +!! html/parsoid +<p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"a : b"}},"i":0}}]}'>a<span typeof="mw:DisplaySpace mw:Placeholder" data-parsoid='{"isDisplayHack":true}'> </span>: b</p> !! end ## Bug T73412 @@ -2679,13 +2855,23 @@ parsoid Templates: Preserve blank parameter names !! wikitext {{echo|=foo}} -!! html/php -<p>{{{1}}} -</p> +!! html/php+tidy +<p>{{{1}}}</p> !! html/parsoid <p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"":{"wt":"foo"}},"i":0}}]}'>{{{1}}}</p> !! end +!! test +Templates: Preserve blank parameter names in other positions +!! wikitext +{{blank_param|bar|=foo}} +!! html/php+tidy +<p>bar foo</p> +!! html/parsoid +<p about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[{"k":"1","spc":["","","",""]},{"k":"","named":true,"spc":["","","",""]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"blank_param","href":"./Template:Blank_param"},"params":{"1":{"wt":"bar"},"":{"wt":"foo"}},"i":0}}]}'>bar +foo</p> +!! end + ### ### Parsoid-centric tests for testing RT edge cases for pre ### @@ -3136,9 +3322,10 @@ parsoid=wt2html,wt2wt !! wikitext [[Category:foo]] <!-- No pre-wrapping --> {{echo| [[Category:foo]]}} <!-- No pre-wrapping --> -!! html - <link rel="mw:PageProp/Category" href="./Category:Foo"> <!-- No pre-wrapping --> -<span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":" [[Category:foo]]"}},"i":0}}]}'> </span><link rel="mw:PageProp/Category" href="./Category:Foo" about="#mwt1"> <!-- No pre-wrapping --> +!! html/php+tidy +!! html/parsoid + <link rel="mw:PageProp/Category" href="./Category:Foo"> <!-- No pre-wrapping --> +<span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":" [[Category:foo]]"}},"i":0}}]}'> </span><link rel="mw:PageProp/Category" href="./Category:Foo" about="#mwt1"> <!-- No pre-wrapping --> !! end !! test @@ -3149,9 +3336,22 @@ parsoid=wt2html,wt2wt [[Category:foo]] a [[Category:foo]] {{echo|b}} !! html -<pre> -<link rel="mw:PageProp/Category" href="./Category:Foo"> a -<link rel="mw:PageProp/Category" href="./Category:Foo"> <span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"b"}},"i":0}}]}'>b</span></pre> +<pre><link rel="mw:PageProp/Category" href="./Category:Foo"> a + <link rel="mw:PageProp/Category" href="./Category:Foo"> <span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"b"}},"i":0}}]}'>b</span></pre> +!! end + +!! test +Indent-Pre: Newlines in comments shouldn't affect sol state +!! wikitext +a <!-- +foo +--> b +!! html/php+tidy +<p>a b</p> +!! html/parsoid +<p>a <!-- +foo +--> b</p> !! end ### @@ -3177,8 +3377,10 @@ foo foo </pre> -!! html -<pre>foo</pre> +!! html/php+tidy +<pre> +foo +</pre> <pre> foo </pre> @@ -3191,7 +3393,6 @@ foo foo </pre> - !! html/parsoid <pre data-parsoid='{"stx":"html"}'>foo</pre> @@ -3230,7 +3431,7 @@ haha </pre> -!! html +!! html/php+tidy <pre> @@ -3247,7 +3448,6 @@ haha </pre> - !! html/parsoid <pre data-parsoid='{"stx":"html"}'> @@ -3290,7 +3490,7 @@ HTML-pre: 3: other wikitext '' no-italic '' [[ NoLink ]] </pre> -!! html +!! html/php <pre> * foo # bar @@ -3299,6 +3499,13 @@ HTML-pre: 3: other wikitext [[ NoLink ]] </pre> +!! html/parsoid +<pre data-parsoid='{"stx":"html","strippedNL":true}'>* foo +# bar += no-h = +'' no-italic '' +[[ NoLink ]] +</pre> !!end ### @@ -3570,20 +3777,26 @@ Definition Lists: Hacky use to indent tables, with comment before table </tr></table></dd></dl></dd></dl> !! end -# Bug 52473 +# The trailing whitespace in this test is to catch a regression in +# Parsoid after T54473. !! test Definition Lists: Hacky use to indent tables (WS-insensitive) -!! options -parsoid !! wikitext : {| |a |} -!! html -<dl> -<dd> <table><tr><td>a</td></tr></table> </dd> -</dl> +!! html/php +<dl><dd><table> +<tr> +<td>a +</td></tr></table></dd></dl> + +!! html/parsoid +<dl><dd> <table> +<tbody><tr><td>a</td></tr> +</tbody></table> </dd></dl> !! end + ## The PHP parser treats : items (dd) without a corresponding ; item (dt) ## as an empty dt item. It also ignores all but the last ";" when followed ## by ":" later on. So, ";" are not ignored in ";;;t3" but are ignored in @@ -3616,7 +3829,7 @@ parsoid ## ## All Parsoid only definition list tests have this difference. ## -## See also: https://bugzilla.wikimedia.org/show_bug.cgi?id=6569 +## See also: https://phabricator.wikimedia.org/T8569 ## and http://lists.wikimedia.org/pipermail/wikitext-l/2011-November/000483.html !! test @@ -3706,12 +3919,19 @@ parsoid !! test Definition Lists: Nesting: Test 2 (Parsoid only) -!! options -parsoid !! wikitext ;t1 ::d2 -!! html +!! html/php+tidy +<dl> +<dt>t1</dt> +<dd> +<dl> +<dd>d2</dd> +</dl> +</dd> +</dl> +!! html/parsoid <dl> <dt>t1</dt> <dd> @@ -3726,12 +3946,27 @@ parsoid !! test Definition Lists: Nesting: Test 3 (Parsoid only) -!! options -parsoid !! wikitext :;t1 ::::d2 -!! html +!! html/php+tidy +<dl> +<dd> +<dl> +<dt>t1</dt> +<dd> +<dl> +<dd> +<dl> +<dd>d2</dd> +</dl> +</dd> +</dl> +</dd> +</dl> +</dd> +</dl> +!! html/parsoid <dl> <dd> <dl> @@ -4006,6 +4241,9 @@ Definition Lists: Mixed Lists: Test 11 # Another case where tidy converts a <dt> to a <dd> (but Parsoid doesn't). +# From whitelist: +# * The test is wrong, there are two colons where there should be :; +# * The PHP parser is wrong to close the <dl> after the <dt> containing the <ul>. !! test Definition Lists: Weird Ones: Test 1 !! wikitext @@ -4063,7 +4301,7 @@ Definition Lists: Weird Ones: Test 1 <dl> <dt> <dl> -<dt> foo<span typeof="mw:Placeholder"> </span></dt> +<dt> foo<span typeof="mw:DisplaySpace mw:Placeholder" data-parsoid='{"src":" ","isDisplayHack":true}'> </span></dt> <dd data-parsoid='{"stx":"row"}'> bar (who uses this?)</dd> </dl></dt> </dl></dd> @@ -4120,6 +4358,17 @@ Definition Lists: colons occurring in tags </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 @@ -4441,6 +4690,25 @@ http://example.com/url_with_entity< !! end !! test +External links: Lone protocols are never linked (T105697) +!! wikitext +http:// +http://; +(http://) +bitcoin: +bitcoin:; +(bitcoin:) +!! html +<p>http:// +http://; +(http://) +bitcoin: +bitcoin:; +(bitcoin:) +</p> +!! end + +!! test External links: No preceding word characters allowed (bug 65278) !! wikitext NOPEhttp://example.com @@ -4981,38 +5249,6 @@ External link containing a single quote. (bug 63947) <p><a rel="mw:ExtLink" href="//foo.org/bar'baz">bang</a></p> !! end - -!! test -External link containing a period in the anchor. (bug 63947) -!! wikitext -[//foo.org/bar#baz. bang] - -[//foo.org/bar. bang] -!! html/php -<p><a rel="nofollow" class="external text" href="//foo.org/bar#baz.">bang</a> -</p><p><a rel="nofollow" class="external text" href="//foo.org/bar.">bang</a> -</p> -!! html/parsoid -<p><a rel="mw:ExtLink" href="//foo.org/bar#baz.">bang</a></p> -<p><a rel="mw:ExtLink" href="//foo.org/bar.">bang</a></p> -!! end - -!! test -External link containing a single quote. (bug 63947) -!! wikitext -[//foo.org/bar'baz] - -[//foo.org/bar'baz bang] -!! html/php -<p><a rel="nofollow" class="external autonumber" href="//foo.org/bar'baz">[1]</a> -</p><p><a rel="nofollow" class="external text" href="//foo.org/bar'baz">bang</a> -</p> -!! html/parsoid -<p><a rel="mw:ExtLink" href="//foo.org/bar'baz"></a></p> -<p><a rel="mw:ExtLink" href="//foo.org/bar'baz">bang</a></p> -!! end - - !! test External link containing double-single-quotes in text '' (bug 4598 sanity check) !! wikitext @@ -5048,9 +5284,22 @@ External link containing double-single-quotes with no space separating the url f External link with comments in link text !! wikitext [http://www.google.com Google <!-- comment -->] -!! html +!! html/php <p><a rel="nofollow" class="external text" href="http://www.google.com">Google </a> </p> +!! html/parsoid +<p><a rel="mw:ExtLink" href="http://www.google.com">Google <!-- comment --></a></p> +!! end + +!! test +External link to bare IPv4 address +!! wikitext +[http://192.168.0.1 Link] +!! html/php +<p><a rel="nofollow" class="external text" href="http://192.168.0.1">Link</a> +</p> +!! html/parsoid +<p><a rel="mw:ExtLink" href="http://192.168.0.1">Link</a></p> !! end !! test @@ -5088,14 +5337,129 @@ http://example.com/index.php?foozoid[]=bar !! end !! test -IPv6 urls (bug 21261) -!! options -disabled +IPv6 urls, autolink format (T23261) !! wikitext http://[2404:130:0:1000::187:2]/index.php -!! html + +Examples from RFC 2373, section 2.2: +* http://[1080::8:800:200C:417A]/unicast +* http://[FF01::101]/multicast +* http://[::1]/loopback +* http://[::]/unspecified +* http://[::13.1.68.3]/ipv4compat +* http://[::FFFF:129.144.52.38]/ipv4compat + +Examples from RFC 2732, section 2: +* http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html +* http://[1080:0:0:0:8:800:200C:417A]/index.html +* http://[3ffe:2a00:100:7031::1] +* http://[1080::8:800:200C:417A]/foo +* http://[::192.9.5.5]/ipng +* http://[::FFFF:129.144.52.38]:80/index.html +* http://[2010:836B:4179::836B:4179] + +!! html/php <p><a rel="nofollow" class="external free" href="http://[2404:130:0:1000::187:2]/index.php">http://[2404:130:0:1000::187:2]/index.php</a> -</p> +</p><p>Examples from <a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc2373">RFC 2373</a>, section 2.2: +</p> +<ul><li> <a rel="nofollow" class="external free" href="http://[1080::8:800:200C:417A]/unicast">http://[1080::8:800:200C:417A]/unicast</a></li> +<li> <a rel="nofollow" class="external free" href="http://[FF01::101]/multicast">http://[FF01::101]/multicast</a></li> +<li> <a rel="nofollow" class="external free" href="http://[::1]/loopback">http://[::1]/loopback</a></li> +<li> <a rel="nofollow" class="external free" href="http://[::]/unspecified">http://[::]/unspecified</a></li> +<li> <a rel="nofollow" class="external free" href="http://[::13.1.68.3]/ipv4compat">http://[::13.1.68.3]/ipv4compat</a></li> +<li> <a rel="nofollow" class="external free" href="http://[::FFFF:129.144.52.38]/ipv4compat">http://[::FFFF:129.144.52.38]/ipv4compat</a></li></ul> +<p>Examples from <a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc2732">RFC 2732</a>, section 2: +</p> +<ul><li> <a rel="nofollow" class="external free" href="http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html">http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html</a></li> +<li> <a rel="nofollow" class="external free" href="http://[1080:0:0:0:8:800:200C:417A]/index.html">http://[1080:0:0:0:8:800:200C:417A]/index.html</a></li> +<li> <a rel="nofollow" class="external free" href="http://[3ffe:2a00:100:7031::1]">http://[3ffe:2a00:100:7031::1]</a></li> +<li> <a rel="nofollow" class="external free" href="http://[1080::8:800:200C:417A]/foo">http://[1080::8:800:200C:417A]/foo</a></li> +<li> <a rel="nofollow" class="external free" href="http://[::192.9.5.5]/ipng">http://[::192.9.5.5]/ipng</a></li> +<li> <a rel="nofollow" class="external free" href="http://[::FFFF:129.144.52.38]:80/index.html">http://[::FFFF:129.144.52.38]:80/index.html</a></li> +<li> <a rel="nofollow" class="external free" href="http://[2010:836B:4179::836B:4179]">http://[2010:836B:4179::836B:4179]</a></li></ul> + +!! html/parsoid +<p><a rel="mw:ExtLink" href="http://[2404:130:0:1000::187:2]/index.php">http://[2404:130:0:1000::187:2]/index.php</a></p> + +<p>Examples from <a href="//tools.ietf.org/html/rfc2373" rel="mw:ExtLink">RFC 2373</a>, section 2.2:</p> +<ul><li> <a rel="mw:ExtLink" href="http://[1080::8:800:200C:417A]/unicast">http://[1080::8:800:200C:417A]/unicast</a></li> +<li> <a rel="mw:ExtLink" href="http://[FF01::101]/multicast">http://[FF01::101]/multicast</a></li> +<li> <a rel="mw:ExtLink" href="http://[::1]/loopback">http://[::1]/loopback</a></li> +<li> <a rel="mw:ExtLink" href="http://[::]/unspecified">http://[::]/unspecified</a></li> +<li> <a rel="mw:ExtLink" href="http://[::13.1.68.3]/ipv4compat">http://[::13.1.68.3]/ipv4compat</a></li> +<li> <a rel="mw:ExtLink" href="http://[::FFFF:129.144.52.38]/ipv4compat">http://[::FFFF:129.144.52.38]/ipv4compat</a></li></ul> + +<p>Examples from <a href="//tools.ietf.org/html/rfc2732" rel="mw:ExtLink">RFC 2732</a>, section 2:</p> +<ul><li> <a rel="mw:ExtLink" href="http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html">http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html</a></li> +<li> <a rel="mw:ExtLink" href="http://[1080:0:0:0:8:800:200C:417A]/index.html">http://[1080:0:0:0:8:800:200C:417A]/index.html</a></li> +<li> <a rel="mw:ExtLink" href="http://[3ffe:2a00:100:7031::1]">http://[3ffe:2a00:100:7031::1]</a></li> +<li> <a rel="mw:ExtLink" href="http://[1080::8:800:200C:417A]/foo">http://[1080::8:800:200C:417A]/foo</a></li> +<li> <a rel="mw:ExtLink" href="http://[::192.9.5.5]/ipng">http://[::192.9.5.5]/ipng</a></li> +<li> <a rel="mw:ExtLink" href="http://[::FFFF:129.144.52.38]:80/index.html">http://[::FFFF:129.144.52.38]:80/index.html</a></li> +<li> <a rel="mw:ExtLink" href="http://[2010:836B:4179::836B:4179]">http://[2010:836B:4179::836B:4179]</a></li></ul> +!! end + +!! test +IPv6 urls, bracketed format (T23261) +!! wikitext +[http://[2404:130:0:1000::187:2]/index.php test] + +Examples from RFC 2373, section 2.2: +* [http://[1080::8:800:200C:417A] unicast] +* [http://[FF01::101] multicast] +* [http://[::1]/ loopback] +* [http://[::] unspecified] +* [http://[::13.1.68.3] ipv4compat] +* [http://[::FFFF:129.144.52.38] ipv4compat] + +Examples from RFC 2732, section 2: +* [http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html 1] +* [http://[1080:0:0:0:8:800:200C:417A]/index.html 2] +* [http://[3ffe:2a00:100:7031::1] 3] +* [http://[1080::8:800:200C:417A]/foo 4] +* [http://[::192.9.5.5]/ipng 5] +* [http://[::FFFF:129.144.52.38]:80/index.html 6] +* [http://[2010:836B:4179::836B:4179] 7] + +!! html/php +<p><a rel="nofollow" class="external text" href="http://[2404:130:0:1000::187:2]/index.php">test</a> +</p><p>Examples from <a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc2373">RFC 2373</a>, section 2.2: +</p> +<ul><li> <a rel="nofollow" class="external text" href="http://[1080::8:800:200C:417A]">unicast</a></li> +<li> <a rel="nofollow" class="external text" href="http://[FF01::101]">multicast</a></li> +<li> <a rel="nofollow" class="external text" href="http://[::1]/">loopback</a></li> +<li> <a rel="nofollow" class="external text" href="http://[::]">unspecified</a></li> +<li> <a rel="nofollow" class="external text" href="http://[::13.1.68.3]">ipv4compat</a></li> +<li> <a rel="nofollow" class="external text" href="http://[::FFFF:129.144.52.38]">ipv4compat</a></li></ul> +<p>Examples from <a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc2732">RFC 2732</a>, section 2: +</p> +<ul><li> <a rel="nofollow" class="external text" href="http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html">1</a></li> +<li> <a rel="nofollow" class="external text" href="http://[1080:0:0:0:8:800:200C:417A]/index.html">2</a></li> +<li> <a rel="nofollow" class="external text" href="http://[3ffe:2a00:100:7031::1]">3</a></li> +<li> <a rel="nofollow" class="external text" href="http://[1080::8:800:200C:417A]/foo">4</a></li> +<li> <a rel="nofollow" class="external text" href="http://[::192.9.5.5]/ipng">5</a></li> +<li> <a rel="nofollow" class="external text" href="http://[::FFFF:129.144.52.38]:80/index.html">6</a></li> +<li> <a rel="nofollow" class="external text" href="http://[2010:836B:4179::836B:4179]">7</a></li></ul> + +!! html/parsoid +<p><a rel="mw:ExtLink" href="http://[2404:130:0:1000::187:2]/index.php">test</a></p> + +<p>Examples from <a href="//tools.ietf.org/html/rfc2373" rel="mw:ExtLink">RFC 2373</a>, section 2.2:</p> +<ul><li> <a rel="mw:ExtLink" href="http://[1080::8:800:200C:417A]">unicast</a></li> +<li> <a rel="mw:ExtLink" href="http://[FF01::101]">multicast</a></li> +<li> <a rel="mw:ExtLink" href="http://[::1]/">loopback</a></li> +<li> <a rel="mw:ExtLink" href="http://[::]">unspecified</a></li> +<li> <a rel="mw:ExtLink" href="http://[::13.1.68.3]">ipv4compat</a></li> +<li> <a rel="mw:ExtLink" href="http://[::FFFF:129.144.52.38]">ipv4compat</a></li></ul> + +<p>Examples from <a href="//tools.ietf.org/html/rfc2732" rel="mw:ExtLink">RFC 2732</a>, section 2:</p> +<ul><li> <a rel="mw:ExtLink" href="http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html">1</a></li> +<li> <a rel="mw:ExtLink" href="http://[1080:0:0:0:8:800:200C:417A]/index.html">2</a></li> +<li> <a rel="mw:ExtLink" href="http://[3ffe:2a00:100:7031::1]">3</a></li> +<li> <a rel="mw:ExtLink" href="http://[1080::8:800:200C:417A]/foo">4</a></li> +<li> <a rel="mw:ExtLink" href="http://[::192.9.5.5]/ipng">5</a></li> +<li> <a rel="mw:ExtLink" href="http://[::FFFF:129.144.52.38]:80/index.html">6</a></li> +<li> <a rel="mw:ExtLink" href="http://[2010:836B:4179::836B:4179]">7</a></li></ul> !! end !! test @@ -5113,7 +5477,8 @@ Non-extlinks in brackets [{{echo|foo}}l's errand] [url={{echo|foo}}] [url=http://example.com] -!! html +[http:// bare protocols don't count] +!! html/php <p>[foo] [foo bar] [foo <i>bar</i>] @@ -5126,7 +5491,22 @@ Non-extlinks in brackets [fool's errand] [url=foo] [url=<a rel="nofollow" class="external free" href="http://example.com">http://example.com</a>] +[http:// bare protocols don't count] </p> +!! html/parsoid +<p>[foo] +[foo bar] +[foo <i>bar</i>] +[fool's] errand +[fool's errand] +[<span typeof="mw:Placeholder" data-parsoid='{"src":"{{echo|foo}}"}'>foo</span>] +[<span typeof="mw:Placeholder" data-parsoid='{"src":"{{echo|foo}}"}'>foo</span> bar] +[<span typeof="mw:Placeholder" data-parsoid='{"src":"{{echo|foo}}"}'>foo</span> <i>bar</i>] +[<span typeof="mw:Placeholder" data-parsoid='{"src":"{{echo|foo}}l's"}'>fool's</span>] errand +[<span typeof="mw:Placeholder" data-parsoid='{"src":"{{echo|foo}}l's"}'>fool's</span> errand] +[<span typeof="mw:Placeholder" data-parsoid='{"src":"url={{echo|foo}}"}'>url=foo</span>] +[url=<a rel="mw:ExtLink" href="http://example.com">http://example.com</a>] +[http:// bare protocols don't count]</p> !! end !! test @@ -5208,15 +5588,55 @@ Parenthesis in external links, w/ transclusion or comment !! end !! test -Replace invalid link targets when serializing +Serialize <a> tags with invalid link targets as plain text !! options parsoid=html2wt -!! html -<a rel="mw:WikiLink" href="./]] foo [[bar">Manual</a> +!! html/parsoid +<a rel="mw:WikiLink" href="[[foo]]">text</a> +<a rel="mw:WikiLink" href="[[foo]]">*text</a> +<a rel="mw:WikiLink" href="[[foo]]">[[foo]]</a> +<a rel="mw:WikiLink" href="[[foo]]">*a [[foo]]</a> !! wikitext -[[MediaWiki:Badtitletext|Manual]] +text +<nowiki>*</nowiki>text +<nowiki>[[foo]]</nowiki> +<nowiki>*a [[foo]]</nowiki> !! end +!! test +mw:ExtLink -vs- mw:WikiLink (T94723) +!! options +parsoid=html2wt +!! html/parsoid +<a rel="mw:WikiLink" href="./Foo" title="Foo" data-parsoid='{"stx":"piped","a":{"href":"./Foo"},"sa":{"href":"Foo"},"dsr":[0,11,6,2]}'>Bar</a> +<a rel="mw:WikiLink" href="./Foo" title="Foo">Bar</a> +<a rel="mw:WikiLink" href="http://en.wikipedia.org/wiki/Foo" title="Foo">Bar</a> +<a rel="mw:ExtLink" href="http://en.wikipedia.org/wiki/Foo" title="Foo">Bar</a> +<p> +<a rel="mw:ExtLink" href="http://en.wikipedia.org/wiki/European_Robin">European Robin</a> +<a rel="mw:WikiLink" href="http://en.wikipedia.org/wiki/European_Robin">European Robin</a> +</p> +!! wikitext +[[Foo|Bar]] +[[Foo|Bar]] +[[wikipedia:Foo|Bar]] +[[wikipedia:Foo|Bar]] + +[[wikipedia:European_Robin|European Robin]] +[[wikipedia:European_Robin|European Robin]] +!! end + +!! test +mw:ExtLink linking to a interwiki URL can be round-tripped losslessly (T94723) +!! options +parsoid=wt2wt +!! wikitext +[http://en.wikipedia.org/wiki/European_Robin European Robin] +!! html/parsoid +THIS SECTION IS NOT USED (but Parsoid won't run the test without it) +!! end + + ### ### Quotes ### @@ -5280,7 +5700,9 @@ Plain ''italic'''s plain </p><p><b>Bold tag left open</b> </p><p><i>Italic tag left open</i> </p><p>Normal text. -</p><p><b>This year'</b>s election <i>should</i> beat <b>last year'</b>s. +</p> +<!-- Unmatching number of opening, closing tags: --> +<p><b>This year'</b>s election <i>should</i> beat <b>last year'</b>s. </p><p><i>Tom<b>s car is bigger than </b></i><b>Susan</b>s. </p><p>Plain <i>italic'</i>s plain </p> @@ -5480,6 +5902,7 @@ Simple table but with multiple dashes for row wikitext </td></tr></table> !! end + !! test Multiplication table !! wikitext @@ -5601,6 +6024,69 @@ Accept "||" in indented table headings !! end !! test +Accept "!!" in templates +!! wikitext +{| +!a {{echo|b!!c}} +|} +!! html/php +<table> +<tr> +<th>a b</th> +<th>c +</th></tr></table> + +!! html/parsoid +<table> +<tbody><tr><th typeof="mw:Transclusion" about="#mwt1" data-parsoid='{"autoInsertedEnd":true,"pi":[[{"k":"1","spc":["","","",""]}]]}' data-mw='{"parts":["!a ",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"b!!c"}},"i":0}}]}'>a b</th><th about="#mwt1">c</th></tr> +!! end + +!! test +Accept "!!" in table headings after newline +!! wikitext +{| +!a +b!!c +|} +!! html/php +<table> +<tr> +<th>a +<p>b!!c +</p> +</th></tr></table> + +!! html/parsoid +<table> +<tbody><tr><th>a +<p>b!!c</p></th></tr> +</tbody></table> +!! end + +!! test +Accept "!!" in table data of mixed wikitext / html syntax +!! wikitext +{| +!a +<tr><td>b!!c</td></tr> +|} +!! html+tidy +<table> +<tr> +<th>a</th> +</tr> +<tr> +<td>b!!c</td> +</tr> +</table> +!! html/parsoid +<table> +<tbody><tr><th>a</th></tr> +<tr data-parsoid='{"stx":"html"}'><td data-parsoid='{"stx":"html"}'>b!!c</td></tr> +</tbody></table> +!! end + +!! test Accept empty attributes in td/th cells (td/th cells starting with leading ||) !! wikitext {| @@ -5746,10 +6232,37 @@ Invalid attributes in table cell (bug 1830) !! end -# The "|}" to close the table is missing from the input, so parsoid's -# *2wt modes will fail. !! test -Table security: embedded pipes (http://lists.wikimedia.org/mailman/htdig/wikitech-l/2006-April/022293.html) +Table cell attributes: Pipes protected by nowikis should be treated as a plain character +!! wikitext +{| +| title="foo" |bar +| title="foo<nowiki>|</nowiki>" |bar +| title="foo<nowiki>|</nowiki>" bar +|} +!! html/php +<table> +<tr> +<td title="foo">bar +</td> +<td title="foo|">bar +</td> +<td> title="foo|" bar +</td></tr></table> + +!! html/parsoid +<table> +<tbody><tr><td title="foo">bar</td> +<td title="foo|" data-parsoid='{"a":{"title":"foo|"},"sa":{"title":"foo<nowiki>|</nowiki>"},"autoInsertedEnd":true}'>bar</td> +<td> title="foo<span typeof="mw:Nowiki">|</span>" bar</td></tr> +</tbody></table> +!! end + +# See: http://lists.wikimedia.org/mailman/htdig/wikitech-l/2006-April/022293.html +# N.B. The "|}" to close the table is missing from the input, so parsoid's +# *2wt modes will fail. +!! test +Table security: embedded pipes !! options parsoid=wt2html,html2html !! wikitext @@ -5767,12 +6280,14 @@ parsoid=wt2html,html2html !! html/parsoid <table><tbody> <tr> -<td><a rel="mw:ExtLink" href="ftp://|x||"></a>" onmouseover="alert(document.cookie)">test</td></tr></tbody></table> +<td data-parsoid='{"startTagSrc":"| ","attrSepSrc":"|","autoInsertedEnd":true}'><a rel="mw:ExtLink" href="ftp://|x||"></a>" onmouseover="alert(document.cookie)">test</td></tr></tbody></table> !! end -# FIXME: The php output is broken. +# FIXME: The output seems broken. Filed as T110268. !! test ! and || in td attributes should not be parsed as <th>/<td> +!! options +parsoid=wt2html !! wikitext {| | style="color: red !important;" data-contrived="put this here ||" | foo @@ -5786,7 +6301,7 @@ parsoid=wt2html,html2html !! html/parsoid <table> -<tbody><tr><td style="color: red !important;" data-contrived="put this here ||" data-parsoid='{"autoInsertedEnd":true}'> foo</td></tr> +<tbody><tr><td> style="color: red !important;" data-contrived="put this here </td><td data-parsoid='{"stx_v":"row","a":{"\"":null},"sa":{"\"":""},"autoInsertedEnd":true}'> foo</td></tr> </tbody></table> !! end @@ -5893,6 +6408,50 @@ Indented table markup mixed with indented pre content (proposed in bug 6200) !! end !! test +4. Template-generated table cell attributes and cell content inside a templated table +!! wikitext +{{tbl-start}} +!align=center {{table_header_cells}} +|- +|align=center {{table_cells}} +{{tbl-end}} +!! html/php +<table> +<tr> +<th align="center" style="color:red;">Foo</th> +<th style="color:red;"><i>Bar</i></th> +<th style="color:brown;"><i>Foo</i> and Baz +</th></tr> +<tr> +<td align="center" style="color:red;">Foo</td> +<td style="color:red;"><i>Bar</i></td> +<td style="color:brown;"><i>Foo</i> and Baz +</td></tr></table> + +!! html/parsoid +<table about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[],[],[],[]]}' data-mw='{"parts":[{"template":{"target":{"wt":"tbl-start","href":"./Template:Tbl-start"},"params":{},"i":0}},"\n!align=center ",{"template":{"target":{"wt":"table_header_cells","href":"./Template:Table_header_cells"},"params":{},"i":1}},"\n|-\n|align=center ",{"template":{"target":{"wt":"table_cells","href":"./Template:Table_cells"},"params":{},"i":2}},"\n",{"template":{"target":{"wt":"tbl-end","href":"./Template:Tbl-end"},"params":{},"i":3}}]}'> +<tbody><tr><th align="center" style="color:red;">Foo</th><th style="color:red;"><i>Bar</i></th><th style="color:brown;"><i>Foo</i> and Baz</th></tr> +<tr> +<td align="center" style="color:red;">Foo</td><td style="color:red;"><i>Bar</i></td><td style="color:brown;"><i>Foo</i> and Baz</td></tr> +</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 {| @@ -6305,7 +6864,7 @@ parsoid=wt2html,wt2wt Parsoid: Default to a newline after tables in new content (bug 51219) !! options parsoid=html2wt -!! html +!! html/parsoid <table><tbody> <tr><td>foo</td></tr></tbody></table> bar <table><tbody> @@ -6325,7 +6884,7 @@ parsoid=html2wt Parsoid: newline inducing block nodes don't suppress <nowiki> !! options parsoid=html2wt -!! html +!! html/parsoid a<h1>foo</h1> !! wikitext <nowiki> </nowiki>a @@ -6359,9 +6918,6 @@ parsoid=wt2html,wt2wt </tbody></table> !! end - -# PHP throws away the (semi-broken) "foo" class here; Parsoid -# preserves it. !!test Parsoid: Recover better from broken table attributes !!options @@ -6372,7 +6928,7 @@ parsoid=wt2html foo |} !!html/php+tidy -<table> +<table class="foo"> <tr> <td class="bar"> <p>foo</p> @@ -6388,10 +6944,27 @@ foo !!end !! test +Tables: Digest broken attributes on table and tr tag +!! options +parsoid=wt2html +!! wikitext +{| || |} ++ +|- || || ++ -- +|- > [ +|} +!! html +<table> +<tbody> +<tr></tr> +<tr></tr> +</tbody></table> +!! end + +!! test Strip unsupported table tags !! options parsoid=html2wt -!! html +!! html/parsoid <table> <thead> <tr> @@ -6597,8 +7170,10 @@ Link with HTML entity in suffix / tail Link with 3 brackets !! wikitext [[[Main Page]]] +Foo [[[Main Page]]] !! html <p>[[[Main Page]]] +Foo [[[Main Page]]] </p> !! end @@ -6696,7 +7271,7 @@ Namespace takes precedence over interwiki link (bug 51680) Link to namespace preferred over interwiki with correct rel attribute !! options parsoid=html2wt,html2html -!! html +!! html/parsoid <p><a rel="mw:WikiLink" href="./MemoryAlpha:AlphaTest" title="MemoryAlpha:AlphaTest">MemoryAlpha:AlphaTest</a></p> !! wikitext [[MemoryAlpha:AlphaTest]] @@ -6810,7 +7385,7 @@ Link containing a tilde !! wikitext [[Foo~bar]] !! html/php -<p><a href="/wiki/Foo%7Ebar" title="Foo~bar">Foo~bar</a> +<p><a href="/wiki/Foo~bar" title="Foo~bar">Foo~bar</a> </p> !! html/parsoid <p><a rel="mw:WikiLink" href="./Foo~bar" title="Foo~bar">Foo~bar</a></p> @@ -6884,10 +7459,10 @@ Broken image links with HTML captions (bug 39700) <a href="/index.php?title=Special:Upload&wpDestFile=Nonexistent" class="new" title="File:Nonexistent">abc</a> </p> !! html/parsoid -<p><span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"<script></script>"}'><a href="./File:Nonexistent"><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220"/></a></span> -<span typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"<script></script>"}'><a href="./File:Nonexistent" data-parsoid='{"a":{"href":"./File:Nonexistent"},"sa":{}}'><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="100" width="100"/></a></span> -<span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"&lt;"}'><a href="./File:Nonexistent"><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220"/></a></span> -<span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"a<i>b</i>c"}'><a href="./File:Nonexistent"><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220"/></a></span></p> +<p><span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"&lt;script&gt;&lt;/script&gt;"}'><a href="./File:Nonexistent"><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220"/></a></span> +<span typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"&lt;script&gt;&lt;/script&gt;"}'><a href="./File:Nonexistent"><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="100" width="100"/></a></span> +<span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"<span typeof=\"mw:Entity\" data-parsoid=\"{&quot;src&quot;:&quot;&amp;lt;&quot;,&quot;srcContent&quot;:&quot;<&quot;,&quot;dsr&quot;:[107,111,null,null]}\">&lt;</span>"}'><a href="./File:Nonexistent"><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220"/></a></span> +<span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"a<i data-parsoid=\"{&quot;stx&quot;:&quot;html&quot;,&quot;dsr&quot;:[134,142,3,4]}\">b</i>c"}'><a href="./File:Nonexistent"><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220"/></a></span></p> !! end !! test @@ -7125,7 +7700,7 @@ title=[[User:test/123]] <p><a href="#a">b</a> </p> !! html/parsoid -<p><a rel="mw:WikiLink" href="../User:Test/123#a" data-parsoid='{"stx":"piped","a":{"href":"../User:Test/123#a"},"sa":{"href":"#a"}}'>b</a></p> +<p><a rel="mw:WikiLink" href="./User:Test/123#a" data-parsoid='{"stx":"piped","a":{"href":"./User:Test/123#a"},"sa":{"href":"#a"}}'>b</a></p> !! end !! test @@ -7204,7 +7779,7 @@ mótmælenda[[söfnuður|söfnuðir]]xxx Parsoid link trail escaping !! options parsoid=html2wt,html2html -!! html +!! html/parsoid <p><a rel="mw:WikiLink" href="Apple" title="Apple">apple</a>s</p> !! wikitext [[apple]]<nowiki/>s @@ -7215,7 +7790,7 @@ Parsoid link prefix escaping !! options language=is parsoid=html2wt,html2html -!! html +!! html/parsoid <p>Aðrir mótmælenda<a rel="mw:WikiLink" href="Söfnuður" title="Söfnuður">söfnuður</a></p> !! wikitext Aðrir mótmælenda<nowiki/>[[söfnuður]] @@ -7515,7 +8090,6 @@ Blah blah blah [[ es :Spanish]] [[ ZH :Chinese]] [[es:Foo_bar]] -[[es:Foo bar]] !! html/php <p>Blah blah blah </p> @@ -7524,7 +8098,21 @@ Blah blah blah <link rel="mw:PageProp/Language" href="http://es.wikipedia.org/wiki/Spanish" /> <link rel="mw:PageProp/Language" href="http://zh.wikipedia.org/wiki/Chinese" /> <link rel="mw:PageProp/Language" href="http://es.wikipedia.org/wiki/Foo_bar" /> -<link rel="mw:PageProp/Language" href="http://es.wikipedia.org/wiki/Foo_bar" /> +!! end + +!! test +Space and question mark encoding in interlanguage links (T95473) +!! options +parsoid=wt2html,wt2wt,html2html +!! wikitext +Blah blah blah +[[es:Foo bar?]] +!! html/php +<p>Blah blah blah +</p> +!! html/parsoid +<p>Blah blah blah</p> +<link rel="mw:PageProp/Language" href="http://es.wikipedia.org/wiki/Foo_bar%3F" /> !! end !! test @@ -7587,7 +8175,7 @@ language=ln Parsoid bug 53221: Wikilinks should be properly entity-escaped !! options parsoid=html2wt -!! html +!! html/parsoid <p>He&nbsp;llo <a href="Foo" rel="mw:WikiLink">He&nbsp;llo</a></p> <p>He&nbsp;llo <a href="He&nbsp;llo" rel="mw:WikiLink">He&nbsp;llo</a></p> !! wikitext @@ -7693,7 +8281,7 @@ Blah blah blah </p> !! html/parsoid <p>Blah blah blah -<a rel="mw:WikiLink" href="Template:Foo" title="Template:Foo">mi:Template:Foo</a></p> +<a rel="mw:WikiLink" href="./Template:Foo" title="Template:Foo">mi:Template:Foo</a></p> !! end ### @@ -7702,12 +8290,10 @@ Blah blah blah !! test 1. Simple redirect to page -!! options -parsoid !! wikitext #REDIRECT [[Main Page]] -!! html -<link rel="mw:PageProp/redirect" href="./Main_Page"> +!! html/parsoid +<link rel="mw:PageProp/redirect" href="./Main_Page"/> !! end !! test @@ -7718,12 +8304,22 @@ parsoid <link rel="mw:PageProp/redirect" href="./Main_Page" data-parsoid='{"src":"#REDIRECT ","a":{"href":"./Main_Page"},"sa":{"href":"Main_Page"}}'/> !! end +# Not a valid redirect in PHP (although perhaps it was, once upon a time) +# This tests the Parsoid bail-out code. !! test 3. Other redirect variants !! wikitext #REDIRECT [[<nowiki>[[Bar]]</nowiki>]] !! html/parsoid -<link rel="mw:PageProp/redirect" href="./%5B%5BBar%5D%5D" data-parsoid='{"src":"#REDIRECT ","a":{"href":"./%5B%5BBar%5D%5D"},"sa":{"href":"<nowiki>[[Bar]]</nowiki>"}}'/> +<ol><li data-parsoid>REDIRECT [[[[Bar]]]]</li></ol> +!! end + +!! test +4. Redirect to a templated destination +!! wikitext +#REDIRECT [[{{echo|Foo}}bar]] +!! html/parsoid +<link typeof="mw:ExpandedAttrs" rel="mw:PageProp/redirect" href="./Foobar" data-mw='{"attribs":[[{"txt":"href"},{"html":"<span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=\"{&quot;pi&quot;:[[{&quot;k&quot;:&quot;1&quot;,&quot;spc&quot;:[&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;]}]],&quot;dsr&quot;:[12,24,null,null]}\" data-mw=\"{&quot;parts&quot;:[{&quot;template&quot;:{&quot;target&quot;:{&quot;wt&quot;:&quot;echo&quot;,&quot;href&quot;:&quot;./Template:Echo&quot;},&quot;params&quot;:{&quot;1&quot;:{&quot;wt&quot;:&quot;Foo&quot;}},&quot;i&quot;:0}}]}\">Foo</span>bar"}]]}'/> !! end !! test @@ -7732,7 +8328,7 @@ Empty redirect parsoid=wt2html,wt2wt !! wikitext #REDIRECT [[]] -!! html +!! html/parsoid <ol> <li>REDIRECT [[]]</li></ol> !! end @@ -7745,8 +8341,8 @@ Optional colon in #REDIRECT parsoid=wt2html,html2html !! wikitext #REDIRECT:[[Main Page]] -!! html -<link rel="mw:PageProp/redirect" href="./Main_Page"> +!! html/parsoid +<link rel="mw:PageProp/redirect" href="./Main_Page"/> !! end !! test @@ -7761,8 +8357,8 @@ parsoid=wt2html,html2html #REDIRECT : [[Main Page]] -!! html -<link rel="mw:PageProp/redirect" href="./Main_Page"> +!! html/parsoid +<link rel="mw:PageProp/redirect" href="./Main_Page"/> !! end !! test @@ -7773,89 +8369,90 @@ Piped link in #REDIRECT parsoid=wt2html !! wikitext #REDIRECT [[Main Page|bar]] -!! html -<link rel="mw:PageProp/redirect" href="./Main_Page"> +!! html/parsoid +<link rel="mw:PageProp/redirect" href="./Main_Page"/> !! end !! test -Redirect to category +Redirect to category (T104502) !! options -parsoid=wt2wt,wt2html +parsoid=wt2html,wt2wt !! wikitext #REDIRECT [[Category:Foo]] -!! html -<link rel="mw:PageProp/redirect" href="./Category:Foo"><link rel="mw:PageProp/Category" href="./Category:Foo"> +!! html/parsoid +<link rel="mw:PageProp/redirect" href="./Category:Foo"/> !! end !! test -Redirect to category with URL encoding +Redirect to category with URL encoding (T104502) !! options parsoid=wt2html !! wikitext #REDIRECT [[Category%3AFoo]] -!! html -<link rel="mw:PageProp/redirect" href="./Category:Foo"><link rel="mw:PageProp/Category" href="./Category:Foo"> +!! html/parsoid +<link rel="mw:PageProp/redirect" href="./Category:Foo"/> !! end !! test Redirect to category page -!! options -parsoid !! wikitext #REDIRECT [[:Category:Foo]] -!! html -<link rel="mw:PageProp/redirect" href="Category:Foo" title="Category:Foo"/> +!! html/parsoid +<link rel="mw:PageProp/redirect" href="./Category:Foo"/> !! end !! test Redirect to image page (1) -!! options -parsoid !! wikitext #REDIRECT [[File:Wiki.png]] -!! html -<link rel="mw:PageProp/redirect" href="./File:Wiki.png"> +!! html/parsoid +<link rel="mw:PageProp/redirect" href="./File:Wiki.png"/> !! end !! test Redirect to image page (2) -!! options -parsoid !! wikitext #REDIRECT [[Image:Wiki.png]] -!! html -<link rel="mw:PageProp/redirect" href="./File:Wiki.png"> +!! html/parsoid +<link rel="mw:PageProp/redirect" href="./File:Wiki.png" data-parsoid='{"src":"#REDIRECT ","a":{"href":"./File:Wiki.png"},"sa":{"href":"Image:Wiki.png"}}'/> !! end +# html2wt disabled because wts serializes as "#REDIRECT [[:en:File:Wiki.png]]" +# Next test confirms this. !! test -Redirect to language +Redirect to language (1) (T104918) !! options -parsoid +parsoid=wt2html,wt2wt,html2html !! wikitext #REDIRECT [[en:File:Wiki.png]] -!! html -<link rel="mw:PageProp/redirect" href="./File:Wiki.png"> +!! html/parsoid +<link rel="mw:PageProp/redirect" href="//en.wikipedia.org/wiki/File:Wiki.png"/> !! end !! test -Redirect to interwiki -!! options -parsoid +Redirect to language (2) (T104918) +!! wikitext +#REDIRECT [[:en:File:Wiki.png]] +!! html/parsoid +<link rel="mw:PageProp/redirect" href="//en.wikipedia.org/wiki/File:Wiki.png"/> +!! end + +!! test +Redirect to interwiki (T104918) !! wikitext #REDIRECT [[meatball:File:Wiki.png]] -!! html -<link rel="mw:PageProp/redirect" href="./File:Wiki.png"> +!! html/parsoid +<link rel="mw:PageProp/redirect" href="http://www.usemod.com/cgi-bin/mb.pl?File:Wiki.png"/> !! end !! test Non-English #REDIRECT !! options -parsoid language=is !! wikitext #TILVÍSUN [[Main Page]] -!! html -<link rel="mw:PageProp/redirect" href="./Main_Page"> +!! html/parsoid +<link rel="mw:PageProp/redirect" href="./Main_Page" data-parsoid='{"src":"#TILVÍSUN ","a":{"href":"./Main_Page"},"sa":{"href":"Main Page"}}'/> !! end !! test @@ -7874,8 +8471,8 @@ some text New redirect !! options parsoid=html2wt -!! html -<p>Foo<link rel="mw:PageProp/redirect" href="./Foo"></p> +!! html/parsoid +<p>Foo<link rel="mw:PageProp/redirect" href="./Foo"/></p> !! wikitext Foo #REDIRECT [[Foo]] @@ -7980,8 +8577,8 @@ Handling html with a br self-closing tag <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" /> @@ -8225,7 +8822,7 @@ parsoid !! wikitext *<references /> !! html/parsoid -<ul><li data-parsoid='{}'><ol class="references" typeof="mw:Extension/references" about="#mwt2" data-parsoid='{}' data-mw='{"name":"references","attrs":{}}'></ol></li></ul> +<ul><li data-parsoid='{}'><ol class="mw-references" typeof="mw:Extension/references" about="#mwt2" data-parsoid='{}' data-mw='{"name":"references","attrs":{}}'></ol></li></ul> !! end !! test @@ -8258,11 +8855,15 @@ List items are not parsed correctly following a <pre> block (bug 785) * <pre>foo</pre> * <pre>bar</pre> * zar -!! html +!! html/php <ul><li> <pre>foo</pre></li> <li> <pre>bar</pre></li> <li> zar</li></ul> +!! html/parsoid +<ul><li> <pre data-parsoid='{"stx":"html"}'>foo</pre></li> +<li> <pre data-parsoid='{"stx":"html"}'>bar</pre></li> +<li> zar</li></ul> !! end !! test @@ -9306,7 +9907,7 @@ hi+world%3F%21 Magic Word: prioritize type info over data-parsoid !! options parsoid=html2wt -!! html +!! html/parsoid <meta property="mw:PageProp/forcetoc" data-parsoid='{"magicSrc":"__NOTOC__"}'/> !! wikitext __FORCETOC__ @@ -9320,7 +9921,7 @@ parsoid=wt2wt,html2wt foo __NOTOC__ bar -!! html +!! html/parsoid foo<meta property="mw:PageProp/notoc"/>bar !! end @@ -9331,10 +9932,19 @@ parsoid=wt2wt language=de !! wikitext __NOEDITSECTION__ -!! html +!! html/parsoid <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 ### @@ -9342,27 +9952,33 @@ __NOEDITSECTION__ Magic links: internal link to RFC (bug 479) !! wikitext [[RFC 123]] -!! html +!! html/php <p><a href="/index.php?title=RFC_123&action=edit&redlink=1" class="new" title="RFC 123 (page does not exist)">RFC 123</a> </p> +!! html/parsoid +<p><a rel="mw:WikiLink" href="./RFC_123" title="RFC 123">RFC 123</a></p> !! end !! test Magic links: RFC (bug 479) !! wikitext RFC 822 -!! html +!! html/php <p><a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc822">RFC 822</a> </p> +!! html/parsoid +<p><a href="//tools.ietf.org/html/rfc822" rel="mw:ExtLink">RFC 822</a></p> !! end !! test Magic links: RFC (bug 65278) !! wikitext This is RFC 822 but thisRFC 822 is not RFC 822linked. -!! html +!! html/php <p>This is <a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc822">RFC 822</a> but thisRFC 822 is not RFC 822linked. </p> +!! html/parsoid +<p>This is <a href="//tools.ietf.org/html/rfc822" rel="mw:ExtLink">RFC 822</a> but thisRFC 822 is not RFC 822linked.</p> !! end !! test @@ -9371,20 +9987,26 @@ Magic links: RFC (w/ non-newline whitespace, bug 28950/29025) RFC      822 RFC 822 -!! html +!! html/php <p><a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc822">RFC 822</a> RFC 822 </p> +!! html/parsoid +<p><a href="//tools.ietf.org/html/rfc822" rel="mw:ExtLink">RFC <span typeof="mw:Entity" data-parsoid='{"src":"&nbsp;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&#160;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&#0160;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&#xA0;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&#Xa0;","srcContent":" "}'> </span> 822</a> +RFC +822</p> !! end !! test Magic links: ISBN (bug 1937) !! wikitext ISBN 0-306-40615-2 -!! html +!! html/php <p><a href="/wiki/Special:BookSources/0306406152" class="internal mw-magiclink-isbn">ISBN 0-306-40615-2</a> </p> +!! html/parsoid +<p><a href="./Special:BookSources/0306406152" rel="mw:WikiLink">ISBN 0-306-40615-2</a></p> !! end !! test @@ -9395,7 +10017,7 @@ This is ISBN 978-0-316-09811-3 but thisISBN 978-0-316-09811-3 is not ISBN 978-0- <p>This is <a href="/wiki/Special:BookSources/9780316098113" class="internal mw-magiclink-isbn">ISBN 978-0-316-09811-3</a> but thisISBN 978-0-316-09811-3 is not ISBN 978-0-316-09811-3linked. </p> !! html/parsoid -<p>This is <a href="./Special:BookSources/9780316098113" rel="mw:ExtLink">ISBN 978-0-316-09811-3</a> but thisISBN 978-0-316-09811-3 is not ISBN 978-0-316-09811-3linked.</p> +<p>This is <a href="./Special:BookSources/9780316098113" rel="mw:WikiLink">ISBN 978-0-316-09811-3</a> but thisISBN 978-0-316-09811-3 is not ISBN 978-0-316-09811-3linked.</p> !! end !! test @@ -9406,31 +10028,41 @@ ISBN 9780316098113 ISBN 978 0316098113 -!! html +!! html/php <p><a href="/wiki/Special:BookSources/9780316098113" class="internal mw-magiclink-isbn">ISBN 978 0 316 09811 3</a> ISBN 9780316098113 ISBN 978 0316098113 </p> +!! html/parsoid +<p><a href="./Special:BookSources/9780316098113" rel="mw:WikiLink">ISBN <span typeof="mw:Entity" data-parsoid='{"src":"&nbsp;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&#160;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&#0160;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&#xA0;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&#Xa0;","srcContent":" "}'> </span> 978<span typeof="mw:Entity" data-parsoid='{"src":"&nbsp;","srcContent":" "}'> </span>0<span typeof="mw:Entity" data-parsoid='{"src":"&#160;","srcContent":" "}'> </span>316<span typeof="mw:Entity" data-parsoid='{"src":"&#0160;","srcContent":" "}'> </span>09811<span typeof="mw:Entity" data-parsoid='{"src":"&#xA0;","srcContent":" "}'> </span>3</a> +ISBN +9780316098113 +ISBN 978 +0316098113</p> !! end !! test Magic links: PMID incorrectly converts space to underscore !! wikitext PMID 1234 -!! html +!! html/php <p><a class="external mw-magiclink-pmid" rel="nofollow" href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract">PMID 1234</a> </p> +!! html/parsoid +<p><a href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract" rel="mw:ExtLink">PMID 1234</a></p> !! end !! test Magic links: PMID (bug 65278) !! wikitext This is PMID 1234 but thisPMID 1234 is not PMID 1234linked. -!! html +!! html/php <p>This is <a class="external mw-magiclink-pmid" rel="nofollow" href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract">PMID 1234</a> but thisPMID 1234 is not PMID 1234linked. </p> +!! html/parsoid +<p>This is <a href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract" rel="mw:ExtLink">PMID 1234</a> but thisPMID 1234 is not PMID 1234linked.</p> !! end !! test @@ -9439,11 +10071,15 @@ Magic links: PMID (w/ non-newline whitespace, bug 28950/29025) PMID      1234 PMID 1234 -!! html +!! html/php <p><a class="external mw-magiclink-pmid" rel="nofollow" href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract">PMID 1234</a> PMID 1234 </p> +!! html/parsoid +<p><a href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract" rel="mw:ExtLink">PMID <span typeof="mw:Entity" data-parsoid='{"src":"&nbsp;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&#160;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&#0160;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&#xA0;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&#Xa0;","srcContent":" "}'> </span> 1234</a> +PMID +1234</p> !! end ### @@ -9642,9 +10278,11 @@ Template with default value (value set) Template redirect !! wikitext {{templateredirect}} -!! html +!! html/php <p>(test) </p> +!! html/parsoid +<link rel="mw:PageProp/redirect" href="./Template:Templatesimple" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"templateredirect","href":"./Template:Templateredirect"},"params":{},"i":0}}]}'/> !! end !! test @@ -9883,6 +10521,24 @@ Template with targets containing wikilinks !! end !! article +Template:'' +!! text +bar +!! endarticle + +!! test +Templates: Double quotes as template target +!! wikitext +foo {{''}} baz +!! html/php +<p>foo bar baz +</p> +!! html/parsoid +<p>foo <span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"''"},"params":{},"i":0}}]}'>bar</span> baz +</p> +!! end + +!! article Template:MSGNW test !! text ''None'' of '''this''' should be @@ -9892,6 +10548,7 @@ Template:MSGNW test <gallery> File:Foobar.jpg </gallery> +<!-- comment --> !! endarticle # hmm, fix this or just deprecate msgnw and document its behavior? @@ -9899,7 +10556,7 @@ File:Foobar.jpg msgnw keyword !! wikitext {{msgnw:MSGNW test}} -!! html +!! html/php <p>''None'' of '''this''' should be * interpreted  but rather passed unmodified @@ -9907,6 +10564,7 @@ msgnw keyword <gallery> File:Foobar.jpg </gallery> +<!-- comment --> </p> !! end @@ -9919,6 +10577,15 @@ int keyword </p> !! end +!! test +int keyword - non-existing message +!! wikitext +{{int:var}} +!! html +<p><var> +</p> +!! end + !! article Template:Includes !! text @@ -10160,7 +10827,7 @@ b}} !! end !! test -Parsoid: Merge double tds (bug 50603) +Parsoid: Merge double tds (T52603) !! options parsoid !! wikitext @@ -10174,7 +10841,7 @@ parsoid !! end !! test -Parsoid: Merge double tds in nested transclusion content (bug 50603) +Parsoid: Merge double tds in nested transclusion content (T52603) !! options parsoid !! wikitext @@ -10669,6 +11336,43 @@ Templates: Support for templates generating attributes and content </tbody></table> !! end +!! test +3. Entities and nowikis inside templated attributes should be handled correctly inside templated tables +!! wikitext +{{tbl-start}} +|{{table_attribs_3}} +{{tbl-end}} +!! html/php +<table> +<tr> +<td style="background:#f9f9f9;">Foo +</td></tr></table> + +!! html/parsoid +<table about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[],[],[]]}' data-mw='{"parts":[{"template":{"target":{"wt":"tbl-start","href":"./Template:Tbl-start"},"params":{},"i":0}},"\n|",{"template":{"target":{"wt":"table_attribs_3","href":"./Template:Table_attribs_3"},"params":{},"i":1}},"\n",{"template":{"target":{"wt":"tbl-end","href":"./Template:Tbl-end"},"params":{},"i":2}}]}'> +<tbody><tr><td style="background:#f9f9f9;">Foo</td></tr> +</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 @@ -10896,6 +11600,46 @@ Templates: Wiki Tables: 6. Templated tags, templated td-tags !!end +## This test case is very specific to Parsoid's internals +## and is hence only tested for Parsoid's code. Parsoid uses +## a <meta> marker tag for <ref> tags and they are expanded +## much later. We are verifying that this <meta> tag usage +## doesn't prevent foster parenting. +!!test +Templates: Wiki Tables: 7. Fosterable <ref>s should get fostered +!!wikitext +{{PartialTable}}<ref>foo</ref> +|} + +<references /> +!!html/parsoid +<span about="#mwt2" class="mw-ref" id="cite_ref-1" rel="dc:references" typeof="mw:Transclusion mw:Extension/ref" data-mw='{"parts":[{"template":{"target":{"wt":"PartialTable","href":"./Template:PartialTable"},"params":{},"i":0}},"<ref>foo</ref>\n|}"]}'><a href="#cite_note-1"><span class="mw-reflink-text">[1]</span></a></span><table about="#mwt2"> +<tbody> +</tbody></table> + +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt4" 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">foo</span></li></ol> +!!end + +!! test +Templates: Wiki Tables: 8. Fosterable meta-tags should get fostered +!! wikitext +{{echo| +{{{!}} +{{!}}-}} +<onlyinclude> +|foo +</onlyinclude> +{{!}}} +!! html/parsoid +<span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"\n{{{!}}\n{{!}}-"}},"i":0}},"\n<onlyinclude>\n|foo\n</onlyinclude>\n{{!}}}"]}'> +</span><meta typeof="mw:Includes/OnlyInclude" about="#mwt1"/><table about="#mwt1"> +<tbody><tr> + +<td>foo +<meta typeof="mw:Includes/OnlyInclude/End"/></td></tr> +</tbody></table> +!! end + !!test Templates: Lists: Multi-line list-items via templates !! wikitext @@ -11093,41 +11837,40 @@ Parser Functions: 2. Nested use (only outermost should be marked up) !! 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 @@ -11138,7 +11881,7 @@ pre-save transform: subst: templates with parameters pst !! wikitext {{subst:paramtest|param="something else"}} -!! html +!! html/php This is a test template with parameter "something else" !! end @@ -11154,11 +11897,10 @@ pre-save transform: nowiki in subst (bug 1188) pst !! wikitext {{subst:nowikitest}} -!! html +!! html/php <nowiki>'''not wiki'''</nowiki> !! end - !! article Template:commenttest !! text @@ -11171,7 +11913,7 @@ pre-save transform: comment in subst (bug 1936) pst !! wikitext {{subst:commenttest}} -!! html +!! html/php This template has <!-- a comment --> in it. !! end @@ -11181,7 +11923,7 @@ pre-save transform: unclosed tag pst noxml !! wikitext <nowiki>'''not wiki''' -!! html +!! html/php <nowiki>'''not wiki''' !! end @@ -11191,7 +11933,7 @@ pre-save transform: mixed tag case pst noxml !! wikitext <NOwiki>'''not wiki'''</noWIKI> -!! html +!! html/php <NOwiki>'''not wiki'''</noWIKI> !! end @@ -11201,7 +11943,7 @@ pre-save transform: unclosed comment in <nowiki> pst noxml !! wikitext wiki<nowiki>nowiki<!--nowiki</nowiki>wiki -!! html +!! html/php wiki<nowiki>nowiki<!--nowiki</nowiki>wiki !!end @@ -11229,7 +11971,7 @@ pre-save transform: comment containing gallery (bug 5024) pst !! wikitext <!-- <gallery>data</gallery> --> -!! html +!! html/php <!-- <gallery>data</gallery> --> !!end @@ -11239,7 +11981,7 @@ pre-save transform: comment containing extension pst !! wikitext <!-- <tag>data</tag> --> -!! html +!! html/php <!-- <tag>data</tag> --> !!end @@ -11249,7 +11991,7 @@ pre-save transform: comment containing nowiki pst !! wikitext <!-- <nowiki>data</nowiki> --> -!! html +!! html/php <!-- <nowiki>data</nowiki> --> !!end @@ -11259,7 +12001,7 @@ pre-save transform: <noinclude> in subst (bug 3298) pst !! wikitext {{subst:Includes}} -!! html +!! html/php Foobar !! end @@ -11269,7 +12011,7 @@ pre-save transform: <onlyinclude> in subst (bug 3298) pst !! wikitext {{subst:Includes2}} -!! html +!! html/php Foo !! end @@ -11291,7 +12033,7 @@ bug 22297: safesubst: works during PST pst !! wikitext {{subst:SafeSubstTest}}{{safesubst:SubstTest}} -!! html +!! html/php FoobarFoobar !! end @@ -11327,7 +12069,7 @@ pst [[|Article (context)]] [[Bar:X (Y) Z|]] [[:Bar:X (Y) Z|]] -!! html +!! html/php [[Article (context)|Article]] [[Bar:Article|Article]] [[:Bar:Article|Article]] @@ -11348,7 +12090,7 @@ pst [[:interwiki:Article|]] [[interwiki:Bar:Article|]] [[:interwiki:Bar:Article|]] -!! html +!! html/php [[interwiki:Article|Article]] [[:interwiki:Article|Article]] [[interwiki:Bar:Article|Bar:Article]] @@ -11361,7 +12103,7 @@ pre-save transform: context links ("pipe trick") with parens in title pst title=[[Somearticle (context)]] !! wikitext [[|Article]] -!! html +!! html/php [[Article (context)|Article]] !! end @@ -11373,7 +12115,7 @@ pst title=[[Someplace, Somewhere]] [[|Otherplace]] [[Otherplace, Elsewhere|]] [[Otherplace, Elsewhere, Anywhere|]] -!! html +!! html/php [[Otherplace, Somewhere|Otherplace]] [[Otherplace, Elsewhere|Otherplace]] [[Otherplace, Elsewhere, Anywhere|Otherplace]] @@ -11386,7 +12128,7 @@ pst title=[[Someplace (IGNORED), Somewhere]] !! wikitext [[|Otherplace]] [[Otherplace (place), Elsewhere|]] -!! html +!! html/php [[Otherplace, Somewhere|Otherplace]] [[Otherplace (place), Elsewhere|Otherplace]] !! end @@ -11398,7 +12140,7 @@ pst title=[[Who, me? (context)]] !! 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 @@ -11409,7 +12151,7 @@ pre-save transform: context links ("pipe trick") with namespace pst title=[[Ns:Somearticle]] !! wikitext [[|Article]] -!! html +!! html/php [[Ns:Article|Article]] !! end @@ -11419,7 +12161,7 @@ pre-save transform: context links ("pipe trick") with namespace and parens pst title=[[Ns:Somearticle (context)]] !! wikitext [[|Article]] -!! html +!! html/php [[Ns:Article (context)|Article]] !! end @@ -11429,7 +12171,7 @@ pre-save transform: context links ("pipe trick") with namespace and comma pst title=[[Ns:Somearticle, Context, Whatever]] !! wikitext [[|Article]] -!! html +!! html/php [[Ns:Article, Context, Whatever|Article]] !! end @@ -11439,7 +12181,7 @@ pre-save transform: context links ("pipe trick") with namespace, comma and paren pst title=[[Ns:Somearticle, Context (context)]] !! wikitext [[|Article]] -!! html +!! html/php [[Ns:Article (context)|Article]] !! end @@ -11449,7 +12191,7 @@ pre-save transform: context links ("pipe trick") with namespace, parens and comm pst title=[[Ns:Somearticle (IGNORED), Context]] !! wikitext [[|Article]] -!! html +!! html/php [[Ns:Article, Context|Article]] !! end @@ -11464,7 +12206,7 @@ pst [[|Article(context)]] [[Bar:X(Y)Z|]] [[:Bar:X(Y)Z|]] -!! html +!! html/php [[Article(context)|Article]] [[Bar:Article(context)|Article]] [[:Bar:Article(context)|Article]] @@ -11484,7 +12226,7 @@ pst [[|Article (context)]] [[Bar:X (Y) Z|]] [[:Bar:X (Y) Z|]] -!! html +!! html/php [[Article (context)|Article]] [[Bar:Article (context)|Article]] [[:Bar:Article (context)|Article]] @@ -11504,7 +12246,7 @@ pst [[|Article(context)]] [[Bar:X(Y)Z|]] [[:Bar:X(Y)Z|]] -!! html +!! html/php [[Article(context)|Article]] [[Bar:Article(context)|Article]] [[:Bar:Article(context)|Article]] @@ -11524,7 +12266,7 @@ pst [[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]] @@ -11543,7 +12285,7 @@ Empty lines are trimmed -!! html +!! html/php Empty lines are trimmed !! end @@ -11556,7 +12298,7 @@ pst * <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> @@ -11587,7 +12329,7 @@ As well as inside noinclude/onlyinclude But not inside includeonly <includeonly>{{subst:Foo}}</includeonly> -!! html +!! html/php Shall not expand: <nowiki>~~~~</nowiki> @@ -11642,7 +12384,7 @@ parsoid=wt2html Parsoid: Escape nowiki with trailing space in tags !! options parsoid=html2wt -!! html +!! html/parsoid <p><nowiki > foo </nowiki ></p> <p>a<nowiki />b</p> <p>c<nowiki/ >d</p> @@ -11658,7 +12400,7 @@ c<nowiki/ >d Parsoid: Escape weird noWikI capitalizations !! options parsoid=html2wt -!! html +!! html/parsoid <p><noWikI > foo </NoWikI ></p> !! wikitext <noWikI > foo </NoWikI > @@ -11921,7 +12663,7 @@ parsoid=wt2html,wt2wt,html2html <div class="thumb tright"><div class="thumbinner" style="width:139px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/137px-Foobar.jpg" width="137" height="16" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/206px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/274px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>This is a caption</div></div></div> !! html/parsoid -<figure typeof="mw:Image/Thumb mw:ExpandedAttrs" about="#mwt2" data-mw='{"attribs":[["thumbnail",{"html":"thumb"}],["width",{"html":"<span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=\"{&quot;pi&quot;:[[{&quot;k&quot;:&quot;1&quot;,&quot;spc&quot;:[&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;]}]],&quot;dsr&quot;:[24,38,null,null]}\" data-mw=\"{&quot;parts&quot;:[{&quot;template&quot;:{&quot;target&quot;:{&quot;wt&quot;:&quot;echo&quot;,&quot;href&quot;:&quot;./Template:Echo&quot;},&quot;params&quot;:{&quot;1&quot;:{&quot;wt&quot;:&quot;137px&quot;}},&quot;i&quot;:0}}]}\">137px</span>"}]]}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="16" width="137"/></a><figcaption>This is a caption</figcaption></figure> +<figure typeof="mw:Image/Thumb mw:ExpandedAttrs" about="#mwt2" data-mw='{"attribs":[["thumbnail",{"html":"thumb"}],["width",{"html":"<span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=\"{&quot;pi&quot;:[[{&quot;k&quot;:&quot;1&quot;,&quot;spc&quot;:[&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;]}]],&quot;dsr&quot;:[24,38,null,null]}\" data-mw=\"{&quot;parts&quot;:[{&quot;template&quot;:{&quot;target&quot;:{&quot;wt&quot;:&quot;echo&quot;,&quot;href&quot;:&quot;./Template:Echo&quot;},&quot;params&quot;:{&quot;1&quot;:{&quot;wt&quot;:&quot;137px&quot;}},&quot;i&quot;:0}}]}\">137px</span>"}]]}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/137px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="16" width="137"/></a><figcaption>This is a caption</figcaption></figure> !! end !! test @@ -11932,7 +12674,7 @@ parsoid=wt2html,wt2wt,html2html <div class="thumb tright"><div class="thumbinner" style="width:139px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/137px-Foobar.jpg" width="137" height="16" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/206px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/274px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>This is a caption</div></div></div> !! html/parsoid -<figure typeof="mw:Image/Thumb mw:ExpandedAttrs" about="#mwt3" data-mw='{"attribs":[["thumbnail",{"html":"<span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=\"{&quot;pi&quot;:[[{&quot;k&quot;:&quot;1&quot;,&quot;spc&quot;:[&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;]}]],&quot;dsr&quot;:[18,32,null,null]}\" data-mw=\"{&quot;parts&quot;:[{&quot;template&quot;:{&quot;target&quot;:{&quot;wt&quot;:&quot;echo&quot;,&quot;href&quot;:&quot;./Template:Echo&quot;},&quot;params&quot;:{&quot;1&quot;:{&quot;wt&quot;:&quot;thumb&quot;}},&quot;i&quot;:0}}]}\">thumb</span>"}],["width",{"html":"<span about=\"#mwt2\" typeof=\"mw:Transclusion\" data-parsoid=\"{&quot;pi&quot;:[[{&quot;k&quot;:&quot;1&quot;,&quot;spc&quot;:[&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;]}]],&quot;dsr&quot;:[33,47,null,null]}\" data-mw=\"{&quot;parts&quot;:[{&quot;template&quot;:{&quot;target&quot;:{&quot;wt&quot;:&quot;echo&quot;,&quot;href&quot;:&quot;./Template:Echo&quot;},&quot;params&quot;:{&quot;1&quot;:{&quot;wt&quot;:&quot;137px&quot;}},&quot;i&quot;:0}}]}\">137px</span>"}]]}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="16" width="137"/></a><figcaption>This is a caption</figcaption></figure> +<figure typeof="mw:Image/Thumb mw:ExpandedAttrs" about="#mwt3" data-mw='{"attribs":[["thumbnail",{"html":"<span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=\"{&quot;pi&quot;:[[{&quot;k&quot;:&quot;1&quot;,&quot;spc&quot;:[&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;]}]],&quot;dsr&quot;:[18,32,null,null]}\" data-mw=\"{&quot;parts&quot;:[{&quot;template&quot;:{&quot;target&quot;:{&quot;wt&quot;:&quot;echo&quot;,&quot;href&quot;:&quot;./Template:Echo&quot;},&quot;params&quot;:{&quot;1&quot;:{&quot;wt&quot;:&quot;thumb&quot;}},&quot;i&quot;:0}}]}\">thumb</span>"}],["width",{"html":"<span about=\"#mwt2\" typeof=\"mw:Transclusion\" data-parsoid=\"{&quot;pi&quot;:[[{&quot;k&quot;:&quot;1&quot;,&quot;spc&quot;:[&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;]}]],&quot;dsr&quot;:[33,47,null,null]}\" data-mw=\"{&quot;parts&quot;:[{&quot;template&quot;:{&quot;target&quot;:{&quot;wt&quot;:&quot;echo&quot;,&quot;href&quot;:&quot;./Template:Echo&quot;},&quot;params&quot;:{&quot;1&quot;:{&quot;wt&quot;:&quot;137px&quot;}},&quot;i&quot;:0}}]}\">137px</span>"}]]}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/137px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="16" width="137"/></a><figcaption>This is a caption</figcaption></figure> !! end !! test @@ -11989,7 +12731,7 @@ thumbsize=220 !! html/parsoid <p>123<span class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span>456</p> <p>123</p><figure class="mw-default-size mw-halign-right" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure><p>456</p> -<p>123</p><figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a></figure><p>456</p> +<p>123</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></figure><p>456</p> !! end !! test @@ -12011,7 +12753,7 @@ Image with multiple widths -- use last <p><a href="/wiki/File:Foobar.jpg" class="image" title="caption"><img alt="caption" src="http://example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg" width="300" height="34" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/450px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/600px-Foobar.jpg 2x" /></a> </p> !! html/parsoid -<p><span typeof="mw:Image" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="34" width="300"/></a></span></p> +<p><span typeof="mw:Image" data-mw='{"caption":"caption"}'><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></span></p> !! end !! test @@ -12027,7 +12769,7 @@ thumbsize=220 <p><a href="/wiki/File:Foobar.jpg" class="image" title="caption"><img alt="caption" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" style="vertical-align: middle" /></a> </p> !! html/parsoid -<figure class="mw-default-size mw-halign-left" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>caption</figcaption></figure> +<figure class="mw-default-size mw-halign-left" 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>caption</figcaption></figure> <p><span class="mw-default-size mw-valign-middle" typeof="mw:Image" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> !! end @@ -12043,9 +12785,9 @@ Image with width attribute at different positions <div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image" title="Caption"><img alt="Caption" src="http://example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" width="200" height="23" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/400px-Foobar.jpg 2x" /></a></div> !! html/parsoid -<figure class="mw-halign-right" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="23" width="200"/></a><figcaption>Caption</figcaption></figure> -<figure class="mw-halign-right" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="23" width="200"/></a><figcaption>Caption</figcaption></figure> -<figure class="mw-halign-right" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="23" width="200"/></a><figcaption>Caption</figcaption></figure> +<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/200px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="23" width="200"/></a><figcaption>Caption</figcaption></figure> +<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/200px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="23" width="200"/></a><figcaption>Caption</figcaption></figure> +<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/200px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="23" width="200"/></a><figcaption>Caption</figcaption></figure> !! end # a sad bit of backward-compatibility @@ -12061,7 +12803,7 @@ parsoid=wt2html,wt2wt,html2html <a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/177px-Foobar.jpg" width="177" height="20" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/265px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/353px-Foobar.jpg 2x" /></a> </p> !! html/parsoid -<p><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="2" width="20"/></a></span> <span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="20" width="177"/></a></span></p> +<p><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/20px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="2" width="20"/></a></span> <span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/177px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="20" width="177"/></a></span></p> !! end !! test @@ -12100,6 +12842,21 @@ Image with link parameter, protocol-less URL target !! end !! test +Escaping non-block captions (T107435) +!! options +parsoid={ + "modes": ["wt2wt"], + "changes": [ + ["[typeof~='mw:Image']", "attr", "data-mw", "{\"caption\": \"|\"}"] + ] +} +!! wikitext +[[Image:Foobar.jpg|caption]] +!! wikitext/edited +[[Image:Foobar.jpg|<nowiki>|</nowiki>]] +!! end + +!! test Image with link parameter, wgExternalLinkTarget !! wikitext [[Image:foobar.jpg|link=http://example.com/]] @@ -12187,7 +12944,7 @@ parsoid=wt2html,wt2wt,html2html <div class="thumb tright"><div class="thumbinner" style="width:222px;"><a href="http://example.com/"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" width="220" height="25" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/330px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/440px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>Title</div></div></div> !! html/parsoid -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="http://example.com/"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>Title</figcaption></figure> +<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="http://example.com/"><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>Title</figcaption></figure> !! end !! test @@ -12287,8 +13044,6 @@ parsoid=wt2html,wt2wt,html2html !! test Image with wiki markup in implicit alt -!! options -parsoid=wt2html,wt2wt,html2html !! wikitext [[Image:Foobar.jpg|testing '''bold''' in alt]] @@ -12298,8 +13053,8 @@ parsoid=wt2html,wt2wt,html2html </p><p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="testing bold in alt" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> </p> !! html/parsoid -<p><span class="mw-default-size" typeof="mw:Image" data-mw="{"caption":"testing '''bold''' in alt"}"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> -<p><span class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img alt="testing bold in alt" resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> +<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"testing <b data-parsoid=\"{&quot;dsr&quot;:[27,37,3,3]}\">bold</b> in alt"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"Image:Foobar.jpg"}}'/></a></span></p> +<p><span class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img alt="testing bold in alt" resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"alt":"testing bold in alt","resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"alt":"alt=testing '''bold''' in alt","resource":"Image:Foobar.jpg"}}'/></a></span></p> !! end !! test @@ -12334,9 +13089,9 @@ parsoid=wt2html,wt2wt,html2html </p><p><a href="/wiki/File:Foobar.jpg" class="image" title="caption"><img alt="caption" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> </p> !! html/parsoid -<p><span class="mw-default-size" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a></span></p> -<p><span class="mw-default-size" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a></span></p> -<p><span class="mw-default-size" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a></span></p> +<p><span class="mw-default-size" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}'><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></span></p> +<p><span class="mw-default-size" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}'><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></span></p> +<p><span class="mw-default-size" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}'><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></span></p> !! end !! test @@ -12372,15 +13127,15 @@ parsoid=wt2html,wt2wt,html2html <div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>caption</div></div></div> !! html/parsoid -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>caption</figcaption></figure> -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>caption</figcaption></figure> -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>caption</figcaption></figure> +<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>caption</figcaption></figure> +<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>caption</figcaption></figure> +<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>caption</figcaption></figure> !! end ################### # Image sizing. # See https://www.mediawiki.org/wiki/Help:Images#Size_and_frame -# and https://bugzilla.wikimedia.org/show_bug.cgi?id=62258 +# and https://phabricator.wikimedia.org/T64258 # Foobar has actual size of 1941x220 # 1. Thumbs & frameless always reduce, can't be enlarged unless it's # a scalable format. @@ -12401,8 +13156,8 @@ parsoid=wt2html,wt2wt,html2html </p><p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="2000" height="227" class="thumbborder" /></a> </p> !! html/parsoid -<p><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="227" width="2000"/></a></span></p> -<p><span class="mw-image-border" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="227" width="2000"/></a></span></p> +<p><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/1941px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="227" width="2000"/></a></span></p> +<p><span class="mw-image-border" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/1941px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="227" width="2000"/></a></span></p> !! end !! test @@ -12418,8 +13173,8 @@ parsoid=wt2html,wt2wt,html2html </p><p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/1000px-Foobar.jpg" width="1000" height="113" class="thumbborder" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/1500px-Foobar.jpg 1.5x, http://example.com/images/3/3a/Foobar.jpg 2x" /></a> </p> !! html/parsoid -<p><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="113" width="1000"/></a></span></p> -<p><span class="mw-image-border" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="113" width="1000"/></a></span></p> +<p><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/1000px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="113" width="1000"/></a></span></p> +<p><span class="mw-image-border" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/1000px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="113" width="1000"/></a></span></p> !! end !! test @@ -12432,7 +13187,7 @@ parsoid=wt2html,wt2wt,html2html <div class="thumb tright"><div class="thumbinner" style="width:52px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" width="50" height="6" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/75px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/100px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div></div></div></div> !! html/parsoid -<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></figure> +<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></figure> !! end !! test @@ -12448,8 +13203,8 @@ parsoid=wt2html,wt2wt,html2html <div class="thumb tright"><div class="thumbinner" style="width:2002px;"><a href="/wiki/File:Foobar.svg" class="image"><img alt="Foobar.svg" src="http://example.com/images/thumb/f/ff/Foobar.svg/2000px-Foobar.svg.png" width="2000" height="1500" class="thumbimage" srcset="http://example.com/images/thumb/f/ff/Foobar.svg/3000px-Foobar.svg.png 1.5x, http://example.com/images/thumb/f/ff/Foobar.svg/4000px-Foobar.svg.png 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.svg" class="internal" title="Enlarge"></a></div></div></div></div> !! html/parsoid -<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure> -<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.svg"><img resource="./File:Foobar.svg" src="//example.com/images/f/ff/Foobar.svg" data-file-width="240" data-file-height="180" data-file-type="drawing" height="1500" width="2000"/></a></figure> +<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/1941px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure> +<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.svg"><img resource="./File:Foobar.svg" src="//example.com/images/thumb/f/ff/Foobar.svg/240px-Foobar.svg" data-file-width="240" data-file-height="180" data-file-type="drawing" height="1500" width="2000"/></a></figure> !! end !! test @@ -12462,7 +13217,7 @@ parsoid=wt2html,wt2wt,html2html <p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" width="50" height="6" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/75px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/100px-Foobar.jpg 2x" /></a> </p> !! html/parsoid -<p><span typeof="mw:Image/Frameless"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></span></p> +<p><span typeof="mw:Image/Frameless"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></span></p> !! end !! test @@ -12478,8 +13233,8 @@ parsoid=wt2html,wt2wt,html2html </p><p><a href="/wiki/File:Foobar.svg" class="image"><img alt="Foobar.svg" src="http://example.com/images/thumb/f/ff/Foobar.svg/2000px-Foobar.svg.png" width="2000" height="1500" srcset="http://example.com/images/thumb/f/ff/Foobar.svg/3000px-Foobar.svg.png 1.5x, http://example.com/images/thumb/f/ff/Foobar.svg/4000px-Foobar.svg.png 2x" /></a> </p> !! html/parsoid -<p><span typeof="mw:Image/Frameless"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> -<p><span typeof="mw:Image/Frameless"><a href="./File:Foobar.svg"><img resource="./File:Foobar.svg" src="//example.com/images/f/ff/Foobar.svg" data-file-width="240" data-file-height="180" data-file-type="drawing" height="1500" width="2000"/></a></span></p> +<p><span typeof="mw:Image/Frameless"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/1941px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> +<p><span typeof="mw:Image/Frameless"><a href="./File:Foobar.svg"><img resource="./File:Foobar.svg" src="//example.com/images/thumb/f/ff/Foobar.svg/240px-Foobar.svg" data-file-width="240" data-file-height="180" data-file-type="drawing" height="1500" width="2000"/></a></span></p> !! end !! test @@ -12537,7 +13292,7 @@ Frameless image caption with a free URL <p><a href="/wiki/File:Foobar.jpg" class="image" title="http://example.com"><img alt="http://example.com" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> </p> !! html/parsoid -<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"http://example.com"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> +<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"<a rel=\"mw:ExtLink\" href=\"http://example.com\" data-parsoid=\"{&quot;stx&quot;:&quot;url&quot;,&quot;dsr&quot;:[18,36,0,0]}\">http://example.com</a>"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> !! end !! test @@ -12550,7 +13305,7 @@ thumbsize=220 <div class="thumb tright"><div class="thumbinner" style="width:222px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" width="220" height="25" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/330px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/440px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a></div></div></div> !! html/parsoid -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption><a rel="mw:ExtLink" href="http://example.com">http://example.com</a></figcaption></figure> +<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><a rel="mw:ExtLink" href="http://example.com">http://example.com</a></figcaption></figure> !! end !! test @@ -12564,7 +13319,7 @@ parsoid=wt2html,wt2wt,html2html <div class="thumb tright"><div class="thumbinner" style="width:222px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Alteration" src="http://example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" width="220" height="25" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/330px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/440px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a></div></div></div> !! html/parsoid -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img alt="Alteration" resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption><a rel="mw:ExtLink" href="http://example.com">http://example.com</a></figcaption></figure> +<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img alt="Alteration" 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><a rel="mw:ExtLink" href="http://example.com">http://example.com</a></figcaption></figure> !! end !! test @@ -12576,7 +13331,7 @@ SVG thumbnails with no language set <div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.svg" class="image"><img alt="" src="http://example.com/images/thumb/f/ff/Foobar.svg/180px-Foobar.svg.png" width="180" height="135" class="thumbimage" srcset="http://example.com/images/thumb/f/ff/Foobar.svg/270px-Foobar.svg.png 1.5x, http://example.com/images/thumb/f/ff/Foobar.svg/360px-Foobar.svg.png 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.svg" class="internal" title="Enlarge"></a></div>caption</div></div></div> !! html/parsoid -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.svg"><img resource="./File:Foobar.svg" src="//example.com/images/f/ff/Foobar.svg" data-file-width="240" data-file-height="180" data-file-type="drawing" height="165" width="220"/></a><figcaption>caption</figcaption></figure> +<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.svg"><img resource="./File:Foobar.svg" src="//example.com/images/thumb/f/ff/Foobar.svg/220px-Foobar.svg" data-file-width="240" data-file-height="180" data-file-type="drawing" height="165" width="220"/></a><figcaption>caption</figcaption></figure> !! end !! test @@ -12589,7 +13344,7 @@ parsoid=wt2html,wt2wt,html2html <div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/index.php?title=File:Foobar.svg&lang=de" class="image"><img alt="" src="http://example.com/images/thumb/f/ff/Foobar.svg/langde-180px-Foobar.svg.png" width="180" height="135" class="thumbimage" srcset="http://example.com/images/thumb/f/ff/Foobar.svg/langde-270px-Foobar.svg.png 1.5x, http://example.com/images/thumb/f/ff/Foobar.svg/langde-360px-Foobar.svg.png 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.svg" class="internal" title="Enlarge"></a></div>caption</div></div></div> !! html/parsoid -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.svg"><img resource="./File:Foobar.svg" src="//example.com/images/f/ff/Foobar.svg" lang="de" data-file-width="240" data-file-height="180" data-file-type="drawing" height="165" width="220"/></a><figcaption>caption</figcaption></figure> +<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.svg"><img resource="./File:Foobar.svg" src="//example.com/images/thumb/f/ff/Foobar.svg/220px-Foobar.svg" lang="de" data-file-width="240" data-file-height="180" data-file-type="drawing" height="165" width="220"/></a><figcaption>caption</figcaption></figure> !! end !! test @@ -12613,7 +13368,7 @@ BUG 1887: A ISBN with a thumbnail <div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div><a href="/wiki/Special:BookSources/1235467890" class="internal mw-magiclink-isbn">ISBN 1235467890</a></div></div></div> !! html/parsoid -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption><a href="Special:BookSources/1235467890" rel="mw:ExtLink">ISBN 1235467890</a></figcaption></figure> +<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><a href="./Special:BookSources/1235467890" rel="mw:WikiLink">ISBN 1235467890</a></figcaption></figure> !! end !! test @@ -12624,7 +13379,7 @@ BUG 1887: A RFC with a thumbnail <div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>This is <a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc12354">RFC 12354</a></div></div></div> !! html/parsoid -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>This is <a href="//tools.ietf.org/html/rfc12354" rel="mw:ExtLink">RFC 12354</a></figcaption></figure> +<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>This is <a href="//tools.ietf.org/html/rfc12354" rel="mw:ExtLink">RFC 12354</a></figcaption></figure> !! end !! test @@ -12635,7 +13390,7 @@ BUG 1887: A mailto link with a thumbnail <div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>Please <a rel="nofollow" class="external free" href="mailto:nobody@example.com">mailto:nobody@example.com</a></div></div></div> !! html/parsoid -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>Please <a rel="mw:ExtLink" href="mailto:nobody@example.com">mailto:nobody@example.com</a></figcaption></figure> +<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>Please <a rel="mw:ExtLink" href="mailto:nobody@example.com">mailto:nobody@example.com</a></figcaption></figure> !! end # Pending resolution to bug 368 @@ -12647,7 +13402,7 @@ BUG 648: Frameless image caption with a link <p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a link in it"><img alt="text with a link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> </p> !! html/parsoid -<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"text with a [[link]] in it"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> +<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"text with a <a rel=\"mw:WikiLink\" href=\"./Link\" title=\"Link\" data-parsoid=\"{&quot;stx&quot;:&quot;simple&quot;,&quot;a&quot;:{&quot;href&quot;:&quot;./Link&quot;},&quot;sa&quot;:{&quot;href&quot;:&quot;link&quot;},&quot;dsr&quot;:[30,38,2,2]}\">link</a> in it"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> !! end !! test @@ -12658,7 +13413,7 @@ BUG 648: Frameless image caption with a link (suffix) <p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a linkfoo in it"><img alt="text with a linkfoo in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> </p> !! html/parsoid -<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"text with a [[link]]foo in it"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> +<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"text with a <a rel=\"mw:WikiLink\" href=\"./Link\" title=\"Link\" data-parsoid=\"{&quot;stx&quot;:&quot;simple&quot;,&quot;a&quot;:{&quot;href&quot;:&quot;./Link&quot;},&quot;sa&quot;:{&quot;href&quot;:&quot;link&quot;},&quot;dsr&quot;:[30,41,2,5],&quot;tail&quot;:&quot;foo&quot;}\">linkfoo</a> in it"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> !! end !! test @@ -12669,7 +13424,7 @@ BUG 648: Frameless image caption with an interwiki link <p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a MeatBall:Link in it"><img alt="text with a MeatBall:Link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> </p> !! html/parsoid -<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"text with a [[MeatBall:Link]] in it"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> +<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"text with a <a rel=\"mw:ExtLink\" href=\"http://www.usemod.com/cgi-bin/mb.pl?Link\" title=\"meatball:Link\" data-parsoid=\"{&quot;stx&quot;:&quot;simple&quot;,&quot;a&quot;:{&quot;href&quot;:&quot;http://www.usemod.com/cgi-bin/mb.pl?Link&quot;},&quot;sa&quot;:{&quot;href&quot;:&quot;MeatBall:Link&quot;},&quot;isIW&quot;:true,&quot;dsr&quot;:[30,47,2,2]}\">MeatBall:Link</a> in it"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> !! end !! test @@ -12680,7 +13435,15 @@ BUG 648: Frameless image caption with a piped interwiki link <p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a link in it"><img alt="text with a link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> </p> !! html/parsoid -<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"text with a [[MeatBall:Link|link]] in it"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> +<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"text with a <a rel=\"mw:ExtLink\" href=\"http://www.usemod.com/cgi-bin/mb.pl?Link\" title=\"meatball:Link\" data-parsoid=\"{&quot;stx&quot;:&quot;piped&quot;,&quot;a&quot;:{&quot;href&quot;:&quot;http://www.usemod.com/cgi-bin/mb.pl?Link&quot;},&quot;sa&quot;:{&quot;href&quot;:&quot;MeatBall:Link&quot;},&quot;isIW&quot;:true,&quot;dsr&quot;:[30,52,16,2]}\">link</a> in it"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> +!! end + +!! test +T107474: Frameless image caption with <nowiki> +!! wikitext +[[File:Foobar.jpg|<nowiki>text with a [[MeatBall:Link|link]] in it</nowiki>]] +!! html/parsoid +<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"<span typeof=\"mw:Nowiki\" data-parsoid=\"{&quot;dsr&quot;:[18,75,8,9]}\">text with a [[MeatBall:Link|link]] in it</span>"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> !! end !! test @@ -12691,7 +13454,7 @@ Escape HTML special chars in image alt text <p><a href="/wiki/File:Foobar.jpg" class="image" title="& < > ""><img alt="& < > "" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> </p> !! html/parsoid -<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"& < > \""}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> +<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"&amp; &lt; &gt; \""}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> !! end !! test @@ -12702,7 +13465,7 @@ BUG 499: Alt text should have Ӓ, not &1234; <p><a href="/wiki/File:Foobar.jpg" class="image" title="♀"><img alt="♀" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> </p> !! html/parsoid -<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"&#9792;"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> +<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"<span typeof=\"mw:Entity\" data-parsoid=\"{&quot;src&quot;:&quot;&amp;#9792;&quot;,&quot;srcContent&quot;:&quot;♀&quot;,&quot;dsr&quot;:[18,25,null,null]}\">♀</span>"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> !! end !! test @@ -12726,7 +13489,7 @@ Image caption containing another image <div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>This is a caption with another <a href="/wiki/File:Thumb.png" class="image" title="image"><img alt="image" src="http://example.com/images/e/ea/Thumb.png" width="135" height="135" /></a> inside it!</div></div></div> !! html/parsoid -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>This is a caption with another <span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"image"}'><a href="./File:Thumb.png"><img resource="./File:Thumb.png" src="//example.com/images/e/ea/Thumb.png" data-file-width="135" data-file-height="135" data-file-type="bitmap" height="135" width="135"/></a></span> inside it!</figcaption></figure> +<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>This is a caption with another <span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"image"}'><a href="./File:Thumb.png"><img resource="./File:Thumb.png" src="//example.com/images/e/ea/Thumb.png" data-file-width="135" data-file-height="135" data-file-type="bitmap" height="135" width="135"/></a></span> inside it!</figcaption></figure> !! end !! test @@ -12750,7 +13513,7 @@ Image: caption containing leading space <div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>bar</div></div></div> !! html/parsoid -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption> bar</figcaption></figure> +<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> bar</figcaption></figure> !!end !! test @@ -12769,7 +13532,7 @@ and some more text.]] <div class="thumb tright"><div class="thumbinner" style="width:202px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" width="200" height="23" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/400px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>This is an example image thumbnail caption with a table <table> <tr> <th> Foo </th> <th> Bar </th></tr> <tr> <td> Foo1 </td> <td> Bar1 </td></tr></table> and some more text.</div></div></div> !! html/parsoid -<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="23" width="200"/></a><figcaption>This is an example image thumbnail caption with a table +<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="23" width="200"/></a><figcaption>This is an example image thumbnail caption with a table <table> <tbody> <tr><th>Foo </th><th>Bar</th></tr> @@ -12786,7 +13549,7 @@ Bug 3090: External links other than http: in image captions <div class="thumb tright"><div class="thumbinner" style="width:202px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" width="200" height="23" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/400px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>This caption has <a rel="nofollow" class="external text" href="irc://example.net">irc</a> and <a rel="nofollow" class="external text" href="https://example.com">Secure</a> ext links in it.</div></div></div> !! html/parsoid -<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="23" width="200"/></a><figcaption>This caption has <a rel="mw:ExtLink" href="irc://example.net">irc</a> and <a rel="mw:ExtLink" href="https://example.com">Secure</a> ext links in it.</figcaption></figure> +<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="23" width="200"/></a><figcaption>This caption has <a rel="mw:ExtLink" href="irc://example.net">irc</a> and <a rel="mw:ExtLink" href="https://example.com">Secure</a> ext links in it.</figcaption></figure> !! end !! test @@ -12828,7 +13591,7 @@ language=es <div class="thumb tleft"><div class="thumbinner" style="width:222px;"><a href="/wiki/Foo" title="Foo"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" width="220" height="25" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/330px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/440px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/Archivo:Foobar.jpg" class="internal" title="Aumentar"></a></div>caption</div></div></div> !! html/parsoid -<figure class="mw-default-size mw-halign-left" typeof="mw:Image/Thumb"><a href="./Foo"><img resource="./Archivo:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>caption</figcaption></figure> +<figure class="mw-default-size mw-halign-left" typeof="mw:Image/Thumb"><a href="Foo"><img resource="./Archivo: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>caption</figcaption></figure> !! end !! test @@ -12842,7 +13605,7 @@ parsoid=wt2html,wt2wt,html2html <p><a href="/wiki/File:Foobar.jpg" class="image" title="caption"><img alt="caption" src="http://example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" width="220" height="25" class="extra thumbborder" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/330px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/440px-Foobar.jpg 2x" /></a> </p> !! html/parsoid -<p><span class="mw-default-size mw-image-border extra" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a></span></p> +<p><span class="mw-default-size mw-image-border extra" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}'><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></span></p> !! end # Note that 'right' is the default alignment, despite the misspelled 'righ' below @@ -12863,9 +13626,9 @@ parsoid=wt2html,wt2wt,html2html <div class="thumb tleft"><div class="thumbinner" style="width:222px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" width="220" height="25" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/330px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/440px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>caption</div></div></div> !! html/parsoid -<figure class="mw-default-size mw-halign-left" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>caption</figcaption></figure> -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>caption</figcaption></figure> -<figure class="mw-default-size mw-halign-left" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>caption</figcaption></figure> +<figure class="mw-default-size mw-halign-left" 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>caption</figcaption></figure> +<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>caption</figcaption></figure> +<figure class="mw-default-size mw-halign-left" 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>caption</figcaption></figure> !! end !! article @@ -12910,7 +13673,7 @@ Parsoid-specific image handling - simple image with size and middle alignment !! wikitext [[File:Foobar.jpg|middle|50px]] !! html/parsoid -<p><span class="mw-valign-middle" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></span></p> +<p><span class="mw-valign-middle" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></span></p> !! end !! test @@ -12921,7 +13684,7 @@ parsoid=wt2wt,wt2html,html2html !! wikitext [[Image:Foobar.jpg|middle|50px]] !! html/parsoid -<p><span class="mw-valign-middle" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></span></p> +<p><span class="mw-valign-middle" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></span></p> !! end !! test @@ -12930,7 +13693,7 @@ Parsoid-specific image handling - simple image with size and middle alignment !! wikitext [[File:Foobar.jpg|50px|middle]] !! html/parsoid -<p><span class="mw-valign-middle" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"width","ak":"50px"},{"ck":"middle","ak":"middle"}]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"6","width":"50"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p> +<p><span class="mw-valign-middle" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"width","ak":"50px"},{"ck":"middle","ak":"middle"}]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"6","width":"50"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p> !! end !! test @@ -12941,7 +13704,7 @@ parsoid=wt2html,wt2wt,html2html !! wikitext [[Image:Foobar.jpg|50px|middle]] !! html/parsoid -<p><span class="mw-valign-middle" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></span></p> +<p><span class="mw-valign-middle" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></span></p> !! end !! test @@ -12965,7 +13728,7 @@ Parsoid-specific image handling - thumbnail with halign, valign, and caption !! wikitext [[File:Foobar.jpg|left|baseline|thumb|caption content]] !! html/parsoid -<figure class="mw-default-size mw-halign-left mw-valign-baseline" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>caption content</figcaption></figure> +<figure class="mw-default-size mw-halign-left mw-valign-baseline" 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>caption content</figcaption></figure> !! end !! test @@ -12974,7 +13737,7 @@ Parsoid-specific image handling - thumbnail with halign, valign, and caption !! wikitext [[File:Foobar.jpg|thumb|left|baseline|caption content]] !! html/parsoid -<figure class="mw-default-size mw-halign-left mw-valign-baseline" typeof="mw:Image/Thumb" data-parsoid='{"optList":[{"ck":"thumbnail","ak":"thumb"},{"ck":"left","ak":"left"},{"ck":"baseline","ak":"baseline"},{"ck":"caption","ak":"caption content"}]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"25","width":"220"},"sa":{"resource":"File:Foobar.jpg"}}'/></a><figcaption>caption content</figcaption></figure> +<figure class="mw-default-size mw-halign-left mw-valign-baseline" typeof="mw:Image/Thumb" data-parsoid='{"optList":[{"ck":"thumbnail","ak":"thumb"},{"ck":"left","ak":"left"},{"ck":"baseline","ak":"baseline"},{"ck":"caption","ak":"caption content"}]}'><a href="./File:Foobar.jpg" data-parsoid='{"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" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"25","width":"220"},"sa":{"resource":"File:Foobar.jpg"}}'/></a><figcaption>caption content</figcaption></figure> !! end !! test @@ -12982,7 +13745,7 @@ Parsoid-specific image handling - thumbnail with specific size, halign, valign, !! wikitext [[Image:Foobar.jpg|right|middle|thumb|50x50px|caption]] !! html/parsoid -<figure class="mw-halign-right mw-valign-middle" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a><figcaption>caption</figcaption></figure> +<figure class="mw-halign-right mw-valign-middle" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a><figcaption>caption</figcaption></figure> !! end !! test @@ -13029,7 +13792,7 @@ Parsoid-specific image handling - simple image with a formatted caption !! wikitext [[File:Foobar.jpg|<table><tr><td>a</td><td>b</td></tr><tr><td>c</td></tr></table>]] !! html/parsoid -<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"<table><tr><td>a</td><td>b</td></tr><tr><td>c</td></tr></table>"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> +<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"<table data-parsoid=\"{&quot;stx&quot;:&quot;html&quot;,&quot;dsr&quot;:[18,81,7,8]}\"><tbody data-parsoid=\"{&quot;dsr&quot;:[25,73,0,0]}\"><tr data-parsoid=\"{&quot;stx&quot;:&quot;html&quot;,&quot;dsr&quot;:[25,54,4,5]}\"><td data-parsoid=\"{&quot;stx&quot;:&quot;html&quot;,&quot;dsr&quot;:[29,39,4,5]}\">a</td><td data-parsoid=\"{&quot;stx&quot;:&quot;html&quot;,&quot;dsr&quot;:[39,49,4,5]}\">b</td></tr><tr data-parsoid=\"{&quot;stx&quot;:&quot;html&quot;,&quot;dsr&quot;:[54,73,4,5]}\"><td data-parsoid=\"{&quot;stx&quot;:&quot;html&quot;,&quot;dsr&quot;:[58,68,4,5]}\">c</td></tr></tbody></table>"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p> !! end !! test @@ -13050,7 +13813,7 @@ foo bar !! html/parsoid <p>foo</p> -<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="23" width="200"/></a><figcaption>This caption has a <center>unbalanced tag in it.</center></figcaption></figure> +<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="23" width="200"/></a><figcaption>This caption has a <center>unbalanced tag in it.</center></figcaption></figure> <p>bar</p> !! end @@ -13061,7 +13824,7 @@ parsoid=wt2html,wt2wt !! wikitext [[File:Foobar.jpg|thumb|]] !! html/parsoid -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption></figcaption></figure> +<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></figcaption></figure> !! end # empty captions don't get serialized unless we're in the "round trip" case @@ -13088,7 +13851,7 @@ Parsoid-specific image handling - whitespace caption !! wikitext [[File:Foobar.jpg|thumb| ]] !! html/parsoid -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption> </figcaption></figure> +<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> </figcaption></figure> !! end !! test @@ -13103,6 +13866,42 @@ bar bar</p> !! end +## Edge case bugs in Parsoid from T93580 +!! test +T93580: 1. Templated <ref> inside block images +!! wikitext +[[File:Foobar.jpg|thumb|Caption with templated ref: {{echo|<ref>foo</ref>}}]] + +<references /> +!! html/parsoid +<figure class="mw-default-size" typeof="mw:Image/Thumb" data-parsoid='{"optList":[{"ck":"thumbnail","ak":"thumb"},{"ck":"caption","ak":"Caption with templated ref: {{echo|<ref>foo</ref>}}"}]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><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" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"25","width":"220"},"sa":{"resource":"File:Foobar.jpg"}}'/></a><figcaption>Caption with templated ref: <span about="#mwt5" class="mw-ref" id="cite_ref-1" rel="dc:references" typeof="mw:Transclusion mw:Extension/ref" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"<ref>foo</ref>"}},"i":0}}]}'><a href="#cite_note-1" style="counter-reset: mw-Ref 1;"><span class="mw-reflink-text">[1]</span></a></span></figcaption></figure> + +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt6" 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 +T93580: 2. <ref> inside inline images +!! wikitext +[[File:Foobar.jpg|Undisplayed caption in inline image with ref: <ref>foo</ref>]] + +<references /> +!! html/parsoid +<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"Undisplayed caption in inline image with ref: <ref>foo</ref>"}]}' data-mw='{"caption":"Undisplayed caption in inline image with ref: <span about=\"#mwt2\" class=\"mw-ref\" id=\"cite_ref-1\" rel=\"dc:references\" typeof=\"mw:Extension/ref\" data-parsoid=\"{&quot;dsr&quot;:[64,78,5,6]}\" data-mw=\"{&quot;name&quot;:&quot;ref&quot;,&quot;body&quot;:{&quot;id&quot;:&quot;mw-reference-text-cite_note-1&quot;},&quot;attrs&quot;:{}}\"><a href=\"#cite_note-1\" style=\"counter-reset: mw-Ref 1;\"><span class=\"mw-reflink-text\">[1]</span></a></span><meta typeof=\"mw:Extension/ref/Marker\" about=\"#mwt2\" data-parsoid=\"{&quot;group&quot;:&quot;&quot;,&quot;name&quot;:&quot;&quot;,&quot;content&quot;:&quot;foo&quot;,&quot;hasRefInRef&quot;:false,&quot;dsr&quot;:[64,78,5,6],&quot;tmp&quot;:{}}\" data-mw=\"{}\">"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p> + +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt4" 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 +T93580: 3. Templated <ref> inside inline images +!! wikitext +[[File:Foobar.jpg|Undisplayed caption in inline image with ref: {{echo|<ref>{{echo|foo}}</ref>}}]] + +<references /> +!! html/parsoid +<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"Undisplayed caption in inline image with ref: {{echo|<ref>{{echo|foo}}</ref>}}"}]}' data-mw='{"caption":"Undisplayed caption in inline image with ref: <span about=\"#mwt2\" class=\"mw-ref\" id=\"cite_ref-1\" rel=\"dc:references\" typeof=\"mw:Transclusion mw:Extension/ref\" data-parsoid=\"{&quot;dsr&quot;:[64,96,null,null],&quot;pi&quot;:[[{&quot;k&quot;:&quot;1&quot;,&quot;spc&quot;:[&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;]}]]}\" data-mw=\"{&quot;parts&quot;:[{&quot;template&quot;:{&quot;target&quot;:{&quot;wt&quot;:&quot;echo&quot;,&quot;href&quot;:&quot;./Template:Echo&quot;},&quot;params&quot;:{&quot;1&quot;:{&quot;wt&quot;:&quot;<ref>{{echo|foo}}</ref>&quot;}},&quot;i&quot;:0}}]}\"><a href=\"#cite_note-1\" style=\"counter-reset: mw-Ref 1;\"><span class=\"mw-reflink-text\">[1]</span></a></span><meta typeof=\"mw:Transclusion mw:Extension/ref/Marker\" about=\"#mwt2\" data-parsoid=\"{&quot;group&quot;:&quot;&quot;,&quot;name&quot;:&quot;&quot;,&quot;content&quot;:&quot;foo&quot;,&quot;hasRefInRef&quot;:false,&quot;dsr&quot;:[64,96,null,null],&quot;pi&quot;:[[{&quot;k&quot;:&quot;1&quot;,&quot;spc&quot;:[&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;]}]],&quot;tmp&quot;:{}}\" data-mw=\"{&quot;parts&quot;:[{&quot;template&quot;:{&quot;target&quot;:{&quot;wt&quot;:&quot;echo&quot;,&quot;href&quot;:&quot;./Template:Echo&quot;},&quot;params&quot;:{&quot;1&quot;:{&quot;wt&quot;:&quot;<ref>{{echo|foo}}</ref>&quot;}},&quot;i&quot;:0}}]}\">"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p> + +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt6" 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 ### ### Subpages @@ -13435,6 +14234,152 @@ Bar </p> !! end +## The whitespace on the empty line is part of the test. Please do not delete +!! test +1. Categories and newlines: All preceding newlines should be suppressed (courtesy bug 87) +!! options +parsoid=wt2html,wt2wt +!! wikitext +This + +[[Category:Foo]] and this should be part of same paragraph (not an indent-pre) + +{{echo|[[Category:Foo]] and so should this!}} +!! html +<p>This and this should be part of same paragraph (not an indent-pre) and so should this! +</p> +!! html/parsoid +<p>This + +<link rel="mw:PageProp/Category" href="./Category:Foo"/> and this should be part of same paragraph (not an indent-pre) + +<link rel="mw:PageProp/Category" href="./Category:Foo" about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[[Category:Foo]] and so should this!"}},"i":0}}]}'/><span about="#mwt1"> and so should this!</span></p> +!! end + +## Parsoid will not try to wt2wt this while preserving newlines because +## it suppresses excess newlines within list items -- and we don't want to +## introduce a special case just for categories, which is, in reality somewhat +## odd behavior -- categories are unlikely to be used in list items like this +## in top-level pages and are only likely to show up in template-generated +## list items where this RT-ing is a non-issue. +## +## The whitespace on the empty line is part of the test. Please do not delete +!! test +2. Categories and newlines: All preceding newlines should be suppressed (courtesy bug 87) +!! options +parsoid=wt2html +!! wikitext +* This + +[[Category:Foo]] and this should be part of the same list item +* So should this + +{{echo|[[Category:Foo]] and this should be part of the same list item}} +!! html +<ul><li>This and this should be part of the same list item</li> +<li>So should this and this should be part of the same list item</li></ul> +!! html/parsoid +<ul> +<li>This <link rel="mw:PageProp/Category" href="./Category:Foo"/> and this should be part of the same list item</li> +<li>So should this <link rel="mw:PageProp/Category" href="./Category:Foo" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[[Category:Foo]] and this should be part of the same list item"}},"i":0}}]}'/><span> and this should be part of the same list item</span></li> +</ul> +!! end + +## Newlines and categories that follow the last item of a list +## are treated differently because this (list followed by categories) +## is an extremely common pattern on wikis. +!! test +3. Categories and newlines: newline suppression for last list item should RT properly +!! wikitext +* a +* b + +[[Category:Foo]] + +[[Category:Bar]] +[[Category:Baz]] +!! html/parsoid +<ul><li> a</li> +<li> b</li></ul> + +<link rel="mw:PageProp/Category" href="./Category:Foo" data-parsoid='{"stx":"simple","a":{"href":"./Category:Foo"},"sa":{"href":"Category:Foo"}}'/> + +<link rel="mw:PageProp/Category" href="./Category:Bar" data-parsoid='{"stx":"simple","a":{"href":"./Category:Bar"},"sa":{"href":"Category:Bar"}}'/> +<link rel="mw:PageProp/Category" href="./Category:Baz" data-parsoid='{"stx":"simple","a":{"href":"./Category:Baz"},"sa":{"href":"Category:Baz"}}'/> +!! end + +!! test +4. Categories and newlines: newline suppression for last list item should RT properly +!! wikitext +* a +**** b + +[[Category:Foo]] +!! html/parsoid +<ul><li> a +<ul><li><ul><li><ul><li> b</li></ul></li></ul></li></ul></li></ul> + +<link rel="mw:PageProp/Category" href="./Category:Foo" data-parsoid='{"stx":"simple","a":{"href":"./Category:Foo"},"sa":{"href":"Category:Foo"}}'/> +!! end + +## only wt2html for this to make sure the algo only applies to the rightmost path +!! test +5. Categories and newlines: migrateTrailingCategories dom pass should only run on the rightmost path of nested lists +!! options +parsoid=wt2html +!! wikitext +* a +** b +[[Category:Foo]] +* c +** d +[[Category:Foo]] +!! html/parsoid +<ul><li> a +<ul><li> b +<link rel="mw:PageProp/Category" href="./Category:Foo" data-parsoid='{"stx":"simple","a":{"href":"./Category:Foo"},"sa":{"href":"Category:Foo"}}'/></li></ul></li> +<li> c +<ul><li> d</li></ul></li></ul> +<link rel="mw:PageProp/Category" href="./Category:Foo" data-parsoid='{"stx":"simple","a":{"href":"./Category:Foo"},"sa":{"href":"Category:Foo"}}'/> +!! end + +!! test +6. Categories and newlines: migrateTrailingCategories dom pass should not migrate categories not preceded by newlines +!! wikitext +* a [[Category:Foo]] +!! html/parsoid +<ul><li>a <link rel="mw:PageProp/Category" href="./Category:Foo" data-parsoid='{"stx":"simple","a":{"href":"./Category:Foo"},"sa":{"href":"Category:Foo"}}'/></li></ul> +!! end + +# This test also demonstrates because of newline+category tunneling +# through the list hander, template wrapping doesn't expand to the +# containing list when the list item swallows the category. +!! test +7. Categories and newlines: migrateTrailingCategories dom pass should leave template content alone +!! wikitext +* {{echo|a +[[Category:Foo]]}} +!! html/parsoid +<ul><li> <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\n[[Category:Foo]]"}},"i":0}}]}'>a</span><span about="#mwt1"> +</span><link rel="mw:PageProp/Category" href="./Category:Foo" about="#mwt1" data-parsoid='{"stx":"simple","a":{"href":"./Category:Foo"},"sa":{"href":"Category:Foo"}}'/></li></ul> +!! end + +!! test +8. Categories and newlines: migrateTrailingCategories dom pass should not get tripped by intervening templates +!! wikitext +* a + +{{echo|[[Category:Foo]] +[[Category:Bar]]}} +[[Category:Baz]] +!! html/parsoid +<ul><li> a</li></ul> + +<link rel="mw:PageProp/Category" href="./Category:Foo" about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"stx":"simple","a":{"href":"./Category:Foo"},"sa":{"href":"Category:Foo"},"pi":[[{"k":"1","spc":["","","",""]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[[Category:Foo]]\n[[Category:Bar]]"}},"i":0}}]}'/><span about="#mwt1"> +</span><link rel="mw:PageProp/Category" href="./Category:Bar" about="#mwt1" data-parsoid='{"stx":"simple","a":{"href":"./Category:Bar"},"sa":{"href":"Category:Bar"}}'/> +<link rel="mw:PageProp/Category" href="./Category:Baz" data-parsoid='{"stx":"simple","a":{"href":"./Category:Baz"},"sa":{"href":"Category:Baz"}}'/> +!! end + !! test Parsoid: Serialize link to category page with colon escape !! options @@ -13445,20 +14390,26 @@ parsoid [[:Category:Foo|Bar]] !! html <p> -<a rel="mw:WikiLink" href="Category:Foo" title="Category:Foo">Category:Foo</a> -<a rel="mw:WikiLink" href="Category:Foo" title="Category:Foo">Bar</a> +<a rel="mw:WikiLink" href="./Category:Foo" title="Category:Foo">Category:Foo</a> +<a rel="mw:WikiLink" href="./Category:Foo" title="Category:Foo">Bar</a> </p> !! end +# html2wt localizes the "Category" namespace. +# XXX the <link> element needs an empty data-parsoid attribute, or +# else the html2html test fails because spaces are inserted. !! test -Parsoid: Link prefix/suffixes aren't applied to category links +Link prefix/suffixes aren't applied to category links !! options parsoid=wt2html,wt2wt,html2html language=is !! wikitext x[[Category:Foo]]y -!! html -<p>x<link rel="mw:PageProp/Category" href="Category:Foo">y</p> +!! html/php +<p>xy +</p> +!! html/parsoid +<p>x<link rel="mw:PageProp/Category" href="./Flokkur:Foo" data-parsoid=""/>y</p> !! end !! test @@ -13484,15 +14435,15 @@ parsoid [[Category:Foo]] [[Category:Foo|Bar]] !! html -<link rel="mw:PageProp/Category" href="Category:Foo"> -<link rel="mw:PageProp/Category" href="Category:Foo#Bar"> +<link rel="mw:PageProp/Category" href="./Category:Foo"> +<link rel="mw:PageProp/Category" href="./Category:Foo#Bar"> !! end !! test Normalize hrefs properly before testing for invalid link targets (bug 70894) !! options parsoid=html2wt -!! html +!! html/parsoid <link rel="mw:PageProp/Category" href="./Category:Toxine_bactérienne"/> !! wikitext [[Category:Toxine bactérienne]] @@ -13630,7 +14581,7 @@ __FORCETOC__ == Headline == == Headline 2 == == Headline == -!! html +!! html/php <div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#Headline_2"><span class="tocnumber">1</span> <span class="toctext">Headline 2</span></a></li> @@ -13745,6 +14696,22 @@ TOC regression (T11764) !! end !! test +TOC for heading containing <span id="..."></span> (T96153) +!! wikitext +__FORCETOC__ +==<span id="old-anchor"></span>New title== +!! html/php +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#New_title"><span class="tocnumber">1</span> <span class="toctext">New title</span></a></li> +</ul> +</div> + +<h2><span class="mw-headline" id="New_title"><span id="old-anchor"></span>New title</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: New title">edit</a><span class="mw-editsection-bracket">]</span></span></h2> + +!! end + +!! test TOC with wgMaxTocLevel=3 (bug 6204) !! options wgMaxTocLevel=3 @@ -14376,11 +15343,8 @@ I always thought &xacute; was a cute letter. </p> !! end -# TODO: generalize to PHP parser? !! test HTML5 tags -!! options -parsoid !! wikitext <data value="5">five</data> <time datetime="2000-01-01T00:00Z">The new millenium started</time> @@ -14388,7 +15352,8 @@ parsoid !! html <p><data value="5">five</data> <time datetime="2000-01-01T00:00Z">The new millenium started</time> -<mark>This highlighted text</mark></p> +<mark>This highlighted text</mark> +</p> !! end !! test @@ -14628,13 +15593,19 @@ Attribute test: unquoted but illegal value (hash) </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 @@ -14932,6 +15903,7 @@ MSIE 6 CSS safety test: sup/sub script (bug 55332) !! end +# FIXME: Parsoid fails to sanitize this! See T58846. !! test Opera -o-link CSS !! wikitext @@ -15006,7 +15978,7 @@ CSS line continuation 2 !! wikitext <div style="background-image: u\ rl(test.jpg); "></div> !! html -<div style="/* insecure input */"></div> +<div style="/* invalid control char */"></div> !! end @@ -15063,7 +16035,7 @@ evil <math>-wiki-tags without Extension:Math enabled Parser hook: empty input !! wikitext <tag></tag> -!! html +!! html/php <pre> '' array ( @@ -15076,7 +16048,7 @@ array ( Parser hook: empty input using terminated empty elements !! wikitext <tag/> -!! html +!! html/php <pre> NULL array ( @@ -15089,7 +16061,7 @@ array ( Parser hook: empty input using terminated empty elements (space before) !! wikitext <tag /> -!! html +!! html/php <pre> NULL array ( @@ -15102,7 +16074,7 @@ array ( Parser hook: basic input !! wikitext <tag>input</tag> -!! html +!! html/php <pre> 'input' array ( @@ -15116,7 +16088,7 @@ array ( Parser hook: case insensitive !! wikitext <TAG>input</TAG> -!! html +!! html/php <pre> 'input' array ( @@ -15130,7 +16102,7 @@ array ( Parser hook: case insensitive, redux !! wikitext <TaG>input</TAg> -!! html +!! html/php <pre> 'input' array ( @@ -15145,7 +16117,7 @@ Parser hook: nested tags noxml !! wikitext <tag><tag></tag></tag> -!! html +!! html/php <pre> '<tag>' array ( @@ -15158,14 +16130,14 @@ 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> @@ -15175,7 +16147,7 @@ array ( Parser hook: argument containing a forward slash (bug 5344) !! wikitext <tag filename='/tmp/bla'></tag> -!! html +!! html/php <pre> '' array ( @@ -15189,7 +16161,7 @@ array ( Parser hook: empty input using terminated empty elements (bug 2374) !! wikitext <tag foo=bar/>text -!! html +!! html/php <pre> NULL array ( @@ -15206,14 +16178,14 @@ Parser hook: basic arguments using terminated empty elements (bug 2374) <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 @@ -15230,7 +16202,7 @@ Parser hook: static parser hook not inside a comment !! wikitext <statictag>hello, world</statictag> <statictag action=flush/> -!! html +!! html/php <p>hello, world </p> !! end @@ -15241,7 +16213,7 @@ Parser hook: static parser hook inside a comment !! wikitext <!-- <statictag>hello, world</statictag> --> <statictag action=flush/> -!! html +!! html/php <p><br /> </p> !! end @@ -15300,20 +16272,24 @@ Sanitizer: Closing of open but not closed tags !! 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 @@ -15360,7 +16336,7 @@ Sanitizer: Validating that <meta> and <link> work, but only for Microdata <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" /> @@ -16112,7 +17088,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php start !! end @@ -16132,7 +17108,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php ==a== ===aa=== ====aaa==== @@ -16154,7 +17130,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php ===aa=== ====aaa==== !! end @@ -16175,7 +17151,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php ====aaa==== !! end @@ -16195,7 +17171,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php ==b== ===ba=== ===bb=== @@ -16219,7 +17195,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php ===ba=== !! end @@ -16239,7 +17215,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php ===bb=== ====bba==== !! end @@ -16260,7 +17236,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php ====bba==== !! end @@ -16280,7 +17256,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php ===bc=== !! end @@ -16300,7 +17276,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php ==c== ===ca=== !! end @@ -16321,7 +17297,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php ===ca=== !! end @@ -16341,7 +17317,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php !! end !! test @@ -16352,7 +17328,7 @@ section=1 ==a== ==bogus== not a legal section ==b== -!! html +!! html/php ==a== ==bogus== not a legal section !! end @@ -16365,7 +17341,7 @@ section=2 ==a== ==bogus== not a legal section ==b== -!! html +!! html/php ==b== !! end @@ -16377,7 +17353,7 @@ section=1 ==a== ==b== <!-- --> ==c== -!! html +!! html/php ==a== !! end @@ -16389,7 +17365,7 @@ section=2 ==a== ==b== <!-- --> ==c== -!! html +!! html/php ==b== <!-- --> !! end @@ -16401,7 +17377,7 @@ section=1 ==a== ==bogus== <nowiki>not a legal section</nowiki> ==b== -!! html +!! html/php ==a== ==bogus== <nowiki>not a legal section</nowiki> !! end @@ -16414,11 +17390,10 @@ section=2 ==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 @@ -16428,7 +17403,7 @@ section=1 !! wikitext <!-- -->==sec1== ==sec2== -!! html +!! html/php ==sec2== !!end @@ -16439,11 +17414,10 @@ section=2 !! 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 @@ -16457,7 +17431,7 @@ unmarked one ==2== two -!! html +!! html/php ==1== one !! end @@ -16473,7 +17447,7 @@ unmarked one ==2== two -!! html +!! html/php ==2== two !! end @@ -16487,7 +17461,7 @@ section=1 !! wikitext <noinclude>==unmarked==</noinclude> ==marked== -!! html +!! html/php ==marked== !!end @@ -16502,7 +17476,7 @@ The line above must have a trailing space === <!-- --> <!-- --> But just in case it doesn't... -!! html +!! html/php === <!-- --> <!-- --> But just in case it doesn't... @@ -16524,7 +17498,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php xxx ==a== @@ -16555,7 +17529,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php start xxx @@ -16584,7 +17558,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php start ==a== xxx @@ -16614,7 +17588,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php start ==a== ===aa=== @@ -16645,7 +17619,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php start ==a== ===aa=== @@ -16672,7 +17646,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php start ==a== ===aa=== @@ -16703,7 +17677,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php start ==a== ===aa=== @@ -16733,7 +17707,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php start ==a== ===aa=== @@ -16764,7 +17738,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php start ==a== ===aa=== @@ -16795,7 +17769,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php start ==a== ===aa=== @@ -16824,7 +17798,7 @@ start ===bc=== ==c== ===ca=== -!! html +!! html/php start ==a== ===aa=== @@ -16846,7 +17820,7 @@ replace=2,"xxx" Preformatted initial line ==a== ===a=== -!! html +!! html/php Preformatted initial line ==a== xxx @@ -16860,7 +17834,7 @@ section=1 !! wikitext ==a== a -!! html +!! html/php ==a== a !! end @@ -16872,7 +17846,7 @@ section=1 !! wikitext ==a== a -!! html +!! html/php ==a== a !! end @@ -16890,7 +17864,7 @@ noxml section=2 == Section Two == stuff -!! html +!! html/php == Section Two == stuff !! end @@ -16907,7 +17881,7 @@ noxml replace=2,"xxx" == Section Two == stuff -!! html +!! html/php == Section One == <pre> ======= @@ -16917,7 +17891,6 @@ xxx !! end - !! test Handling of 
 in URLs !! wikitext @@ -17338,12 +18311,44 @@ parsoid=wt2html,wt2wt,html2html <p><span typeof="mw:Entity">î</span><span typeof="mw:Entity">î</span></p> !! end +# See: http://www.w3.org/TR/html5/syntax.html#character-references +# Note that U+000C (form feed) is not a valid XML character, so +# it is banned even though allowed in HTML5. +!! test +Illegal character references (T106578) +!! wikitext +; Null: � +; FF:  +; CR: 
 +; Control (low):  +; Control (high):  Ÿ +; Surrogate: �� +; This is an okay astral character: 💩 +!! html+tidy +<dl> +<dt>Null</dt> +<dd>&#00;</dd> +<dt>FF</dt> +<dd>&#xC;</dd> +<dt>CR</dt> +<dd>&#xD;</dd> +<dt>Control (low)</dt> +<dd>&#8;</dd> +<dt>Control (high)</dt> +<dd>&#x7F; &#x9F;</dd> +<dt>Surrogate</dt> +<dd>&#xD83D;&#xDCA9;</dd> +<dt>This is an okay astral character</dt> +<dd>💩</dd> +</dl> +!! end + !! test __FORCETOC__ override !! wikitext __NEWSECTIONLINK__ __FORCETOC__ -!! html +!! html/php <p><br /> </p> !! end @@ -17358,7 +18363,7 @@ ISBN 978-0-1234-56 789 !! html+tidy <p><a href="/wiki/Special:BookSources/9780123456" class="internal mw-magiclink-isbn">ISBN 978-0-1234-56</a> 789</p> !! html/parsoid -<p><a href="./Special:BookSources/9780123456" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>ISBN 978-0-1234-56</a><span typeof="mw:Entity" data-parsoid='{"src":"&#x20;","srcContent":" "}'> </span>789</p> +<p><a href="./Special:BookSources/9780123456" rel="mw:WikiLink" data-parsoid='{"stx":"magiclink"}'>ISBN 978-0-1234-56</a><span typeof="mw:Entity" data-parsoid='{"src":"&#x20;","srcContent":" "}'> </span>789</p> !! end !! test @@ -17378,24 +18383,34 @@ ISBN ISBN 1234567890 <p>ISBN <a href="/wiki/Special:BookSources/1234567890" class="internal mw-magiclink-isbn">ISBN 1234567890</a> </p> !! html/parsoid -<p>ISBN <a href="./Special:BookSources/1234567890" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>ISBN 1234567890</a></p> +<p>ISBN <a href="./Special:BookSources/1234567890" rel="mw:WikiLink" data-parsoid='{"stx":"magiclink"}'>ISBN 1234567890</a></p> !! end +# Uppercase X and lowercase x as well !! test ISBN with an X !! wikitext ISBN 3-462-04561-X +ISBN 3-462-04561-x ISBN 080442957X +ISBN 080442957x ISBN 978080442957X +ISBN 978080442957x !! html/php <p><a href="/wiki/Special:BookSources/346204561X" class="internal mw-magiclink-isbn">ISBN 3-462-04561-X</a> +<a href="/wiki/Special:BookSources/346204561X" class="internal mw-magiclink-isbn">ISBN 3-462-04561-x</a> <a href="/wiki/Special:BookSources/080442957X" class="internal mw-magiclink-isbn">ISBN 080442957X</a> +<a href="/wiki/Special:BookSources/080442957X" class="internal mw-magiclink-isbn">ISBN 080442957x</a> <a href="/wiki/Special:BookSources/978080442957X" class="internal mw-magiclink-isbn">ISBN 978080442957X</a> +<a href="/wiki/Special:BookSources/978080442957X" class="internal mw-magiclink-isbn">ISBN 978080442957x</a> </p> !! html/parsoid -<p><a href="./Special:BookSources/346204561X" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>ISBN 3-462-04561-X</a> -<a href="./Special:BookSources/080442957X" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>ISBN 080442957X</a> -<a href="./Special:BookSources/978080442957X" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>ISBN 978080442957X</a></p> +<p><a href="./Special:BookSources/346204561X" rel="mw:WikiLink" data-parsoid='{"stx":"magiclink"}'>ISBN 3-462-04561-X</a> +<a href="./Special:BookSources/346204561X" rel="mw:WikiLink" data-parsoid='{"stx":"magiclink"}'>ISBN 3-462-04561-x</a> +<a href="./Special:BookSources/080442957X" rel="mw:WikiLink" data-parsoid='{"stx":"magiclink"}'>ISBN 080442957X</a> +<a href="./Special:BookSources/080442957X" rel="mw:WikiLink" data-parsoid='{"stx":"magiclink"}'>ISBN 080442957x</a> +<a href="./Special:BookSources/978080442957X" rel="mw:WikiLink" data-parsoid='{"stx":"magiclink"}'>ISBN 978080442957X</a> +<a href="./Special:BookSources/978080442957X" rel="mw:WikiLink" data-parsoid='{"stx":"magiclink"}'>ISBN 978080442957x</a></p> !! end !! test @@ -17406,7 +18421,7 @@ ISBN 1234567890 <p><a href="/wiki/Special:BookSources/1234567890" class="internal mw-magiclink-isbn">ISBN 1234567890</a> </p> !! html/parsoid -<p><a href="Special:BookSources/1234567890" rel="mw:ExtLink">ISBN 1234567890</a></p> +<p><a href="./Special:BookSources/1234567890" rel="mw:WikiLink">ISBN 1234567890</a></p> !! end !! test @@ -17417,7 +18432,7 @@ Bug 22905: <abbr> followed by ISBN followed by </a> <p><abbr>(fr)</abbr> <a href="/wiki/Special:BookSources/2753300917" class="internal mw-magiclink-isbn">ISBN 2753300917</a> <a rel="nofollow" class="external text" href="http://www.example.com">example.com</a> </p> !! html/parsoid -<p><abbr data-parsoid='{"stx":"html"}'>(fr)</abbr> <a href="./Special:BookSources/2753300917" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>ISBN 2753300917</a> <a rel="mw:ExtLink" href="http://www.example.com">example.com</a></p> +<p><abbr data-parsoid='{"stx":"html"}'>(fr)</abbr> <a href="./Special:BookSources/2753300917" rel="mw:WikiLink" data-parsoid='{"stx":"magiclink"}'>ISBN 2753300917</a> <a rel="mw:ExtLink" href="http://www.example.com">example.com</a></p> !! end !! test @@ -17548,7 +18563,7 @@ Images with the "|" character in the comment <div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>An <a rel="nofollow" class="external text" href="http://test/?param1=%7Cleft%7C&param2=%7Cx">external</a> URL</div></div></div> !! html/parsoid -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>An <a rel="mw:ExtLink" href="http://test/?param1=|left|&param2=|x">external</a> URL</figcaption></figure> +<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>An <a rel="mw:ExtLink" href="http://test/?param1=|left|&param2=|x">external</a> URL</figcaption></figure> !! end !! test @@ -17704,7 +18719,7 @@ Don't fall for the self-closing div MSGNW magic word !! wikitext {{MSGNW:msg}} -!! html +!! html/php <p>[[:Template:Msg]] </p> !! end @@ -18294,6 +19309,61 @@ Raw: -{R|zh:China;zh-tw:Taiwan}- !! end !! test +Strings evaluating false shouldn't be ignored by Language converter (T51072) +!! options +language=zh variant=zh-cn +!! input +-{zh-cn:0;zh-sg:1;zh-tw:2;zh-hk:3}- +!! result +<p>0 +</p> +!! end + +!! test +Conversion rules from [numeric-only string] to [something else] (T48634) +!! options +language=zh variant=zh-cn +!! input +-{H|0=>zh-cn:B}--{H|0=>zh-cn:C;0=>zh-cn:D}--{H|0=>zh-hans:A}-012345-{A|zh-tw:0;zh-cn:E;}-012345 +!! result +<p>D12345EE12345 +</p> +!! end + +!! test +Bidirectional converter rule entries with an empty value should be ignored (T53551) +!! options +language=zh variant=zh-cn +!! input +-{H|zh-cn:foo;zh-tw:;}-foobar +!! result +<p>foobar +</p> +!! end + +!! test +Unidirectional converter rule entries with an empty "from" string should be ignored (T53551) +!! options +language=zh variant=zh-cn +!! input +-{H|=>zh-cn:foo;}-foobar +!! result +<p>foobar +</p> +!! end + +!! test +Empty converter rule entries shouldn't be inserted into the conversion table (T53551) +!! options +language=zh variant=zh-cn +!! input +-{H|}-foobar +!! result +<p>foobar +</p> +!! end + +!! test Nested using of manual convert syntax !! options language=zh variant=zh-hk @@ -19038,7 +20108,7 @@ percent-encoding and + signs in comments (Bug 26410) comment !! wikitext [[ABC%33D% ++]] [[ABC%33D% ++|+%20]] -!! html +!! html/php <a href="/index.php?title=ABC3D%25_%2B%2B&action=edit&redlink=1" class="new" title="ABC3D% ++ (page does not exist)">ABC3D% ++</a> <a href="/index.php?title=ABC3D%25_%2B%2B&action=edit&redlink=1" class="new" title="ABC3D% ++ (page does not exist)">+%20</a> !! end @@ -19082,7 +20152,7 @@ wgAllowDisplayTitle=true wgRestrictDisplayTitle=false !! wikitext this is not the the title -!! html +!! html/php Parser test <p>this is not the the title </p> @@ -19099,7 +20169,7 @@ wgRestrictDisplayTitle=false !! wikitext this is not the the title {{DISPLAYTITLE:whatever}} -!! html +!! html/php whatever <p>this is not the the title </p> @@ -19116,7 +20186,7 @@ wgRestrictDisplayTitle=true !! wikitext this is not the the title {{DISPLAYTITLE:whatever}} -!! html +!! html/php Screen <p>this is not the the title </p> @@ -19133,7 +20203,7 @@ wgRestrictDisplayTitle=true !! wikitext this is not the the title {{DISPLAYTITLE:screen}} -!! html +!! html/php screen <p>this is not the the title </p> @@ -19149,7 +20219,7 @@ wgAllowDisplayTitle=false !! 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> @@ -19165,7 +20235,7 @@ title=[[Screen]] wgAllowDisplayTitle=false !! wikitext this is not the the title -!! html +!! html/php Screen <p>this is not the the title </p> @@ -19182,7 +20252,7 @@ wgRestrictDisplayTitle=true !! 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> @@ -19199,7 +20269,7 @@ wgRestrictDisplayTitle=true !! 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> @@ -19224,7 +20294,7 @@ Page status indicators: Weird syntaxes that are okay showindicators !! wikitext <indicator name="empty" /> -<indicator name></indicator> +<indicator name="name"></indicator> !! html empty= name= @@ -19287,7 +20357,7 @@ preload: check <noinclude> and <includeonly> preload !! wikitext Hello <noinclude>cruel</noinclude><includeonly>kind</includeonly> world. -!! html +!! html/php Hello kind world. !! end @@ -19297,7 +20367,7 @@ preload: check <onlyinclude> preload !! wikitext Goodbye <onlyinclude>Hello world</onlyinclude> -!! html +!! html/php Hello world !! end @@ -19307,7 +20377,7 @@ preload: can pass tags through if we want to preload !! wikitext <includeonly><</includeonly>includeonly>Hello world<includeonly><</includeonly>/includeonly> -!! html +!! html/php <includeonly>Hello world</includeonly> !! end @@ -19317,7 +20387,7 @@ preload: check that it doesn't try to do tricks 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 @@ -19368,7 +20438,10 @@ percent-encoding and + signs in internal links (Bug 26410) <a href="/index.php?title=3E&action=edit&redlink=1" class="new" title="3E (page does not exist)">3E</a> <a href="/index.php?title=3E%2B&action=edit&redlink=1" class="new" title="3E+ (page does not exist)">3E+</a> </p> !! html/parsoid -<p><a rel="mw:WikiLink" href="User:+%" title="User:+%">User:+%</a> <a rel="mw:WikiLink" href="Page+title%" title="Page+title%">Page+title%</a> <a rel="mw:WikiLink" href="%+" title="%+">%+</a> <a rel="mw:WikiLink" href="%+" title="%+">%20</a> <a rel="mw:WikiLink" href="%+" title="%+">%+ </a> <a rel="mw:WikiLink" href="%+r" title="%+r">%+r</a> <a rel="mw:WikiLink" href="%" title="%">%</a> <a rel="mw:WikiLink" href="+" title="+">+</a> <span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"[[bar]]"}'><a href="./File:%+abc9"><img resource="./File:%25+abc9" src="./Special:FilePath/%+abc9" height="220" width="220"/></a></span> <a rel="mw:WikiLink" href="3E" title="3E">3E</a> <a rel="mw:WikiLink" href="3E+" title="3E+">3E+</a></p> +<p><a rel="mw:WikiLink" href="./User:+%25" title="User:+%">User:+%</a> <a rel="mw:WikiLink" href="Page+title%25" title="Page+title%">Page+title%</a> +<a rel="mw:WikiLink" href="%25+" title="%+">%+</a> <a rel="mw:WikiLink" href="%25+" title="%+">%20</a> <a rel="mw:WikiLink" href="%25+" title="%+">%+ </a> <a rel="mw:WikiLink" href="%25+r" title="%+r">%+r</a> +<a rel="mw:WikiLink" href="%25" title="%">%</a> <a rel="mw:WikiLink" href="+" title="+">+</a> <span class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"bogus","ak":"foo"},{"ck":"caption","ak":"[[bar]]"}]}' data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"<a rel=\"mw:WikiLink\" href=\"./Bar\" title=\"Bar\" data-parsoid=\"{&quot;stx&quot;:&quot;simple&quot;,&quot;a&quot;:{&quot;href&quot;:&quot;./Bar&quot;},&quot;sa&quot;:{&quot;href&quot;:&quot;bar&quot;},&quot;dsr&quot;:[94,101,2,2]}\">bar</a>"}'><a href="./File:%25+abc9"><img resource="./File:%25+abc9" src="./Special:FilePath/%25+abc9" height="220" width="220" data-parsoid='{"a":{"resource":"./File:%25+abc9","height":"220","width":"220"},"sa":{"resource":"File:%+abc%39"}}'/></a></span> +<a rel="mw:WikiLink" href="./3E" title="3E" data-parsoid='{"stx":"simple","a":{"href":"./3E"},"sa":{"href":"%33%45"}}'>3E</a> <a rel="mw:WikiLink" href="./3E+" title="3E+" data-parsoid='{"stx":"simple","a":{"href":"./3E+"},"sa":{"href":"%33%45+"}}'>3E+</a></p> !! end !! test @@ -19382,7 +20455,7 @@ Special characters in embedded file links (bug 27679) </p> !! html/parsoid <p><span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}]}'><a href="./File:Contains_&_ampersand.jpg"><img resource="./File:Contains_&_ampersand.jpg" src="./Special:FilePath/Contains_&_ampersand.jpg" height="220" width="220"/></a></span> -<span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"Title with & ampersand"}'><a href="./File:Does_not_exist.jpg"><img resource="./File:Does_not_exist.jpg" src="./Special:FilePath/Does_not_exist.jpg" height="220" width="220"/></a></span></p> +<span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"Title with &amp; ampersand"}'><a href="./File:Does_not_exist.jpg"><img resource="./File:Does_not_exist.jpg" src="./Special:FilePath/Does_not_exist.jpg" height="220" width="220"/></a></span></p> !! end !! test @@ -19653,14 +20726,18 @@ __TOC__ <p><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Foo Bar">edit</a><span class="mw-editsection-bracket">]</span></span></p> !! end +# Don't expect Parsoid to roundtrip this until the php parser comes closer to +# html5 tag parsing. !! test Tags with parameters in TOC +!! options +parsoid=wt2html !! wikitext __TOC__ == <sup class="in-h2">Hello</sup> == == <sup class="a > b">Evilbye</sup> == -!! html +!! html/php <div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#Hello"><span class="tocnumber">1</span> <span class="toctext"><sup>Hello</sup></span></a></li> @@ -19669,8 +20746,13 @@ __TOC__ </div> <h2><span class="mw-headline" id="Hello"><sup class="in-h2">Hello</sup></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Hello">edit</a><span class="mw-editsection-bracket">]</span></span></h2> -<h2><span class="mw-headline" id="b.22.3EEvilbye"><sup> b">Evilbye</sup></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: b">Evilbye">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h2><span class="mw-headline" id="b.22.3EEvilbye"><sup class="a"> b">Evilbye</sup></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: b">Evilbye">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +!! html/parsoid +<meta property="mw:PageProp/toc" /> +<h2> <sup class="in-h2" data-parsoid='{"stx":"html"}'>Hello</sup> </h2> + +<h2> <sup class="a " data-parsoid='{"stx":"html"}'> b">Evilbye</sup> </h2> !! end !! test @@ -19775,10 +20857,12 @@ Strip marker in urlencode {{urlencode:x<nowiki/>y}} {{urlencode:x<nowiki/>y|wiki}} {{urlencode:x<nowiki/>y|path}} +{{urlencode:x<pre id="one">two</pre>y}} !! html <p>xy xy xy +xy </p> !! end @@ -20137,10 +21221,35 @@ parsoid=wt2html,wt2wt <small>[[Image:Foobar.jpg|right|300px]]</small> !! html/parsoid + <p><b>foo</b></p> -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption><b>caption</b></figcaption></figure> +<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><b>caption</b></figcaption></figure> <p><b>bar</b></p> -<small><figure class="mw-halign-right" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="34" width="300"/></a></figure></small> +<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 #### ---------------------------------------------------------------- @@ -20159,13 +21268,13 @@ B <ref name="x">foo</ref> C <ref name="y" /> <references /> !! html -<p>A <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> -B <span about="#mwt4" class="reference" id="cite_ref-x_2-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-x-2"},"attrs":{"name":"x"}}'><a href="#cite_note-x-2">[2]</a></span> -C <span about="#mwt6" class="reference" id="cite_ref-y_3-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"y"}}'><a href="#cite_note-y-3">[3]</a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt8" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo</span></li> -<li about="#cite_note-x-2" id="cite_note-x-2"><span rel="mw:referencedBy"><a href="#cite_ref-x_2-0">↑</a></span> <span id="mw-reference-text-cite_note-x-2" class="mw-reference-text">foo</span></li> -<li about="#cite_note-y-3" id="cite_note-y-3"><span rel="mw:referencedBy"><a href="#cite_ref-y_3-0">↑</a></span> <span id="mw-reference-text-cite_note-y-3" class="mw-reference-text"></span></li> +<p>A <span about="#mwt2" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span> +B <span about="#mwt4" class="mw-ref" id="cite_ref-x_2-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-x-2"},"attrs":{"name":"x"}}'><a href="#cite_note-x-2"><span class="mw-reflink-text">[2]</span></a></span> +C <span about="#mwt6" class="mw-ref" id="cite_ref-y_3-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"y"}}'><a href="#cite_note-y-3"><span class="mw-reflink-text">[3]</span></a></span></p> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt8" 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">foo</span></li> +<li about="#cite_note-x-2" id="cite_note-x-2"><a href="#cite_ref-x_2-0" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-x-2" class="mw-reference-text">foo</span></li> +<li about="#cite_note-y-3" id="cite_note-y-3"><a href="#cite_ref-y_3-0" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-y-3" class="mw-reference-text"></span></li> </ol> !!end @@ -20178,10 +21287,10 @@ A <ref name="x">foo</ref> B <ref name="x" /> <references /> !! html -<p>A <span about="#mwt2" class="reference" id="cite_ref-x_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-x-1"},"attrs":{"name":"x"}}'><a href="#cite_note-x-1">[1]</a></span> -B <span about="#mwt4" class="reference" id="cite_ref-x_1-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"x"}}'><a href="#cite_note-x-1">[1]</a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-x-1" id="cite_note-x-1"><span rel="mw:referencedBy">↑ <a href="#cite_ref-x_1-0">1.0</a> <a href="#cite_ref-x_1-1">1.1</a></span> <span id="mw-reference-text-cite_note-x-1" class="mw-reference-text">foo</span></li> +<p>A <span about="#mwt2" class="mw-ref" id="cite_ref-x_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-x-1"},"attrs":{"name":"x"}}'><a href="#cite_note-x-1"><span class="mw-reflink-text">[1]</span></a></span> +B <span about="#mwt4" class="mw-ref" id="cite_ref-x_1-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"x"}}'><a href="#cite_note-x-1"><span class="mw-reflink-text">[1]</span></a></span></p> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{}}'> +<li about="#cite_note-x-1" id="cite_note-x-1"><span rel="mw:referencedBy"><a href="#cite_ref-x_1-0"><span class="mw-linkback-text">1 </span></a><a href="#cite_ref-x_1-1"><span class="mw-linkback-text">2 </span></a></span> <span id="mw-reference-text-cite_note-x-1" class="mw-reference-text">foo</span></li> </ol> !!end @@ -20195,11 +21304,11 @@ B <ref name=" x " /> C <ref name= x /> <references /> !! html -<p>A <span about="#mwt2" class="reference" id="cite_ref-x_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-x-1"},"attrs":{"name":"x"}}'><a href="#cite_note-x-1">[1]</a></span> -B <span about="#mwt4" class="reference" id="cite_ref-x_1-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"x"}}'><a href="#cite_note-x-1">[1]</a></span> -C <span about="#mwt6" class="reference" id="cite_ref-x_1-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"x"}}'><a href="#cite_note-x-1">[1]</a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt8" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-x-1" id="cite_note-x-1"><span rel="mw:referencedBy">↑ <a href="#cite_ref-x_1-0">1.0</a> <a href="#cite_ref-x_1-1">1.1</a> <a href="#cite_ref-x_1-2">1.2</a></span> <span id="mw-reference-text-cite_note-x-1" class="mw-reference-text">foo</span></li> +<p>A <span about="#mwt2" class="mw-ref" id="cite_ref-x_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-x-1"},"attrs":{"name":"x"}}'><a href="#cite_note-x-1"><span class="mw-reflink-text">[1]</span></a></span> +B <span about="#mwt4" class="mw-ref" id="cite_ref-x_1-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"x"}}'><a href="#cite_note-x-1"><span class="mw-reflink-text">[1]</span></a></span> +C <span about="#mwt6" class="mw-ref" id="cite_ref-x_1-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"x"}}'><a href="#cite_note-x-1"><span class="mw-reflink-text">[1]</span></a></span></p> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt8" data-mw='{"name":"references","attrs":{}}'> +<li about="#cite_note-x-1" id="cite_note-x-1"><span rel="mw:referencedBy"><a href="#cite_ref-x_1-0"><span class="mw-linkback-text">1 </span></a><a href="#cite_ref-x_1-1"><span class="mw-linkback-text">2 </span></a><a href="#cite_ref-x_1-2"><span class="mw-linkback-text">3 </span></a></span> <span id="mw-reference-text-cite_note-x-1" class="mw-reference-text">foo</span></li> </ol> !!end @@ -20212,9 +21321,9 @@ parsoid A <ref name="constructor">foo</ref> <references /> !! html -<p>A <span about="#mwt2" class="reference" id="cite_ref-constructor_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-constructor-1"},"attrs":{"name":"constructor"}}'><a href="#cite_note-constructor-1">[1]</a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-constructor-1" id="cite_note-constructor-1"><span rel="mw:referencedBy"><a href="#cite_ref-constructor_1-0">↑</a></span> <span id="mw-reference-text-cite_note-constructor-1" class="mw-reference-text">foo</span></li> +<p>A <span about="#mwt2" class="mw-ref" id="cite_ref-constructor_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-constructor-1"},"attrs":{"name":"constructor"}}'><a href="#cite_note-constructor-1"><span class="mw-reflink-text">[1]</span></a></span></p> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'> +<li about="#cite_note-constructor-1" id="cite_note-constructor-1"><a href="#cite_ref-constructor_1-0" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-constructor-1" class="mw-reference-text">foo</span></li> </ol> !!end @@ -20229,10 +21338,10 @@ A <ref> <references /> !! html -<p>A <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></p> +<p>A <span about="#mwt2" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt5" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">This is a <b><a rel="mw:WikiLink" href="Bolded_link" title="Bolded link">bolded link</a></b> and this is a <span about="#mwt3" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"transclusion"}},"i":0}}]}'>transclusion</span> +<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">This is a <b><a rel="mw:WikiLink" href="Bolded_link" title="Bolded link">bolded link</a></b> and this is a <span about="#mwt3" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"transclusion"}},"i":0}}]}'>transclusion</span> </span></li> </ol> !!end @@ -20250,10 +21359,10 @@ A <ref> <references /> !! html -<p>A <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></p> +<p>A <span about="#mwt2" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt4" 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">foo bar baz </span></li> @@ -20280,10 +21389,10 @@ booz <references /> !! html -<p>A <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></p> +<p>A <span about="#mwt2" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt4" 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">foo bar @@ -20306,9 +21415,9 @@ A <ref> foo {{echo|</ref> B C}} <references /> !! html -<p>A <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> B C<span typeof="mw:Nowiki">}}</span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo <span typeof="mw:Nowiki" data-parsoid='{"src":"{{","dsr":[12,14,0,0]}'>{{</span>echo|</span></li> +<p>A <span about="#mwt2" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span> B C<span typeof="mw:Nowiki">}}</span></p> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt4" 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">foo <span typeof="mw:Nowiki" data-parsoid='{"src":"{{","dsr":[12,14,0,0]}'>{{</span>echo|</span></li> </ol> !!end @@ -20320,9 +21429,9 @@ parsoid A <ref> foo <!--</ref> B C <references /> !! html -<p>A <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> B C</p> -<ol class="references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo <!----></span></li> +<p>A <span about="#mwt2" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span> B C</p> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt4" 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">foo <!----></span></li> </ol> !!end @@ -20335,11 +21444,11 @@ A <ref> <b> foo </ref> B C <references /> !! html -<p>A <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> B C</p> +<p>A <span about="#mwt2" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span> B C</p> -<ol class="references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text"><b data-parsoid='{"stx":"html","autoInsertedEnd":true}'> foo </b></span></li> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt4" 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"><b data-parsoid='{"stx":"html","autoInsertedEnd":true}'> foo </b></span></li> </ol> !!end @@ -20352,37 +21461,35 @@ A <ref>foo</ref> B C <ref>bar</ref> D <references /> !! html -<p>A <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> B -C <span about="#mwt4" class="reference" id="cite_ref-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-2"},"attrs":{}}'><a href="#cite_note-2">[2]</a></span> D</p> -<ol class="references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo</span></li> -<li about="#cite_note-2" id="cite_note-2"><span rel="mw:referencedBy"><a href="#cite_ref-2">↑</a></span> <span id="mw-reference-text-cite_note-2" class="mw-reference-text">bar</span></li> +<p>A <span about="#mwt2" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span> B +C <span about="#mwt4" class="mw-ref" id="cite_ref-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-2"},"attrs":{}}'><a href="#cite_note-2"><span class="mw-reflink-text">[2]</span></a></span> D</p> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt6" 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">foo</span></li> +<li about="#cite_note-2" id="cite_note-2"><a href="#cite_ref-2" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-2" class="mw-reference-text">bar</span></li> </ol> !!end !!test Ref: 12. ref-tags act as trailing newline migration barrier -!!options -parsoid !! wikitext -<!--the newline at the end of this line moves out of the p-tag-->a +<!--the newline at the end of this line moves out of the p tag-->a -b<!--the newline at the end of this line stays inside the p-tag--> <ref /> +b<!--the newline at the end of this line stays inside the p tag--> <ref /> <ref /> c <references /> -!! html -<p><!--the newline at the end of this line moves out of the p-tag-->a</p> +!! html/parsoid +<!--the newline at the end of this line moves out of the p tag--><p>a</p> -<p>b<!--the newline at the end of this line stays inside the p-tag--> <span about="#mwt2" class="reference" id="cite_ref-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{}}'><a href="#cite_note-1">[1]</a></span> -<span about="#mwt4" class="reference" id="cite_ref-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{}}'><a href="#cite_note-2">[2]</a></span></p> +<p>b<!--the newline at the end of this line stays inside the p tag--> <span about="#mwt2" class="mw-ref" id="cite_ref-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{}}'><a href="#cite_note-1"><span class="mw-reflink-text">[1]</span></a></span> +<span about="#mwt4" class="mw-ref" id="cite_ref-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{}}'><a href="#cite_note-2"><span class="mw-reflink-text">[2]</span></a></span></p> <p>c</p> -<ol class="references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text"></span></li> -<li about="#cite_note-2" id="cite_note-2"><span rel="mw:referencedBy"><a href="#cite_ref-2">↑</a></span> <span id="mw-reference-text-cite_note-2" class="mw-reference-text"></span></li></ol> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt6" 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"></span></li> +<li about="#cite_note-2" id="cite_note-2"><a href="#cite_ref-2" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-2" class="mw-reference-text"></span></li></ol> !!end !!test @@ -20395,11 +21502,11 @@ parsoid </ref> B <references /> !! html -<p><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> A -<span about="#mwt4" class="reference" id="cite_ref-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-2"},"attrs":{}}'><a href="#cite_note-2">[2]</a></span> B</p> -<ol class="references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo</span></li> -<li about="#cite_note-2" id="cite_note-2"><span rel="mw:referencedBy"><a href="#cite_ref-2">↑</a></span> <span id="mw-reference-text-cite_note-2" class="mw-reference-text">bar +<p><span about="#mwt2" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span> A +<span about="#mwt4" class="mw-ref" id="cite_ref-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-2"},"attrs":{}}'><a href="#cite_note-2"><span class="mw-reflink-text">[2]</span></a></span> B</p> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt6" 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">foo</span></li> +<li about="#cite_note-2" id="cite_note-2"><a href="#cite_ref-2" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-2" class="mw-reference-text">bar </span></li> </ol> !!end @@ -20413,10 +21520,10 @@ parsoid <references /> !! html -<p><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> +<p><span about="#mwt2" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span> </p> -<ol class="references" typeof="mw:Extension/references" about="#mwt5" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo <ref>bar</ref> baz</span></li> +<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">foo <ref>bar</ref> baz</span></li> </ol> !!end @@ -20430,10 +21537,10 @@ B1 <ref name="b" /> B2 <ref name="b">bar</ref> <references /> !! html -<p>A1 <span about="#mwt3" class="reference" id="cite_ref-a_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-a-1"},"attrs":{"name":"a"}}'><a href="#cite_note-a-1">[1]</a></span> A2 <span about="#mwt4" class="reference" id="cite_ref-a_1-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"a"}}'><a href="#cite_note-a-1">[1]</a></span> -B1 <span about="#mwt7" class="reference" id="cite_ref-b_2-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"b"}}'><a href="#cite_note-b-2">[2]</a></span> B2 <span about="#mwt8" class="reference" id="cite_ref-b_2-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-b-2"},"attrs":{"name":"b"}}'><a href="#cite_note-b-2">[2]</a></span></p> +<p>A1 <span about="#mwt3" class="mw-ref" id="cite_ref-a_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-a-1"},"attrs":{"name":"a"}}'><a href="#cite_note-a-1"><span class="mw-reflink-text">[1]</span></a></span> A2 <span about="#mwt4" class="mw-ref" id="cite_ref-a_1-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"a"}}'><a href="#cite_note-a-1"><span class="mw-reflink-text">[1]</span></a></span> +B1 <span about="#mwt7" class="mw-ref" id="cite_ref-b_2-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"b"}}'><a href="#cite_note-b-2"><span class="mw-reflink-text">[2]</span></a></span> B2 <span about="#mwt8" class="mw-ref" id="cite_ref-b_2-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-b-2"},"attrs":{"name":"b"}}'><a href="#cite_note-b-2"><span class="mw-reflink-text">[2]</span></a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt10" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-a-1" id="cite_note-a-1"><span rel="mw:referencedBy">↑ <a href="#cite_ref-a_1-0">1.0</a> <a href="#cite_ref-a_1-1">1.1</a></span> <span id="mw-reference-text-cite_note-a-1" class="mw-reference-text">foo</span></li><li about="#cite_note-b-2" id="cite_note-b-2"><span rel="mw:referencedBy">↑ <a href="#cite_ref-b_2-0">2.0</a> <a href="#cite_ref-b_2-1">2.1</a></span> <span id="mw-reference-text-cite_note-b-2" class="mw-reference-text">bar</span></li> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt10" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-a-1" id="cite_note-a-1"><span rel="mw:referencedBy"><a href="#cite_ref-a_1-0"><span class="mw-linkback-text">1 </span></a><a href="#cite_ref-a_1-1"><span class="mw-linkback-text">2 </span></a></span> <span id="mw-reference-text-cite_note-a-1" class="mw-reference-text">foo</span></li><li about="#cite_note-b-2" id="cite_note-b-2"><span rel="mw:referencedBy"><a href="#cite_ref-b_2-0"><span class="mw-linkback-text">1 </span></a><a href="#cite_ref-b_2-1"><span class="mw-linkback-text">2 </span></a></span> <span id="mw-reference-text-cite_note-b-2" class="mw-reference-text">bar</span></li> </ol> !!end @@ -20447,9 +21554,9 @@ A <ref >foo</ref > <references /> !! html -<p>A <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></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo</span></li></ol> +<p>A <span about="#mwt2" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span></p> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt4" 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">foo</span></li></ol> !!end !!test @@ -20461,11 +21568,11 @@ parsoid <references /> !!html -<p><span class="reference" id="cite_ref-a_b_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-a_b-1"},"attrs":{"name":"a b"}}'><a href="#cite_note-a_b-1">[1]</a></span> +<p><span class="mw-ref" id="cite_ref-a_b_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-a_b-1"},"attrs":{"name":"a b"}}'><a href="#cite_note-a_b-1"><span class="mw-reflink-text">[1]</span></a></span> </p> -<ol class="references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-a_b-1" id="cite_note-a_b-1"><span rel="mw:referencedBy"><a href="#cite_ref-a_b_1-0">↑</a></span> <span id="mw-reference-text-cite_note-a_b-1" class="mw-reference-text">foo</span></li> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'> +<li about="#cite_note-a_b-1" id="cite_note-a_b-1"><a href="#cite_ref-a_b_1-0" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-a_b-1" class="mw-reference-text">foo</span></li> </ol> !!end @@ -20478,11 +21585,11 @@ parsoid <references /> !!html -<p><span class="reference" id="cite_ref-.7B.7Becho.7Ca.7D.7D_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-.7B.7Becho.7Ca.7D.7D-1"},"attrs":{"name":"{{echo|a}}"}}'><a href="#cite_note-.7B.7Becho.7Ca.7D.7D-1">[1]</a></span> +<p><span class="mw-ref" id="cite_ref-.7B.7Becho.7Ca.7D.7D_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-.7B.7Becho.7Ca.7D.7D-1"},"attrs":{"name":"{{echo|a}}"}}'><a href="#cite_note-.7B.7Becho.7Ca.7D.7D-1"><span class="mw-reflink-text">[1]</span></a></span> </p> -<ol class="references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-.7B.7Becho.7Ca.7D.7D-1" id="cite_note-.7B.7Becho.7Ca.7D.7D-1"><span rel="mw:referencedBy"><a href="#cite_ref-.7B.7Becho.7Ca.7D.7D_1-0">↑</a></span> <span id="mw-reference-text-cite_note-.7B.7Becho.7Ca.7D.7D-1" class="mw-reference-text">foo</span></li> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'> +<li about="#cite_note-.7B.7Becho.7Ca.7D.7D-1" id="cite_note-.7B.7Becho.7Ca.7D.7D-1"><a href="#cite_ref-.7B.7Becho.7Ca.7D.7D_1-0" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-.7B.7Becho.7Ca.7D.7D-1" class="mw-reference-text">foo</span></li> </ol> !!end @@ -20495,10 +21602,10 @@ parsoid <references /> !! html -<p>1 <span about="#mwt3" class="reference" id="cite_ref-a_.26_b_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-a_.26_b-1"},"attrs":{"name":"a & b"}}'><a href="#cite_note-a_.26_b-1">[1]</a></span> 2 <span about="#mwt4" class="reference" id="cite_ref-a_.26_b_1-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"a &amp; b"}}'><a href="#cite_note-a_.26_b-1">[1]</a></span> +<p>1 <span about="#mwt3" class="mw-ref" id="cite_ref-a_.26_b_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-a_.26_b-1"},"attrs":{"name":"a & b"}}'><a href="#cite_note-a_.26_b-1"><span class="mw-reflink-text">[1]</span></a></span> 2 <span about="#mwt4" class="mw-ref" id="cite_ref-a_.26_b_1-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"a &amp; b"}}'><a href="#cite_note-a_.26_b-1"><span class="mw-reflink-text">[1]</span></a></span> </p> -<ol class="references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-a_.26_b-1" id="cite_note-a_.26_b-1"><span rel="mw:referencedBy">↑ <a href="#cite_ref-a_.26_b_1-0">1.0</a> <a href="#cite_ref-a_.26_b_1-1">1.1</a></span> <span id="mw-reference-text-cite_note-a_.26_b-1" class="mw-reference-text">foo</span></li> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{}}'> +<li about="#cite_note-a_.26_b-1" id="cite_note-a_.26_b-1"><span rel="mw:referencedBy"><a href="#cite_ref-a_.26_b_1-0"><span class="mw-linkback-text">1 </span></a><a href="#cite_ref-a_.26_b_1-1"><span class="mw-linkback-text">2 </span></a></span> <span id="mw-reference-text-cite_note-a_.26_b-1" class="mw-reference-text">foo</span></li> </ol> !!end @@ -20513,28 +21620,24 @@ C <ref name="foo" /> <references /> !! html -<p>A <span about="#mwt2" class="reference" id="cite_ref-foo_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-foo-1"},"attrs":{"name":"foo"}}'><a href="#cite_note-foo-1">[1]</a></span> -B <span about="#mwt4" class="reference" id="cite_ref-foo_1-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"html":"Foo two"},"attrs":{"name":"foo"}}'><a href="#cite_note-foo-1">[1]</a></span> -C <span about="#mwt6" class="reference" id="cite_ref-foo_1-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"foo"}}'><a href="#cite_note-foo-1">[1]</a></span></p> +<p>A <span about="#mwt2" class="mw-ref" id="cite_ref-foo_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-foo-1"},"attrs":{"name":"foo"}}'><a href="#cite_note-foo-1"><span class="mw-reflink-text">[1]</span></a></span> +B <span about="#mwt4" class="mw-ref" id="cite_ref-foo_1-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"html":"Foo two"},"attrs":{"name":"foo"}}'><a href="#cite_note-foo-1"><span class="mw-reflink-text">[1]</span></a></span> +C <span about="#mwt6" class="mw-ref" id="cite_ref-foo_1-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"foo"}}'><a href="#cite_note-foo-1"><span class="mw-reflink-text">[1]</span></a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt8" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-foo-1" id="cite_note-foo-1"><span rel="mw:referencedBy">↑ <a href="#cite_ref-foo_1-0">1.0</a> <a href="#cite_ref-foo_1-1">1.1</a> <a href="#cite_ref-foo_1-2">1.2</a></span> <span id="mw-reference-text-cite_note-foo-1" class="mw-reference-text">Foo one</span></li> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt8" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-foo-1" id="cite_note-foo-1"><span rel="mw:referencedBy"><a href="#cite_ref-foo_1-0"><span class="mw-linkback-text">1 </span></a><a href="#cite_ref-foo_1-1"><span class="mw-linkback-text">2 </span></a><a href="#cite_ref-foo_1-2"><span class="mw-linkback-text">3 </span></a></span> <span id="mw-reference-text-cite_note-foo-1" class="mw-reference-text">Foo one</span></li> </ol> !!end !!test References: 1. references tag without any refs should be handled properly -!!options -parsoid !! wikitext <references /> -!! html -<ol class="references" typeof="mw:Extension/references" about="#mwt2" data-mw='{"name":"references","attrs":{}}'></ol> +!! html/parsoid +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt2" data-mw='{"name":"references","attrs":{}}'></ol> !!end !!test References: 2. references tag with group only outputs references from that group -!!options -parsoid !! wikitext A <ref group="a">foo</ref> B <ref group="b">bar</ref> @@ -20543,26 +21646,24 @@ C <ref>baz</ref> <references group="a" /> <references /> <references group="b" /> -!! html -<p>A <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":{"group":"a"}}'><a href="#cite_note-1">[a 1]</a></span> -B <span about="#mwt4" class="reference" id="cite_ref-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-2"},"attrs":{"group":"b"}}'><a href="#cite_note-2">[b 1]</a></span> -C <span class="reference" id="cite_ref-3" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-3"},"attrs":{}}'><a href="#cite_note-3">[1]</a></span></p> +!! html/parsoid +<p>A <span about="#mwt2" class="mw-ref" id="cite_ref-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-1"},"attrs":{"group":"a"}}'><a href="#cite_note-1" data-mw-group="a"><span class="mw-reflink-text">[a 1]</span></a></span> +B <span about="#mwt4" class="mw-ref" id="cite_ref-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-2"},"attrs":{"group":"b"}}'><a href="#cite_note-2" data-mw-group="b"><span class="mw-reflink-text">[b 1]</span></a></span> +C <span class="mw-ref" id="cite_ref-3" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-3"},"attrs":{}}'><a href="#cite_note-3"><span class="mw-reflink-text">[1]</span></a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt8" data-mw='{"name":"references","attrs":{"group":"a"}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo</span></li> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt8" data-mw-group="a" data-mw='{"name":"references","attrs":{"group":"a"}}'> +<li about="#cite_note-1" id="cite_note-1"><a href="#cite_ref-1" data-mw-group="a" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo</span></li> </ol> -<ol class="references" typeof="mw:Extension/references" about="#mwt10" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-3" id="cite_note-3"><span rel="mw:referencedBy"><a href="#cite_ref-3">↑</a></span> <span id="mw-reference-text-cite_note-3" class="mw-reference-text">baz</span></li> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt10" data-mw='{"name":"references","attrs":{}}'> +<li about="#cite_note-3" id="cite_note-3"><a href="#cite_ref-3" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-3" class="mw-reference-text">baz</span></li> </ol> -<ol class="references" typeof="mw:Extension/references" about="#mwt12" data-mw='{"name":"references","attrs":{"group":"b"}}'> -<li about="#cite_note-2" id="cite_note-2"><span rel="mw:referencedBy"><a href="#cite_ref-2">↑</a></span> <span id="mw-reference-text-cite_note-2" class="mw-reference-text">bar</span></li> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt12" data-mw-group="b" data-mw='{"name":"references","attrs":{"group":"b"}}'> +<li about="#cite_note-2" id="cite_note-2"><a href="#cite_ref-2" data-mw-group="b" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-2" class="mw-reference-text">bar</span></li> </ol> !!end !!test References: 3. ref list should be cleared after processing references -!!options -parsoid !! wikitext A <ref>foo</ref> @@ -20571,23 +21672,21 @@ A <ref>foo</ref> B <ref>bar</ref> <references /> -!! html -<p>A <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></p> +!! html/parsoid +<p>A <span about="#mwt2" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo</span></li> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt4" 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">foo</span></li> </ol> -<p>B <span about="#mwt6" class="reference" id="cite_ref-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-2"},"attrs":{}}'><a href="#cite_note-2">[1]</a></span></p> +<p>B <span about="#mwt6" class="mw-ref" id="cite_ref-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-2"},"attrs":{}}'><a href="#cite_note-2"><span class="mw-reflink-text">[1]</span></a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt8" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-2" id="cite_note-2"><span rel="mw:referencedBy"><a href="#cite_ref-2">↑</a></span> <span id="mw-reference-text-cite_note-2" class="mw-reference-text">bar</span></li> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt8" data-mw='{"name":"references","attrs":{}}'> +<li about="#cite_note-2" id="cite_note-2"><a href="#cite_ref-2" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-2" class="mw-reference-text">bar</span></li> </ol> !!end !!test References: 4. only referenced group should be cleared after processing references -!!options -parsoid !! wikitext A <ref group="a">afoo</ref> B <ref>bfoo</ref> @@ -20597,23 +21696,21 @@ B <ref>bfoo</ref> C <ref>cfoo</ref> <references /> -!! html -<p>A <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":{"group":"a"}}'><a href="#cite_note-1">[a 1]</a></span> -B <span about="#mwt4" class="reference" id="cite_ref-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-2"},"attrs":{}}'><a href="#cite_note-2">[1]</a></span></p> +!! html/parsoid +<p>A <span about="#mwt2" class="mw-ref" id="cite_ref-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-1"},"attrs":{"group":"a"}}'><a href="#cite_note-1" data-mw-group="a"><span class="mw-reflink-text">[a 1]</span></a></span> +B <span about="#mwt4" class="mw-ref" id="cite_ref-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-2"},"attrs":{}}'><a href="#cite_note-2"><span class="mw-reflink-text">[1]</span></a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{"group":"a"}}'><li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">afoo</span></li> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt6" data-mw-group="a" data-mw='{"name":"references","attrs":{"group":"a"}}'><li about="#cite_note-1" id="cite_note-1"><a href="#cite_ref-1" data-mw-group="a" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">afoo</span></li> </ol> -<p>C <span about="#mwt8" class="reference" id="cite_ref-3" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-3"},"attrs":{}}'><a href="#cite_note-3">[2]</a></span></p> +<p>C <span about="#mwt8" class="mw-ref" id="cite_ref-3" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-3"},"attrs":{}}'><a href="#cite_note-3"><span class="mw-reflink-text">[2]</span></a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt10" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-2" id="cite_note-2"><span rel="mw:referencedBy"><a href="#cite_ref-2">↑</a></span> <span id="mw-reference-text-cite_note-2" class="mw-reference-text">bfoo</span></li><li about="#cite_note-3" id="cite_note-3"><span rel="mw:referencedBy"><a href="#cite_ref-3">↑</a></span> <span id="mw-reference-text-cite_note-3" class="mw-reference-text">cfoo</span></li> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt10" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-2" id="cite_note-2"><a href="#cite_ref-2" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-2" class="mw-reference-text">bfoo</span></li><li about="#cite_note-3" id="cite_note-3"><a href="#cite_ref-3" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-3" class="mw-reference-text">cfoo</span></li> </ol> !!end !!test References: 5. ref tags in references should be processed while ignoring all other content -!!options -parsoid !! wikitext A <ref name="a" /> B <ref name="b">bar</ref> @@ -20622,30 +21719,26 @@ B <ref name="b">bar</ref> <ref name="a">foo</ref> This should just get lost. </references> -!! html -<p>A <span about="#mwt2" class="reference" id="cite_ref-a_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"a"}}'><a href="#cite_note-a-1">[1]</a></span> -B <span about="#mwt4" class="reference" id="cite_ref-b_2-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-b-2"},"attrs":{"name":"b"}}'><a href="#cite_note-b-2">[2]</a></span></p> +!! html/parsoid +<p>A <span about="#mwt2" class="mw-ref" id="cite_ref-a_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"name":"a"}}'><a href="#cite_note-a-1"><span class="mw-reflink-text">[1]</span></a></span> +B <span about="#mwt4" class="mw-ref" id="cite_ref-b_2-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-b-2"},"attrs":{"name":"b"}}'><a href="#cite_note-b-2"><span class="mw-reflink-text">[2]</span></a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","body":{"extsrc":"<ref name=\"a\">foo</ref>\nThis should just get lost.","html":"\n<span about=\"#mwt8\" class=\"reference\" rel=\"dc:references\" typeof=\"mw:Extension/ref\" data-parsoid='{\"dsr\":[59,82,14,6]}' data-mw='{\"name\":\"ref\",\"body\":{\"id\":\"mw-reference-text-cite_note-a-1\"},\"attrs\":{\"name\":\"a\"}}'><a href=\"#cite_note-a-1\">[1]</a></span>\n"},"attrs":{}}'><li about="#cite_note-a-1" id="cite_note-a-1"><span rel="mw:referencedBy"><a href="#cite_ref-a_1-0">↑</a></span> <span id="mw-reference-text-cite_note-a-1" class="mw-reference-text">foo</span></li><li about="#cite_note-b-2" id="cite_note-b-2"><span rel="mw:referencedBy"><a href="#cite_ref-b_2-0">↑</a></span> <span id="mw-reference-text-cite_note-b-2" class="mw-reference-text">bar</span></li> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","body":{"extsrc":"<ref name=\"a\">foo</ref>\nThis should just get lost.","html":"\n<span about=\"#mwt8\" class=\"mw-ref\" rel=\"dc:references\" typeof=\"mw:Extension/ref\" data-parsoid='{\"dsr\":[59,82,14,6]}' data-mw='{\"name\":\"ref\",\"body\":{\"id\":\"mw-reference-text-cite_note-a-1\"},\"attrs\":{\"name\":\"a\"}}'><a href=\"#cite_note-a-1\" style=\"counter-reset: mw-Ref 1;\"><span class=\"mw-reflink-text\">[1]</span></a></span>\n"},"attrs":{}}'><li about="#cite_note-a-1" id="cite_note-a-1"><a href="#cite_ref-a_1-0" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-a-1" class="mw-reference-text">foo</span></li><li about="#cite_note-b-2" id="cite_note-b-2"><a href="#cite_ref-b_2-0" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-b-2" class="mw-reference-text">bar</span></li> </ol> !!end !!test References: 6. <references /> from a transclusion -!!options -parsoid !! wikitext <ref>Foo</ref> {{echo|<references />}} -!! html -<p><span about="#mwt3" 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></p> <ol class="references" typeof="mw:Extension/references mw:Transclusion" about="#mwt4" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"<references />"}},"i":0}}]}'><li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">Foo</span></li> +!! html/parsoid +<p><span about="#mwt3" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span></p> <ol class="mw-references" typeof="mw:Extension/references mw:Transclusion" about="#mwt4" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"<references />"}},"i":0}}]}'><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">Foo</span></li> </ol> !!end !! test References: 7. Multiple references tags (one without and one with nested refs) should be correctly handled -!! options -parsoid !! wikitext A <ref>foo bar for a</ref> B <ref group="X" name="b" /> @@ -20655,30 +21748,28 @@ B <ref group="X" name="b" /> <references group="X"> <ref name="b">foo</ref> </references> -!! html -<p>A <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> -B <span about="#mwt4" class="reference" id="cite_ref-b_2-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"group":"X","name":"b"}}'><a href="#cite_note-b-2">[X 1]</a></span> +!! html/parsoid +<p>A <span about="#mwt2" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span> +B <span about="#mwt4" class="mw-ref" id="cite_ref-b_2-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","attrs":{"group":"X","name":"b"}}'><a href="#cite_note-b-2" data-mw-group="X"><span class="mw-reflink-text">[X 1]</span></a></span> </p> -<ol class="references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo bar for a</span></li> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt6" 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">foo bar for a</span></li> </ol> -<ol class="references" typeof="mw:Extension/references" about="#mwt8" data-mw='{"name":"references","body":{"extsrc":"<ref name=\"b\">foo</ref>","html":"\n<span about=\"#mwt10\" class=\"reference\" rel=\"dc:references\" typeof=\"mw:Extension/ref\" data-parsoid='{\"dsr\":[96,119,14,6]}' data-mw='{\"name\":\"ref\",\"body\":{\"id\":\"mw-reference-text-cite_note-b-2\"},\"attrs\":{\"name\":\"b\"}}'><a href=\"#cite_note-b-2\">[X 1]</a></span>\n"},"attrs":{"group":"X"}}'> -<li about="#cite_note-b-2" id="cite_note-b-2"><span rel="mw:referencedBy"><a href="#cite_ref-b_2-0">↑</a></span> <span id="mw-reference-text-cite_note-b-2" class="mw-reference-text">foo</span></li> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt8" data-mw-group="X" data-mw='{"name":"references","body":{"extsrc":"<ref name=\"b\">foo</ref>","html":"\n<span about=\"#mwt10\" class=\"mw-ref\" rel=\"dc:references\" typeof=\"mw:Extension/ref\" data-parsoid='{\"dsr\":[96,119,14,6]}' data-mw='{\"name\":\"ref\",\"body\":{\"id\":\"mw-reference-text-cite_note-b-2\"},\"attrs\":{\"name\":\"b\"}}'><a href=\"#cite_note-b-2\" style=\"counter-reset: mw-Ref 1;\" data-mw-group=\"X\"><span class=\"mw-reflink-text\">[X 1]</span></a></span>\n"},"attrs":{"group":"X"}}'> +<li about="#cite_note-b-2" id="cite_note-b-2"><a href="#cite_ref-b_2-0" data-mw-group="X" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-b-2" class="mw-reference-text">foo</span></li> </ol> !! end !! test References: 8. T88019: Remove <meta>s from templates inside <ref> that's itself inside a template -!! options -parsoid !! wikitext X{{echo|<ref>foo {{echo|<b>bar</b>}} and {{echo|baz}} boo</ref>}} <references /> -!! html -<p>X<span about="#mwt2" class="reference" id="cite_ref-1" rel="dc:references" typeof="mw:Transclusion mw:Extension/ref" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"<ref>foo {{echo|<b>bar</b>}} and {{echo|baz}} boo</ref>"}},"i":0}}]}'><a href="#cite_note-1">[1]</a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt7" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo <b data-parsoid='{"stx":"html"}'>bar</b> and baz boo</span></li> +!! html/parsoid +<p>X<span about="#mwt2" class="mw-ref" id="cite_ref-1" rel="dc:references" typeof="mw:Transclusion mw:Extension/ref" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"<ref>foo {{echo|<b>bar</b>}} and {{echo|baz}} boo</ref>"}},"i":0}}]}'><a href="#cite_note-1"><span class="mw-reflink-text">[1]</span></a></span></p> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt7" 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">foo <b data-parsoid='{"stx":"html"}'>bar</b> and baz boo</span></li> </ol> !!end @@ -20688,18 +21779,16 @@ X{{echo|<ref>foo {{echo|<b>bar</b>}} and {{echo|baz}} boo</ref>}} # wt2wt. !! test References: 9. Generate missing references list at the end -!! options -parsoid !! wikitext A <ref>foo</ref> B <ref group="inexistent">bar</ref> -!! html -<p>A <span 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> B <span class="reference" id="cite_ref-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-2"},"attrs":{"group":"inexistent"}}'><a href="#cite_note-2">[inexistent 1]</a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt5" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo</span></li> +!! html/parsoid +<p>A <span class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span> B <span class="mw-ref" id="cite_ref-2" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-2"},"attrs":{"group":"inexistent"}}'><a href="#cite_note-2" data-mw-group="inexistent"><span class="mw-reflink-text">[inexistent 1]</span></a></span></p> +<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">foo</span></li> </ol> -<ol class="references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{"group":"inexistent"}}'> -<li about="#cite_note-2" id="cite_note-2"><span rel="mw:referencedBy"><a href="#cite_ref-2">↑</a></span> <span id="mw-reference-text-cite_note-2" class="mw-reference-text">bar</span></li> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt6" data-mw-group="inexistent" data-mw='{"name":"references","attrs":{"group":"inexistent"}}'> +<li about="#cite_note-2" id="cite_note-2"><a href="#cite_ref-2" data-mw-group="inexistent" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-2" class="mw-reference-text">bar</span></li> </ol> !! end @@ -20721,15 +21810,13 @@ A <ref>foo</ref> !! test Entities in ref name -!! options -parsoid !! wikitext <ref name="test & me">hi</ref> <references /> -!! html -<p><span about="#mwt2" class="reference" id="cite_ref-test_.26_me_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-test_.26_me-1"},"attrs":{"name":"test &amp; me"}}'><a href="#cite_note-test_.26_me-1">[1]</a></span></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-test_.26_me-1" id="cite_note-test_.26_me-1"><span rel="mw:referencedBy"><a href="#cite_ref-test_.26_me_1-0">↑</a></span> <span id="mw-reference-text-cite_note-test_.26_me-1" class="mw-reference-text">hi</span></li> +!! html/parsoid +<p><span about="#mwt2" class="mw-ref" id="cite_ref-test_.26_me_1-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-test_.26_me-1"},"attrs":{"name":"test &amp; me"}}'><a href="#cite_note-test_.26_me-1"><span class="mw-reflink-text">[1]</span></a></span></p> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'> +<li about="#cite_note-test_.26_me-1" id="cite_note-test_.26_me-1"><a href="#cite_ref-test_.26_me_1-0" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-test_.26_me-1" class="mw-reference-text">hi</span></li> </ol> !! end @@ -20743,10 +21830,10 @@ parsoid=wt2html a<ref>foo</ref> <references> -!! html -<p>a<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></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo</span></li></ol> +!! html/parsoid +<p>a<span about="#mwt2" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span></p> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt4" 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">foo</span></li></ol> !! end !! test @@ -20756,8 +21843,8 @@ parsoid=wt2wt,html2wt !! wikitext foo <references /> -!! html -foo<ol class="references" typeof="mw:Extension/references" about="#mwt2" data-mw='{"name":"references","attrs":{}}'></ol> +!! html/parsoid +foo<ol class="mw-references" typeof="mw:Extension/references" about="#mwt2" data-mw='{"name":"references","attrs":{}}'></ol> !! end #### ---------------------------------------------------------------- @@ -20864,23 +21951,23 @@ Empty TR nodes should not be stripped if they have any attributes set !! test Headings: 0. Unnested !! options -parsoid +parsoid=html2wt +!! html/parsoid +<p>=foo=</p> + +<p> =foo= +<!--cmt--> +=foo=</p> + +<p>=foo<i>a</i>=</p> !! wikitext <nowiki>=foo=</nowiki> -<nowiki> =foo= </nowiki> +<nowiki> </nowiki>=foo= <!--cmt--> <nowiki>=foo=</nowiki> =foo''a''<nowiki>=</nowiki> -!! html -<p><span typeof="mw:Nowiki">=foo=</span></p> - -<p><span typeof="mw:Nowiki"> =foo= </span> -<!--cmt--> -<span typeof="mw:Nowiki">=foo=</span></p> - -<p>=foo<i>a</i><span typeof="mw:Nowiki">=</span></p> !!end # New headings and existing headings are handled differently @@ -20888,7 +21975,7 @@ parsoid Headings: 1. Nested inside html !! options parsoid=html2wt -!! html +!! html/parsoid <h1>=foo=</h1> <h2>=foo=</h2> <h3>=foo=</h3> @@ -20919,7 +22006,7 @@ parsoid=html2wt Headings: 2. Outside heading nest on a single line <h1>foo</h1>*bar !! options parsoid=html2wt -!! html +!! html/parsoid <h1>foo</h1>*bar <h1>foo</h1>=bar <h1>foo</h1>=bar= @@ -20937,15 +22024,26 @@ parsoid=html2wt !! test Headings: 3. Nested inside html with wikitext split by html tags !! options -parsoid=html2wt,wt2wt -!! wikitext -= ='''bold'''<nowiki>foo=</nowiki> = +parsoid=html2wt !! html/parsoid <h1>=<b>bold</b>foo=</h1> +!! wikitext += ='''bold'''<nowiki>foo=</nowiki> = !!end !! test Headings: 4a. No escaping needed (testing just h1 and h2) +!! options +parsoid=html2wt +!! html/parsoid +<h1>=foo</h1> +<h1>foo=</h1> +<h1> =foo= </h1> +<h1>=foo= bar</h1> +<h2>=foo</h2> +<h2>foo=</h2> +<h1>=</h1> +<h1><i>=</i>foo=</h1> !! wikitext = =foo = @@ -20962,22 +22060,13 @@ Headings: 4a. No escaping needed (testing just h1 and h2) = = = = ''=''foo= = -!! html/parsoid -<h1>=foo</h1> -<h1>foo=</h1> -<h1> =foo= </h1> -<h1>=foo= bar</h1> -<h2>=foo</h2> -<h2>foo=</h2> -<h1>=</h1> -<h1><i>=</i>foo=</h1> !!end !! test Headings: 4b. No escaping needed (inside p-tags) !! options parsoid=html2wt -!! html +!! html/parsoid <p>=== =foo= x =foo= <s></s> @@ -20991,7 +22080,19 @@ parsoid=html2wt !! test Headings: 5. Empty headings !! options -parsoid +parsoid=html2wt +!! html/parsoid +<h1 data-parsoid='{}'></h1> + +<h2 data-parsoid='{}'></h2> + +<h3 data-parsoid='{}'></h3> + +<h4 data-parsoid='{}'></h4> + +<h5 data-parsoid='{}'></h5> + +<h6 data-parsoid='{}'></h6> !! wikitext =<nowiki/>= @@ -21004,92 +22105,81 @@ parsoid =====<nowiki/>===== ======<nowiki/>====== -!! html -<h1></h1> -<h2></h2> -<h3></h3> -<h4></h4> -<h5></h5> -<h6></h6> !!end !! test Headings: 6a. Heading chars in SOL context (with trailing spaces) !! options -parsoid +parsoid=html2wt +!! html/parsoid +<p>=a=</p> + +<p>=a=</p> + +<p>=a=</p> !! wikitext <nowiki>=a=</nowiki> <nowiki>=a=</nowiki> <nowiki>=a=</nowiki> - -<nowiki>=a=</nowiki> -!! html -<p>=a=</p> -<p>=a= </p> -<p>=a= </p> -<p>=a= </p> !!end !! test Headings: 6b. Heading chars in SOL context (with trailing newlines) !! options -parsoid -!! wikitext -<nowiki>=a= -b</nowiki> - -<nowiki>=a= -b</nowiki> - -<nowiki>=a= -b</nowiki> - -<nowiki>=a= -b</nowiki> -!! html +parsoid=html2wt +!! html/parsoid <p>=a= b</p> + <p>=a= b</p> + <p>=a= b</p> -<p>=a= -b</p> -</p> +!! wikitext +<nowiki>=a=</nowiki> +b + +<nowiki>=a=</nowiki> +b + +<nowiki>=a=</nowiki> +b !!end !! test Headings: 6c. Heading chars in SOL context (leading newline break) !! options -parsoid +parsoid=html2wt +!! html/parsoid +<p>a +=b=</p> !! wikitext a <nowiki>=b=</nowiki> -!! html -<p>a -=b=</p> !!end !! test Headings: 6d. Heading chars in SOL context (with interspersed comments) !! options -parsoid +parsoid=html2wt +!! html/parsoid +<!--c0--><p>=a=</p> + +<!--c1--><p>=a=</p> <!--c2--> <!--c3--> !! wikitext <!--c0--><nowiki>=a=</nowiki> <!--c1--><nowiki>=a=</nowiki> <!--c2--> <!--c3--> -!! html -<p><!--c0-->=a=</p> -<p><!--c1-->=a= <!--c2--> <!--c3--></p> !!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> @@ -21099,11 +22189,11 @@ parsoid=html2wt 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 @@ -21126,21 +22216,36 @@ a !! test Lists: 0. Outside nests +!! options +parsoid=html2wt +!! html/parsoid +<p>*foo</p> + +<p>#foo</p> + +<p>;Foo:bar</p> !! wikitext <nowiki>*</nowiki>foo <nowiki>#</nowiki>foo -<nowiki>;Foo:</nowiki>bar -!! html -<p>*foo -</p><p>#foo -</p><p>;Foo:bar -</p> +<nowiki>;</nowiki>Foo<nowiki>:</nowiki>bar !!end !! test Lists: 1. Nested inside html +!! options +parsoid=html2wt +!! html/parsoid +<ul><li>*foo</li></ul> +<ul><li>#foo</li></ul> +<ul><li>:foo</li></ul> +<ul><li>;foo</li></ul> +<ol><li>*foo</li></ol> +<ol><li>#foo</li></ol> +<ol><li>:foo</li></ol> +<ol><li>;foo</li></ol> + !! wikitext *<nowiki>*foo</nowiki> @@ -21157,20 +22262,19 @@ Lists: 1. Nested inside html #<nowiki>:foo</nowiki> #<nowiki>;foo</nowiki> -!! html -<ul><li>*foo</li></ul> -<ul><li>#foo</li></ul> -<ul><li>:foo</li></ul> -<ul><li>;foo</li></ul> -<ol><li>*foo</li></ol> -<ol><li>#foo</li></ol> -<ol><li>:foo</li></ol> -<ol><li>;foo</li></ol> - !!end !! test Lists: 2. Inside definition lists +!! options +parsoid=html2wt +!! html/parsoid +<dl><dt>;foo</dt></dl> +<dl><dt>:foo</dt></dl> +<dl><dt>:foo</dt> +<dd>bar</dd></dl> +<dl><dd>:foo</dd></dl> + !! wikitext ;<nowiki>;foo</nowiki> @@ -21180,40 +22284,27 @@ Lists: 2. Inside definition lists :bar :<nowiki>:foo</nowiki> -!! html -<dl><dt>;foo</dt></dl> -<dl><dt>:foo</dt></dl> -<dl><dt>:foo</dt> -<dd>bar</dd></dl> -<dl><dd>:foo</dd></dl> - !!end !! test Lists: 3. Only bullets at start of text should be escaped +!! options +parsoid=html2wt +!! html/parsoid +<ul><li>*foo*bar</li></ul> +<ul><li>*foo<i>it</i>*bar</li></ul> + !! wikitext *<nowiki>*foo*bar</nowiki> *<nowiki>*foo</nowiki>''it''*bar -!! html -<ul><li>*foo*bar</li></ul> -<ul><li>*foo<i>it</i>*bar</li></ul> - !!end !! test Lists: 4. No escapes needed !! options -parsoid -!! wikitext -*foo*bar - -*''foo''*bar - -*[[Foo]]: bar - -*[[Foo]]*bar -!! html +parsoid=html2wt +!! html/parsoid <ul> <li>foo*bar </li> @@ -21230,10 +22321,29 @@ parsoid <li><a rel="mw:WikiLink" href="Foo" title="Foo">Foo</a>*bar </li> </ul> +!! wikitext +*foo*bar + +*''foo''*bar + +*[[Foo]]: bar + +*[[Foo]]*bar !!end !! test Lists: 5. No unnecessary escapes +!! options +parsoid=html2wt +!! html/parsoid +<ul><li> bar <span>[[foo]]</span></li></ul> +<ul><li> =bar <span>[[foo]]</span></li></ul> +<ul><li> [[bar <span>[[foo]]</span></li></ul> +<ul><li> ]]bar <span>[[foo]]</span></li></ul> +<ul><li> =bar <span>foo]]</span>=</li></ul> +<ul><li> <s></s>: a</li></ul> +<ul><li> <i>* foo</i></li></ul> + !! wikitext * bar <span><nowiki>[[foo]]</nowiki></span> @@ -21248,22 +22358,13 @@ Lists: 5. No unnecessary escapes * <s></s>: a * ''* foo'' -!! html -<ul><li> bar <span>[[foo]]</span></li></ul> -<ul><li> =bar <span>[[foo]]</span></li></ul> -<ul><li> [[bar <span>[[foo]]</span></li></ul> -<ul><li> ]]bar <span>[[foo]]</span></li></ul> -<ul><li> =bar <span>foo]]</span>=</li></ul> -<ul><li> <s></s>: a</li></ul> -<ul><li> <i>* foo</i></li></ul> - !!end !! test Lists: 6. Escape bullets in SOL position !! options parsoid=html2wt -!! html +!! html/parsoid <p><!--cmt-->*foo</p> !! wikitext <!--cmt--><nowiki>*</nowiki>foo @@ -21271,20 +22372,22 @@ parsoid=html2wt !! test Lists: 7. Escape bullets in a multi-line context -!! wikitext -a -<nowiki>*</nowiki>b -!! html +!! options +parsoid=html2wt +!! html/parsoid <p>a *b </p> +!! wikitext +a +<nowiki>*</nowiki>b !!end !! test Lists: 8. Escape colons only if not present in tags !! options parsoid=html2wt -!! html +!! html/parsoid <dl><dt>a:b<i>c:d</i></dt></dl> !! wikitext ; <nowiki>a:b</nowiki>''c:d'' @@ -21296,17 +22399,16 @@ parsoid=html2wt !! test HRs: 1. Single line +!! options +parsoid=html2wt +!! html/parsoid +<hr />---- +<hr />=foo= +<hr />*foo !! wikitext ----<nowiki>----</nowiki> ----=foo= ----*foo -!! html+tidy -<hr /> -<p>----</p> -<hr /> -<p>=foo=</p> -<hr /> -<p>*foo</p> !! end #### --------------- Tables --------------- @@ -21330,40 +22432,48 @@ HRs: 1. Single line !! test Tables: 1a. Simple example -!! wikitext -<nowiki>{| -|}</nowiki> -!! html +!! options +parsoid=html2wt +!! html/parsoid <p>{| |} </p> +!! wikitext +<nowiki>{|</nowiki> +|} !! end !! test Tables: 1b. No escaping needed -!! wikitext -!foo -!! html +!! options +parsoid=html2wt +!! html/parsoid <p>!foo </p> +!! wikitext +!foo !! end !! test Tables: 1c. No escaping needed -!! wikitext -|foo -!! html +!! options +parsoid=html2wt +!! html/parsoid <p>|foo </p> +!! wikitext +|foo !! end !! test Tables: 1d. No escaping needed -!! wikitext -|}foo -!! html +!! options +parsoid=html2wt +!! html/parsoid <p>|}foo </p> +!! wikitext +|}foo !! end !! test @@ -21424,11 +22534,8 @@ parsoid=html2wt !! test Tables: 2c. Nested in td -- no escaping needed -!! wikitext -{| - -|foo!!bar -|} +!! options +parsoid=html2wt !! html/* <table> @@ -21436,15 +22543,17 @@ Tables: 2c. Nested in td -- no escaping needed <td>foo!!bar </td></tr></table> -!! end - -!! test -Tables: 3a. Nested in th !! wikitext {| -!foo!bar +|foo!!bar |} +!! end + +!! test +Tables: 3a. Nested in th +!! options +parsoid=html2wt !! html/* <table> @@ -21452,6 +22561,11 @@ Tables: 3a. Nested in th <th>foo!bar </th></tr></table> +!! wikitext +{| + +!foo!bar +|} !! end !! test @@ -21560,6 +22674,19 @@ parsoid=html2wt !! test Tables: 4c. No escaping needed +!! options +parsoid=html2wt +!! html/parsoid +<table><tbody> +<tr><td>foo-bar</td><td>foo+bar</td></tr> +<tr><td><i>foo</i>-bar</td><td><i>foo</i>+bar</td></tr> +<tr><td>foo +<p>bar|baz ++bar +-bar</p></td></tr> +<tr><td>x +<div>a|b</div></td> +</tbody></table> !! wikitext {| |foo-bar @@ -21600,21 +22727,18 @@ bar|baz <div>a|b</div> </td></tr></table> -!! html/parsoid -<table><tbody> -<tr><td>foo-bar</td><td>foo+bar</td></tr> -<tr><td><i>foo</i>-bar</td><td><i>foo</i>+bar</td></tr> -<tr><td>foo -<p>bar|baz -+bar --bar</p></td></tr> -<tr><td>x -<div>a|b</div></td> -</tbody></table> !! end !! test Tables: 4d. No escaping needed +!! options +parsoid=html2wt +!! html/parsoid +<table> +<tbody><tr><td><a rel="mw:WikiLink" href="./Foo" title="Foo">Foo</a>-bar</td> +<td data-parsoid='{"startTagSrc":"|","attrSepSrc":"|"}'>+1</td> +<td data-parsoid='{"startTagSrc":"|","attrSepSrc":"|"}'>-2</td></tr> +</tbody></table> !! wikitext {| |[[Foo]]-bar @@ -21631,29 +22755,42 @@ Tables: 4d. No escaping needed <td>-2 </td></tr></table> +!! end + +!! test +T97430: Don't emit empty nowiki pairs around marker meta tags +!! options +parsoid=html2wt !! html/parsoid -<table> -<tbody><tr><td><a rel="mw:WikiLink" href="./Foo" title="Foo">Foo</a>-bar</td> -<td data-parsoid='{"startTagSrc":"|","attrSepSrc":"|"}'>+1</td> -<td data-parsoid='{"startTagSrc":"|","attrSepSrc":"|"}'>-2</td></tr> -</tbody></table> +<p>*This is a long sentence here that will make the nowiki algo split up the nowikis into multiple pairs +|** Make this another long long long sentence forcing the nowiki algo to split up the nowikis.</p> +!! wikitext +<nowiki>*</nowiki>This is a long sentence here that will make the nowiki algo split up the nowikis into multiple pairs +|** Make this another long long long sentence forcing the nowiki algo to split up the nowikis. !! end !! test -Tables: Digest broken attributes on table and tr tag +Unclosed xmlish element in table line shouldn't eat end delimiters !! options -parsoid=wt2html +parsoid=html2wt +!! html/parsoid +<table> +<tbody><tr><td> <foo</td> +<td> bar></td></tr> +</tbody></table> !! wikitext -{| || |} ++ -|- || || ++ -- -|- > [ +{| +| <foo +| bar> |} -!! html +!! html/php <table> -<tbody> -<tr></tr> -<tr></tr> -</tbody></table> +<tr> +<td> <foo +</td> +<td> bar> +</td></tr></table> + !! end #### --------------- Links ---------------- @@ -21665,6 +22802,12 @@ parsoid=wt2html #### -------------------------------------- !! test Links 1. WikiLinks: No escapes needed +!! options +parsoid=html2wt +!! html/parsoid +<p><a rel="mw:WikiLink" href="Foo" title="Foo">Foo<i>boo</i></a> +<a rel="mw:WikiLink" href="Foo" title="Foo">[Foobar]</a> +<a rel="mw:WikiLink" href="Foo" title="Foo">x [Foobar] x</a></p> !! wikitext [[Foo|Foo''boo'']] [[Foo|[Foobar]]] @@ -21674,10 +22817,6 @@ Links 1. WikiLinks: No escapes needed <a href="/wiki/Foo" title="Foo">[Foobar]</a> <a href="/wiki/Foo" title="Foo">x [Foobar] x</a> </p> -!! html/parsoid -<p><a rel="mw:WikiLink" href="Foo" title="Foo">Foo<i>boo</i></a> -<a rel="mw:WikiLink" href="Foo" title="Foo">[Foobar]</a> -<a rel="mw:WikiLink" href="Foo" title="Foo">x [Foobar] x</a></p> !! end !! test @@ -21722,6 +22861,11 @@ parsoid=html2wt !! test Links 3. WikiLinks: No escapes needed +!! options +parsoid=html2wt +!! html/parsoid +<p><a rel="mw:WikiLink" href="Foo">[Foobar</a> +<a rel="mw:WikiLink" href="Foo" title="Foo">foo|bar</a></p> !! wikitext [[Foo|[Foobar]] [[Foo|foo|bar]] @@ -21729,9 +22873,6 @@ Links 3. WikiLinks: No escapes needed <p><a href="/wiki/Foo" title="Foo">[Foobar</a> <a href="/wiki/Foo" title="Foo">foo|bar</a> </p> -!! html/parsoid -<p><a rel="mw:WikiLink" href="Foo">[Foobar</a> -<a rel="mw:WikiLink" href="Foo" title="Foo">foo|bar</a></p> !! end !! test @@ -21761,17 +22902,21 @@ parsoid=html2wt !! test Links 5. ExtLinks: No escapes needed +!! options +parsoid=html2wt +!! html/parsoid +<p><a rel="mw:ExtLink" href="http://google.com">[google</a></p> !! wikitext [http://google.com [google] !! html/php <p><a rel="nofollow" class="external text" href="http://google.com">[google</a> </p> -!! html/parsoid -<p><a rel="mw:ExtLink" href="http://google.com">[google</a></p> !! end !! test Links 6. Add <nowiki/>s between text-nodes and url-links when required (bug 64300) +!! options +parsoid=html2wt !! html/parsoid <p>x<a rel="mw:ExtLink" href="http://example.com" data-parsoid='{"stx":"url"}'>http://example.com</a>y <a rel="mw:ExtLink" href="http://example.com" data-parsoid='{"stx":"url"}'>http://example.com</a>?x @@ -21805,6 +22950,8 @@ http://example.com(x<nowiki/>) !! test Links 7a. Don't add spurious <nowiki/>s between text-nodes and url-links (bug 64300) +!! options +parsoid=html2wt !! html/parsoid <p>x <a rel="mw:ExtLink" href="http://example.com" data-parsoid='{"stx":"url"}'>http://example.com</a> @@ -21838,6 +22985,8 @@ y !! test Links 7b. Don't add spurious <nowiki/>s between text-nodes and url-links (bug 64300) +!! options +parsoid=html2wt !! html/parsoid <p><a rel="mw:ExtLink" href="http://example.com" data-parsoid='{"stx":"url"}'>http://example.com</a>.,;:!?\ -<a rel="mw:ExtLink" href="http://example.com">http://example.com</a>:</p> @@ -21852,6 +23001,8 @@ http://example.com.,;:!?\ !! test Links 8. Add <nowiki/>s between text-nodes and RFC-links when required (bug 64300) +!! options +parsoid=html2wt !! html/parsoid <p><a href="//tools.ietf.org/html/rfc123" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>RFC 123</a>4 <a href="//tools.ietf.org/html/rfc123" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>RFC 123</a>y @@ -21864,6 +23015,8 @@ X<nowiki/>RFC 123<nowiki/>y !! test Links 9. Don't add spurious <nowiki/>s between text-nodes and RFC-links (bug 64300) +!! options +parsoid=html2wt !! html/parsoid <p><a href="//tools.ietf.org/html/rfc123" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>RFC 123</a>?foo <a href="//tools.ietf.org/html/rfc123" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>RFC 123</a>&foo @@ -21882,6 +23035,8 @@ RFC 123&foo !! test Links 10. Add <nowiki/>s between text-nodes and PMID-links when required (bug 64300) +!! options +parsoid=html2wt !! html/parsoid <p><a href="//www.ncbi.nlm.nih.gov/pubmed/123?dopt=Abstract" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>PMID 123</a>4 <a href="//www.ncbi.nlm.nih.gov/pubmed/123?dopt=Abstract" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>PMID 123</a>y @@ -21894,6 +23049,8 @@ X<nowiki/>PMID 123<nowiki/>y !! test Links 11. Don't add spurious <nowiki/>s between text-nodes and PMID-links (bug 64300) +!! options +parsoid=html2wt !! html/parsoid <p><a href="//www.ncbi.nlm.nih.gov/pubmed/123?dopt=Abstract" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>PMID 123</a>?foo <a href="//www.ncbi.nlm.nih.gov/pubmed/123?dopt=Abstract" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>PMID 123</a>&foo @@ -21912,10 +23069,12 @@ PMID 123&foo !! test Links 12. Add <nowiki/>s between text-nodes and ISBN-links when required (bug 64300) +!! options +parsoid=html2wt !! html/parsoid -<p><a href="./Special:BookSources/1234567890" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>ISBN 1234567890</a>1 -<a href="./Special:BookSources/1234567890" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>ISBN 1234567890</a>x -a<a href="./Special:BookSources/1234567890" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>ISBN 1234567890</a>b +<p><a href="./Special:BookSources/1234567890" rel="mw:WikiLink" data-parsoid='{"stx":"magiclink"}'>ISBN 1234567890</a>1 +<a href="./Special:BookSources/1234567890" rel="mw:WikiLink" data-parsoid='{"stx":"magiclink"}'>ISBN 1234567890</a>x +a<a href="./Special:BookSources/1234567890" rel="mw:WikiLink" data-parsoid='{"stx":"magiclink"}'>ISBN 1234567890</a>b </p> !! wikitext ISBN 1234567890<nowiki/>1 @@ -21925,8 +23084,10 @@ a<nowiki/>ISBN 1234567890<nowiki/>b !! test Links 13. Don't add spurious <nowiki/>s between text-nodes and ISBN-links (bug 64300) +!! options +parsoid=html2wt !! html/parsoid -<p>-<a href="./Special:BookSources/1234567890" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>ISBN 1234567890</a>'s +<p>-<a href="./Special:BookSources/1234567890" rel="mw:WikiLink" data-parsoid='{"stx":"magiclink"}'>ISBN 1234567890</a>'s !! wikitext -ISBN 1234567890's !! html/php @@ -21942,20 +23103,21 @@ parsoid=html2wt <p>this is not a link: http://example.com </p> !! wikitext -this is not a link: <nowiki>http://example.com</nowiki> +<nowiki>this is not a link: http://example.com</nowiki> !! end !! test Links 15. Link trails can't become link prefixes. !! options language=is +parsoid=html2wt +!! html/parsoid +<p><a rel="mw:WikiLink" href="Söfnuður" title="Söfnuður" data-parsoid='{"stx":"simple","tail":"-"}'>Söfnuður-</a><a rel="mw:WikiLink" href="00" title="00">00</a></p> !! wikitext [[Söfnuður]]-[[00]] !! html/php <p><a href="/wiki/S%C3%B6fnu%C3%B0ur" title="Söfnuður">Söfnuður-</a><a href="/wiki/00" title="00">00</a> </p> -!! html/parsoid -<p><a rel="mw:WikiLink" href="Söfnuður" title="Söfnuður" data-parsoid='{"stx":"simple","tail":"-"}'>Söfnuður-</a><a rel="mw:WikiLink" href="00" title="00">00</a></p> !! end #### --------------- Quotes --------------- @@ -21967,28 +23129,7 @@ language=is !! test 1a. Quotes inside <b> and <i> !! options -parsoid=html2wt,wt2wt -!! wikitext -''<nowiki/>'foo''' -''<nowiki>''foo''</nowiki>'' -''<nowiki>'''foo'''</nowiki>'' -''foo''<nowiki/>'s -'''<nowiki/>'foo'''' -'''<nowiki>''foo''</nowiki>''' -'''<nowiki>'''foo'''</nowiki>''' -'''foo'<nowiki/>''bar'<nowiki/>''baz''' -'''foo'''<nowiki/>'s -'''foo'' -''foo''<nowiki/>' -''foo'''<nowiki/>' -'''foo''<nowiki/>' -''''foo''' -'''foo'''<nowiki/>' -''''foo'''<nowiki/>' -''fools'<span> errand</span>'' -''<span>fool</span>'s errand'' -'<nowiki/>''foo'' bar '''baz'' -a|!*#-:;+-~[]{}b'''x'' +parsoid=html2wt !! html/* <p><i>'foo'</i> <i>''foo''</i> @@ -22011,12 +23152,44 @@ a|!*#-:;+-~[]{}b'''x'' '<i>foo</i> bar '<i>baz</i> a|!*#-:;+-~[]{}b'<i>x</i> </p> +!! wikitext +''<nowiki/>'foo''' +''<nowiki>''foo''</nowiki>'' +''<nowiki>'''foo'''</nowiki>'' +''foo''<nowiki/>'s +'''<nowiki/>'foo'''' +'''<nowiki>''foo''</nowiki>''' +'''<nowiki>'''foo'''</nowiki>''' +'''foo'<nowiki/>''bar'<nowiki/>''baz''' +'''foo'''<nowiki/>'s +'''foo'' +''foo''<nowiki/>' +''foo'''<nowiki/>' +'''foo''<nowiki/>' +''''foo''' +'''foo'''<nowiki/>' +''''foo'''<nowiki/>' +''fools'<span> errand</span>'' +''<span>fool</span>'s errand'' +'<nowiki/>''foo'' bar '''baz'' +a|!*#-:;+-~[]{}b'''x'' !! end !! test 1b. Quotes inside <b> and <i> with other tags on same line !! options -parsoid=html2wt,wt2wt +parsoid=html2wt +!! html/parsoid +'<i>a</i> foo <i><a rel="mw:WikiLink" href="Bar" title="Bar">bar</a></i> +<i>a'</i> foo <i><a rel="mw:WikiLink" href="Bar" title="Bar">bar</a></i> +<i>a'</i> foo <b><a rel="mw:WikiLink" href="Bar" title="Bar" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[[bar]]"}},"i":0}}]}'>bar</a></b> +<a rel="mw:WikiLink" href="Foo" title="Foo">foo</a> x'<i><a href="Bar" rel="mw:WikiLink" title="Bar">bar</a></i> +'<i>foo</i> <span class="mw-ref" id="cite_ref-1-0" 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"><span class="mw-reflink-text">[1]</span></a></span> +'<i>foo</i> <div title="name">test</div> +'<i>foo</i> and <br data-parsoid='{"stx":"html","noClose":true}'/> bar +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt5" data-mw='{"name":"references","attrs":{}}'> +<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text" data-parsoid="{}">test</span></li> +</ol> !! wikitext '''a'' foo ''[[bar]]'' ''a''' foo ''[[bar]]'' @@ -22026,56 +23199,49 @@ parsoid=html2wt,wt2wt '''foo'' <div title="name">test</div> '''foo'' and <br> bar <references /> -!! html -'<i>a</i> foo <i><a rel="mw:WikiLink" href="Bar" title="Bar">bar</a></i> -<i>a'</i> foo <i><a rel="mw:WikiLink" href="Bar" title="Bar">bar</a></i> -<i>a'</i> foo <b><a rel="mw:WikiLink" href="Bar" title="Bar" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[[bar]]"}},"i":0}}]}'>bar</a></b> -<a rel="mw:WikiLink" href="Foo" title="Foo">foo</a> x'<i><a href="Bar" rel="mw:WikiLink" title="Bar">bar</a></i> -'<i>foo</i> <span class="reference" id="cite_ref-1-0" 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> -'<i>foo</i> <div title="name">test</div> -'<i>foo</i> and <br data-parsoid='{"stx":"html","noClose":true}'/> bar -<ol class="references" typeof="mw:Extension/references" about="#mwt5" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text" data-parsoid="{}">test</span></li> -</ol> !! end !! test 2. Link fragments separated by <i> and <b> tags +!! options +parsoid=html2wt +!! html/parsoid +<p>[[<i>foo</i>hello]]</p> +<p>[[<b>foo</b>hello]]</p> !! wikitext [[''foo''<nowiki>hello]]</nowiki> [['''foo'''<nowiki>hello]]</nowiki> -!! html -<p>[[<i>foo</i>hello]] -</p><p>[[<b>foo</b>hello]] -</p> !! end # FIXME: Escaping one or both of [[ and ]] is also acceptable -- # this is one of the shortcomings of this format !! test 3. Link fragments inside <i> and <b> +!! options +parsoid=html2wt +!! html/parsoid +<p><i>[[foo</i>]]</p> +<p><b>[[foo</b>]]</p> !! wikitext ''[[foo''<nowiki>]]</nowiki> '''[[foo'''<nowiki>]]</nowiki> -!! html -<p><i>[[foo</i>]] -</p><p><b>[[foo</b>]] -</p> !! end !! test 4. No escaping needed -!! wikitext -'<span>''bar''</span>' -'<span>'''bar'''</span>' -'a:b'foo -!! html +!! options +options=html2wt +!! html/parsoid <p>'<span><i>bar</i></span>' '<span><b>bar</b></span>' 'a:b'foo </p> +!! wikitext +'<span>''bar''</span>' +'<span>'''bar'''</span>' +'a:b'foo !! end #### ----------- Paragraphs --------------- @@ -22084,6 +23250,15 @@ parsoid=html2wt,wt2wt !! test 1. No unnecessary escapes +!! options +parsoid=html2wt +!! html/parsoid +<p>bar <span>[[foo]]</span> +</p><p>=bar <span>[[foo]]</span> +</p><p>[[bar <span>[[foo]]</span> +</p><p>]]bar <span>[[foo]]</span> +</p><p>=bar <span>foo]]</span>= +</p> !! wikitext bar <span><nowiki>[[foo]]</nowiki></span> @@ -22094,13 +23269,6 @@ bar <span><nowiki>[[foo]]</nowiki></span> ]]bar <span><nowiki>[[foo]]</nowiki></span> =bar <span>foo]]</span><nowiki>=</nowiki> -!! html -<p>bar <span>[[foo]]</span> -</p><p>=bar <span>[[foo]]</span> -</p><p>[[bar <span>[[foo]]</span> -</p><p>]]bar <span>[[foo]]</span> -</p><p>=bar <span>foo]]</span>= -</p> !!end #### ----------------------- PRE -------------------------- @@ -22109,101 +23277,136 @@ bar <span><nowiki>[[foo]]</nowiki></span> !! test 1. Leading whitespace in SOL context should be escaped !! options -parsoid +parsoid=html2wt +!! html/parsoid +<p> a</p> + +<p> a</p> + +<p> a(tab)</p> + +<p> a +<!--cmt--> + a</p> + +<p>a + b</p> + +<p>a + b</p> + +<p>a + b</p> !! wikitext <nowiki> </nowiki>a <nowiki> </nowiki> a -<nowiki> </nowiki>a(tab) + a(tab) <nowiki> </nowiki> a <!--cmt--> -<nowiki> </nowiki> a +<nowiki> </nowiki>a a <nowiki> </nowiki>b a -<nowiki> </nowiki>b + b a -<nowiki> </nowiki> b -!! html -<p> a</p> -<p> a</p> -<p> a(tab)</p> -<p> a</p> -<p><!--cmt--> a</p> -<p>a - b</p> -<p>a - b</p> -<p>a - b</p> + b +!! html/php +<p> a +</p><p> a +</p><p> a(tab) +</p><p> a + a +</p><p>a + b +</p><p>a + b +</p><p>a + b +</p> !! end !! test 2. Leading whitespace in non-indent-pre contexts should not be escaped !! options -parsoid +parsoid=htm2wt +!! html/parsoid +<p>foo <span about="#mwt2" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span></p> +<ol class="mw-references" typeof="mw:Extension/references" about="#mwt4" 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"><i data-parsoid='{"dsr":[9,14,2,2]}'>a</i> + b</span></li> +</ol> !! wikitext foo <ref>''a'' b</ref> <references /> -!! html -<p>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></p> -<ol class="references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'> -<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text"><i data-parsoid='{"dsr":[9,14,2,2]}'>a</i> - b</span></li> -</ol> !! end !! test 3. Leading whitespace in indent-pre suppressing contexts should not be escaped !! options -parsoid -!! wikitext +parsoid=html2wt +!! html/parsoid <blockquote> +<p> a <span>b</span> - c + c</p> </blockquote> -!! html +!! wikitext <blockquote> -<p> a <span>b</span> - c</p> + c </blockquote> !! end !! test 4. Leading whitespace in indent-pre suppressing contexts should not be escaped !! options -parsoid +options=html2wt +!! html/parsoid + <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>caption</figcaption></figure> !! wikitext [[File:Foobar.jpg|thumb|caption]] -!! html/parsoid - <figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>caption</figcaption></figure> !! end !! test 5. Nowiki escaping should account for indent-pres !! options parsoid=html2wt -!! html +!! html/parsoid <pre>==foo==</pre> !! wikitext ==foo== !! end +!!test +T95794: nowiki escaping should account for leading space at start-of-line in an indent-pre block +!! options +parsoid=html2wt +!! html/parsoid +<pre> +* foo +* bar +</pre> +!! wikitext + * foo + * bar +!! end + #### --------------- Behavior Switches -------------------- + !! test 1. Valid behavior switches should be escaped !! options parsoid=html2wt -!! html +!! html/parsoid __TOC__ <i>__TOC__</i> !! wikitext @@ -22215,7 +23418,7 @@ __TOC__ 2. Invalid behavior switches should not be escaped !! options parsoid=html2wt -!! html +!! html/parsoid __TOO__ __|__ !! wikitext @@ -22223,6 +23426,31 @@ __TOO__ __|__ !! end +# We use indent-pre as an indirect way to test for sol-transparent behavior. +!! test +Behavior switches should be SOL-transparent +!! options +parsoid=html2wt +!! html/parsoid + <meta property="mw:PageProp/toc" /> + + <!-- this one's bogus --> +<pre>__TOO__</pre> + +<pre data-parsoid='{}'><meta property="mw:PageProp/toc" data-parsoid='{"src":"__TOC__","magicSrc":"__TOC__"}'/> foo</pre> + +<meta property="mw:PageProp/toc" data-parsoid='{"src":"__TOC__","magicSrc":"__TOC__"}'/><pre data-parsoid='{}'>bar</pre> +!! wikitext + __TOC__ + + <!-- this one's bogus --> + __TOO__ + + __TOC__ foo + +__TOC__ bar +!! end + #### --------------- HTML tags --------------- #### 1. a tags #### 2. other tags @@ -22232,75 +23460,85 @@ __|__ !! test 1. a tags !! options -parsoid +parsoid=html2wt +!! html/parsoid +<a href="http://google.com">google</a> !! wikitext <a href="http://google.com">google</a> -!! html -<a href="http://google.com">google</a> !! end !! test 2. other tags -!! wikitext -* <nowiki><div>foo</div></nowiki> -* <nowiki><div style="color:red">foo</div></nowiki> -* <nowiki><td></nowiki> -!! html +!! options +parsoid=html2wt +!! html/parsoid <ul><li> <div>foo</div></li> <li> <div style="color:red">foo</div></li> <li> <td></li></ul> +!! wikitext +* <nowiki><div>foo</div></nowiki> +* <nowiki><div style="color:red">foo</div></nowiki> +* <nowiki><td></nowiki> !! end !! test 3. multi-line html tag -!! wikitext -<nowiki><div ->foo</div -></nowiki> -!! html +!! options +parsoid=html2wt +!! html/parsoid <p><div >foo</div > </p> +!! wikitext +<nowiki><div +>foo</div +></nowiki> !! end !! test 4. extension tags +!! options +parsoid=html2wt +!! html/parsoid +<p><ref>foo</ref> +</p><p><ref>bar +</p><p>baz</ref> +</p> !! wikitext <nowiki><ref>foo</ref></nowiki> <nowiki><ref>bar</nowiki> baz<nowiki></ref></nowiki> -!! html -<p><ref>foo</ref> -</p><p><ref>bar -</p><p>baz</ref> -</p> !! end #### --------------- Others --------------- !! test Escaping nowikis -!! wikitext -<nowiki>foo</nowiki> -!! html +!! options +parsoid=html2wt +!! html/parsoid <p><nowiki>foo</nowiki> </p> +!! wikitext +<nowiki>foo</nowiki> !! end ## The quote-char in the input is necessary for triggering the bug !! test (Bug 52035) Nowiki-escaping should not get tripped by " :" in text !! options -parsoid=wt2wt,html2wt +parsoid=html2wt +!! html/parsoid +<p>foo's bar :</p> !! wikitext foo's bar : -!! html -<p>foo's bar :</p> !! end +#----------- End of wikitext escaping tests -------------- + !! test Tag-like HTML structures are passed through as text @@ -22352,20 +23590,9 @@ HTML tag with broken attribute value quoting !! wikitext <span title="Hello world>Foo</span> !! html/php -<p><span>Foo</span> -</p> -!! html/parsoid <p><span title="Hello world">Foo</span> </p> -!! end - -!! test -Parsoid-only: HTML tag with broken attribute value quoting -!! options -parsoid -!! wikitext -<span title="Hello world>Foo</span> -!! html +!! html/parsoid <p><span title="Hello world">Foo</span> </p> !! end @@ -22379,7 +23606,7 @@ Table with broken attribute value quoting !! html/php <table> <tr> -<td>Foo +<td title="Hello world">Foo </td></tr></table> !! html/parsoid @@ -22400,9 +23627,9 @@ Table with broken attribute value quoting on consecutive lines !! html/php <table> <tr> -<td>Foo +<td title="Hello world">Foo </td> -<td>Bar +<td style="color:red">Bar </td></tr></table> !! html/parsoid @@ -22415,7 +23642,7 @@ Table with broken attribute value quoting on consecutive lines !! end !! test -Parsoid-only: Don't wrap broken template tags in <nowiki> on wt2wt (Bug 42353) +2. Parsoid-only: Don't wrap broken template tags in <nowiki> on wt2wt (Bug 42353) !! options parsoid !! wikitext @@ -22425,7 +23652,7 @@ parsoid !! end !! test -Parsoid-only: Don't wrap broken template tags in <nowiki> on wt2wt (Bug 42353) +1. Parsoid-only: Don't wrap broken template tags in <nowiki> on wt2wt (Bug 42353) !! options parsoid !! wikitext @@ -22544,6 +23771,8 @@ bar </tbody></table> !!end +# Note that the "style" attribute is really a template parameter here. +# The = would have to be {{=}} if you wanted the literal. !!test Empty TD followed by TD with tpl-generated attribute !! wikitext @@ -22704,7 +23933,7 @@ Multi-line image caption generated by templates with/without trailing newlines New element inserted (without intervening newlines) after an old sol-transparent node should serialize correctly !! options parsoid=html2wt -!! html +!! html/parsoid <meta typeof="mw:Includes/IncludeOnly" data-parsoid='{"src":"<includeonly>foo</includeonly>"}'/><meta typeof="mw:Includes/IncludeOnly/End" data-parsoid='{"src":""}'/><p>new para</p> <link rel="mw:PageProp/Category" href="./Category:Foo" data-parsoid='{}'/><h1>new heading</h1> @@ -22721,12 +23950,10 @@ new para ## a Parsoid serializer test, marking this Parsoid only !!test Improperly nested inline or quotes tags with whitespace in between -!!options -parsoid !! wikitext <span> <s>x</span> </s> ''' ''x''' '' -!! html +!! html/parsoid <p><span> <s>x</s></span><s> </s> <b> <i>x</i></b><i> </i> </p> @@ -22734,12 +23961,10 @@ parsoid !!test Encapsulate protected attributes from wt -!!options -parsoid !! wikitext -<div typeof="mw:placeholder stuff" data-parsoid="weird" data-parsoid-other="no" about="time" rel="mw:true">foo</div> -!! html -<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> +<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-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 @@ -22752,7 +23977,7 @@ Ensure ParagraphWrapper can deal with stray closing pre tags parsoid=wt2html !! wikitext plain text</pre> -!! html +!! html/parsoid plain text !!end @@ -22762,7 +23987,7 @@ plain text parsoid=wt2html !! wikitext <table>hi</table><table>ho</table> -!! html +!! html/parsoid <p>hi</p> <table></table> <p>ho</p> @@ -22778,7 +24003,7 @@ parsoid=wt2html,wt2wt <tr> || || <td> a </table> -!! html +!! html/parsoid <p> || || </p><table> <tbody><tr><td> a</td></tr> @@ -22791,7 +24016,7 @@ Encapsulation properly handles null DSR information from foster box parsoid=wt2html,wt2wt !! wikitext {{echo|<table>foo<tr><td>bar</td></tr></table>}} -!! html +!! html/parsoid <span typeof="mw:Transclusion" data-mw="{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"<table>foo<tr><td>bar</td></tr></table>"}},"i":0}}]}">foo</span><table><tbody><tr><td>bar</td></tr></tbody></table> !!end @@ -22801,7 +24026,7 @@ parsoid=wt2html,wt2wt parsoid=wt2wt,wt2html !! wikitext <table>{{echo|foo<tr><td>bar</td></tr>}}</table> -!! html +!! html/parsoid <p typeof="mw:Transclusion" data-mw="{"parts":["<table>",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo<tr><td>bar</td></tr>"}},"i":0}},"</table>"]}">foo</p><table> <tbody> <tr> @@ -22817,7 +24042,7 @@ parsoid=wt2wt,wt2html parsoid=wt2wt,wt2html !! wikitext <table><div>{{echo|foo}}</div><tr><td>bar</td></tr></table> -!! html +!! html/parsoid <div typeof="mw:Transclusion" data-mw="{"parts":["<table><div>",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo"}},"i":0}},"</div><tr><td>bar</td></tr></table>"]}">foo</div> <table> <tbody> @@ -22834,7 +24059,7 @@ parsoid=wt2wt,wt2html parsoid=wt2wt,wt2html !! wikitext <table><div><p>{{echo|foo</p></div><tr><td>}}bar</td></tr></table> -!! html +!! html/parsoid <div typeof="mw:Transclusion" data-mw="{"parts":["<table><div><p>",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo</p></div><tr><td>"}},"i":0}},"bar</td></tr></table>"]}"> <p>foo</p> </div> @@ -22853,7 +24078,7 @@ parsoid=wt2wt,wt2html parsoid=wt2wt,wt2html !! wikitext <table><div><p>{{echo|foo</p></div><tr><td>}}bar</td></tr></table> -!! html +!! html/parsoid <div typeof="mw:Transclusion" data-mw="{"parts":["<table><div><p>",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo</p></div><tr><td>"}},"i":0}},"bar</td></tr></table>"]}"> <p>foo</p> </div> @@ -22872,7 +24097,7 @@ parsoid=wt2wt,wt2html parsoid=wt2wt,wt2html !! wikitext <table><tr><td><div><p>{{echo|foo</p></div></td>foo}}</tr></table> -!! html +!! html/parsoid <p typeof="mw:Transclusion" data-mw="{"parts":["<table><tr><td><div><p>",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo</p></div></td>foo"}},"i":0}},"</tr></table>"]}">foo</p> <table> <tbody> @@ -22893,7 +24118,7 @@ parsoid=wt2wt,wt2html parsoid=wt2wt,wt2html !! wikitext <table><tr><td><div><p>{{echo|foo</p></div></td>foo</tr></table>}}<p>ok</p> -!! html +!! html/parsoid <p typeof="mw:Transclusion" data-mw="{"parts":["<table><tr><td><div><p>",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo</p></div></td>foo</tr></table>"}},"i":0}}]}">foo</p> <table> <tbody> @@ -22915,7 +24140,7 @@ parsoid=wt2wt,wt2html parsoid=wt2wt,wt2html !! wikitext <table>{{echo|<p>foo</p>}}<td>bar</td></table> -!! html +!! html/parsoid <p typeof="mw:Transclusion" data-mw="{"parts":["<table>",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"<p>foo</p>"}},"i":0}},"<td>bar</td></table>"]}">foo</p> <table> <tbody> @@ -22926,6 +24151,8 @@ parsoid=wt2wt,wt2html </table> !!end +# Note that the wt is broken on purpose: the = should be {{=}} if you +# don't want it to be a template parameter key. !!test 8. Encapsulate foster-parented transclusion content !!options @@ -22936,8 +24163,11 @@ parsoid=wt2wt,wt2html |- |b |} -!! html -<p typeof="mw:Transclusion" data-mw="{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"a\n"}},"i":0}}]}">a</p><p typeof="mw:Transclusion" data-mw="{"parts":["{|",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"style":{"wt":"'color:red'"}},"i":0}},"\n|-\n|b\n|}"]}">{{{1}}}</p><table> +!! html/parsoid +<p typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"a\n"}},"i":0}}]}'>a</p> +<span> </span> +<p typeof="mw:Transclusion" data-mw='{"parts":["{|",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"style":{"wt":"'color:red'"}},"i":0}},"\n|-\n|b\n|}"]}'>{{{1}}}</p> +<table> <tbody> <tr> <td>b</td> @@ -22952,7 +24182,7 @@ parsoid=wt2wt,wt2html parsoid=wt2wt,wt2html !! wikitext <table>{{echo|hi</table>hello}} -!! html +!! html/parsoid <p about="#mwt2" typeof="mw:Transclusion" data-mw='{"parts":["<table>",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"hi</table>hello"}},"i":0}}]}' data-parsoid='{"fostered":true,"autoInsertedEnd":true,"autoInsertedStart":true,"pi":[[{"k":"1","spc":["","","",""]}]]}'>hi</p><table about="#mwt2" data-parsoid='{"stx":"html"}'></table><p about="#mwt2">hello</p> !!end @@ -22967,7 +24197,7 @@ parsoid=wt2html,wt2wt |} </div> |} -!! html +!! html/parsoid <div about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"OpenTable","href":"./Template:OpenTable"},"params":{},"i":0}},"\n<div>"]}' data-parsoid='{"stx":"html","autoInsertedEnd":true,"pi":[[]]}'></div><span about="#mwt1"> </span> <table about="#mwt1" data-parsoid='{"autoInsertedEnd":true}'></table> @@ -22995,7 +24225,7 @@ Properly encapsulate empty-content transclusions in fosterable positions Support <object> element with .data attribute !!options parsoid=html2wt -!! html +!! html/parsoid <object data="test.swf"></object> !! wikitext <object data="test.swf"></object> @@ -23024,7 +24254,7 @@ Don't block XML namespace declaration Serialize interwiki links pointing to the current wiki as plain wiki links (bug 65869) !! options parsoid=html2wt -!! html +!! html/parsoid <p><a rel="mw:ExtLink" href="http://mi.wikipedia.org/wiki/Foo">Foo</a></p> !! wikitext [[Foo]] @@ -23035,7 +24265,7 @@ parsoid=html2wt New wikilinks should be serialized properly !! options parsoid=html2wt -!! html +!! html/parsoid <a rel="mw:WikiLink" href="./Foo" title="Foo" data-parsoid='{}'>Foo</a> <a rel="mw:WikiLink" href="./Foo" title="Foo">Foo</a> !! wikitext @@ -23047,7 +24277,7 @@ parsoid=html2wt New wiki links (href variations) !! options parsoid=html2wt -!! html +!! html/parsoid <a rel="mw:WikiLink" href="./Foo_bar">Foo_bar</a> <a rel="mw:WikiLink" href="Foo_bar">Foo_bar</a> <a rel="mw:WikiLink" href="Foo bar">Foo_bar</a> @@ -23063,7 +24293,7 @@ parsoid=html2wt New wiki links (content string variations) !! options parsoid=html2wt -!! html +!! html/parsoid <a rel="mw:WikiLink" href="./Foo_bar">Foo_bar</a> <a rel="mw:WikiLink" href="./Foo_bar">Foo bar</a> <a rel="mw:WikiLink" href="./Foo_bar">./Foo_bar</a> @@ -23077,7 +24307,7 @@ parsoid=html2wt New category links (href variations) !! options parsoid=html2wt -!! html +!! html/parsoid <link rel="mw:PageProp/Category" href="./Category:Toxine_bactérienne" /> <link rel="mw:PageProp/Category" href="./Category:Toxine_bact%C3%A9rienne" /> <link rel="mw:PageProp/Category" href="Category:Toxine_bact%C3%A9rienne" /> @@ -23092,7 +24322,7 @@ New sol transparent links don't need indent-pre nowiki protection !! options parsoid=html2wt language=de -!! html +!! html/parsoid <link rel="mw:PageProp/redirect" href="./Main_Page"> <!-- this is good --> <link rel="mw:PageProp/Category" href="./Category:Good" /> <!-- this is great --> <link rel="mw:PageProp/Category" href="./Kategorie:Great" /> @@ -23106,7 +24336,7 @@ language=de New interlanguage links (href variations) !! options parsoid=html2wt -!! html +!! html/parsoid <link rel="mw:PageProp/Language" href="http://es.wikipedia.org/wiki/Toxine bactérienne" /> <link rel="mw:PageProp/Language" href="http://es.wikipedia.org/wiki/Toxine_bactérienne" /> <link rel="mw:PageProp/Language" href="http://es.wikipedia.org/wiki/Toxine_bact%C3%A9rienne" /> @@ -23253,16 +24483,17 @@ parsoid <figure class="mw-default-size" typeof="mw:Image/Thumb" data-parsoid='{"optList":[{"ck":"thumbnail","ak":"thumb"},{"ck":"alt","ak":"alt="},{"ck":"caption","ak":"bar"}]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"}}'><img alt="" 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" data-parsoid='{"a":{"alt":"","resource":"./File:Foobar.jpg","height":"25","width":"220"},"sa":{"alt":"alt=","resource":"File:Foobar.jpg"}}'/></a><figcaption>bar</figcaption></figure> !! end -#!! test -#Image: new attributes should be serialized in wiki's language for RTL languages (bug 51852) -#!! options -#parsoid=html2wt -#language=ar -#!! html -#<figure class="mw-default-size mw-halign-right" typeof="mw:Image/Thumb"><a href="Imagen:Foobar.jpg"><img resource="./Imagen:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="20" width="180"/></a></figure> -#!! wikitext -#[[Imagen:Foobar.jpg|derecha|miniaturadeimagen]] -#!! end +!! test +Image: new attributes should be serialized in wiki's language for RTL languages (bug 51852) +!! options +parsoid=html2wt +language=ar +disabled +!! html/parsoid +<figure class="mw-default-size mw-halign-right" typeof="mw:Image/Thumb"><a href="./Imagen:Foobar.jpg"><img resource="./Imagen:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="20" width="180"/></a></figure> +!! wikitext +[[Imagen:Foobar.jpg|derecha|miniaturadeimagen]] +!! end !! test Image: Block level image should have \n before and after @@ -23272,7 +24503,7 @@ Image: Block level image should have \n before and after 456 !! html/parsoid <p>123</p> -<figure class="mw-halign-right" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="17" width="150"/></a></figure> +<figure class="mw-halign-right" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/150px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="17" width="150"/></a></figure> <p>456</p> !!end @@ -23290,26 +24521,22 @@ Image: New block level image should have \n before and after (existing content) !! test Image: upright option (parsoid) -!! options -parsoid !! wikitext [[File:Foobar.jpg|thumb|upright|caption]] [[File:Foobar.jpg|thumb|upright=0.5|caption]] [[File:Foobar.jpg|thumb|500x500px|upright=0.5|caption]] -!! html -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="19" width="170"/></a><figcaption>caption</figcaption></figure> -<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="12" width="110"/></a><figcaption>caption</figcaption></figure> -<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="57" width="500"/></a><figcaption>caption</figcaption></figure> +!! html/parsoid +<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/170px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="19" width="170"/></a><figcaption>caption</figcaption></figure> +<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/110px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="12" width="110"/></a><figcaption>caption</figcaption></figure> +<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/500px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="57" width="500"/></a><figcaption>caption</figcaption></figure> !!end !! test Image: upright option is ignored on inline and frame images (parsoid) -!! options -parsoid !! wikitext [[File:Foobar.jpg|500x500px|upright=0.5|caption]] -!! html -<p><span typeof="mw:Image" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="57" width="500"/></a></span></p> +!! html/parsoid +<p><span typeof="mw:Image" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/500px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="57" width="500"/></a></span></p> !!end !! test @@ -23358,7 +24585,7 @@ parsoid=html2wt Lists: Serialize correctly even when list content is wrapped in p-tags (like VE does) !! options parsoid=html2wt -!! html +!! html/parsoid <ul> <li><p>foo</p></li> </ul> @@ -23370,7 +24597,7 @@ parsoid=html2wt Lists: Serialize correctly even when list tags has unneeded whitespace between tags !! options parsoid=html2wt -!! html +!! html/parsoid <ul> <li>foo</li></ul> !! wikitext * foo @@ -23380,7 +24607,7 @@ parsoid=html2wt Don't strip leading whitespace when handling indent-pre suppressing tags !! options parsoid=html2wt -!! html +!! html/parsoid <table> <tr><td> indented row</td></tr> </table> @@ -23413,55 +24640,39 @@ foo Nowiki-wrap leading whitespace when handling indent-pre inducing tags !! options parsoid=html2wt -!! wikitext -foo -<nowiki> </nowiki><span>bar</span> +!! html/parsoid +<p>foo</p> + <span>bar</span> <span>foo2 -<nowiki> </nowiki></span>bar2 + </span>bar2 <div>foo</div> -<nowiki> </nowiki><span>bar</span> + <span>bar</span> <div> -<nowiki> </nowiki><span>foo</span> + <span>foo</span> </div> -!! html -<p>foo</p> - <span>bar</span> +!! wikitext +foo +<nowiki> </nowiki><span>bar</span> <span>foo2 - </span>bar2 +<nowiki> </nowiki></span>bar2 <div>foo</div> - <span>bar</span> +<nowiki> </nowiki><span>bar</span> <div> - <span>foo</span> +<nowiki> </nowiki><span>foo</span> </div> !! end !! test -Lists: Add space after bullets -!! options -parsoid=html2wt -!! html -<ul> -<li>foo</li> -<li> bar</li> -<li><span> baz</span></li> -</ul> -!! wikitext -* foo -* bar -* <span> baz</span> -!! end - -!! test Lists: Dont insert newlines in a serialized list item. !! options parsoid=html2wt -!! html +!! html/parsoid <ul><li>a<br>b</li><li>c</li></ul> !! wikitext * a<br>b @@ -23469,49 +24680,106 @@ parsoid=html2wt !! end !! test -Headings: Add space before/after == (Bug 51744) +1. Headings: Force sol-transparent links and behavior switches to serialize before/after !! options -parsoid=html2wt -!! html -<h2>foo</h2> -<h2> bar</h2> -<h2>baz </h2> -<h2><span> baz</span></h2> +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": false +} +!! html/parsoid +<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 -== foo == +== hello there [[Category:A1]] == -== bar == +== [[Category:A2]] hi pal == -== baz == +== <!--foo--> [[Category:A3]] how goes it == -== <span> baz</span> == +== it goes well [[Category:A4]] <!--bar--> == + +==howdy [[Category:A5]] == + +== __TOC__ ok == !! end !! test -Headings: Force metas to serialize before/after +2. Headings: Force sol-transparent links and behavior switches to serialize before/after !! options -parsoid=html2wt -!! 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> +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": true +} +!! html/parsoid +<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><!--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><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--> + +__TOC__ + +== ok == +!! end + +!! test +Headings: Don't hoist metas that come from templates +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": true +} +!! html/parsoid +<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 +== {{echo|foo [[Category:Foo]]}} == +!! end + +!! test +Headings: Category in ref isn't hoisted +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": true +} +!! html/parsoid +<h2> foo <span about="#mwt2" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span> </h2> + +<ol class="references" typeof="mw:Extension/references" about="#mwt3" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">bar <link rel="mw:PageProp/Category" href="./Category:Baz" /> </span></li></ol> +!! wikitext +== foo <ref>bar +[[Category:Baz]] </ref> == + +<references /> !! end !! test Parsoid: Serialize positional parameters with = in them as named parameter !! options parsoid=html2wt -!! html +!! html/parsoid <p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"f=oo"}},"i":0}}]}'>foo</p> @@ -23535,7 +24803,7 @@ data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"}, Parsoid: Serialize positional parameters with = in extlink as named parameter !! options parsoid=html2wt -!! html +!! html/parsoid <p><a rel="mw:ExtLink" href="http://stuff?is=ok" about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"http://stuff?is=ok"}},"i":0}}]}'>http://stuff?is=ok</a></p> !! wikitext {{echo|1 = http://stuff?is=ok}} @@ -23545,7 +24813,7 @@ parsoid=html2wt Parsoid: Correctly serialize block-node children when they are a combination of text and p-nodes !! options parsoid=html2wt -!! html +!! html/parsoid <div>a<p>b</p></div> <div>a <p>b</p></div> @@ -23570,7 +24838,7 @@ b Substrings resembling wikitext in hrefs should not get nowiki escapes !! options parsoid=html2wt -!! html +!! html/parsoid <a rel="mw:WikiLink" href="./Foo''bar''baz">Foo''bar''baz</a> !! wikitext [[Foo''bar''baz]] @@ -23580,27 +24848,89 @@ parsoid=html2wt Enforce single-line context in the serializer !! options parsoid=html2wt -!! html +!! html/parsoid <h2>testing 123</h2> +<h2> hi <span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"bogus","href":"./Template:Bogus"},"params":{"1":{"wt":"there\nyou"}},"i":0}}]}'>there</span><span about="#mwt1"> +</span><span about="#mwt1">you</span> </h2> + +<h2> foo <span about="#mwt2" class="mw-ref" 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"><span class="mw-reflink-text">[1]</span></a></span> </h2> + +<ol class="references" typeof="mw:Extension/references" about="#mwt3" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1">↑</a></span> <span id="mw-reference-text-cite_note-1" class="mw-reference-text" data-parsoid="{}">hello +there</span></li></ol> + <ul><li>asd sdf</li></ul> + +<ul><li>foo +bar +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 == +== hi {{bogus|there +you}} == + +== foo <ref>hello +there</ref> == + +<references /> + * asd sdf + +* foo bar baz +* foo '''bar''' baz + +; hi ho : hi ho + +: {| +| ha +ha +ha +|} +!! end + +!! test +Serialize new placeholder space without spans +!! options +parsoid=html2wt +!! html/parsoid +<p>foo<span typeof="mw:Placeholder"> </span>: bar</p> + +<p>foo<span typeof="mw:DisplaySpace mw:Placeholder" data-parsoid='{"src":" ","isDisplayHack":true}'> </span>: bar</p> + +<span typeof="mw:Extension/ref" data-mw="{"name":"ref","body":{"html":"foo<span typeof=\"mw:Placeholder\">&nbsp;</span>: bar"}}"><sup>[1]</sup></span>ok</p> +!! wikitext +foo : bar + +foo : bar + +<ref>foo : bar</ref>ok !! end -#----------------------------- -# I/B quote minimization tests -#----------------------------- + +#----------------------- +# Tag minimization tests +#----------------------- !! test 1. I/B quote minimization: wikitext-only tags should be combined !! options parsoid=html2wt -!! html +!! html/parsoid <p><i>A</i><i>B</i></p> <p><b>A</b><b>B</b></p> <p><i>A</i><b><i>B</i></b></p> @@ -23631,7 +24961,7 @@ parsoid=html2wt 2. I/B quote minimization: wikitext and html tags should not be combined !! options parsoid=html2wt -!! html +!! html/parsoid <p><i>A</i><i data-parsoid='{"stx":"html"}'>B</i></p> <p><i>A</i><b><i data-parsoid='{"stx":"html"}'>B</i></b></p> !! wikitext @@ -23644,7 +24974,7 @@ parsoid=html2wt 3. I/B quote minimization: templated content stops minimization !! options parsoid=html2wt -!! html +!! html/parsoid <p><i>A</i><i about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"''B''"}},"i":0}}]}'>B</i> <p><i>A</i><b about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"'''''B'''''"}},"i":0}}]}'><i>B</i></b> !! wikitext @@ -23657,7 +24987,7 @@ parsoid=html2wt 4. I/B quote minimization: new content should be mimimized with adjacent old content !! options parsoid=html2wt -!! html +!! html/parsoid <p><i>A</i><i>B</i></p> <p><b>A</b><b>B</b></p> <p><i>A</i><b><i>B</i></b></p> @@ -23699,18 +25029,61 @@ parsoid={ ''ac'' !! end -#------------------------------------ -# End of I/B quote minimization tests -#------------------------------------ +!! test +1. Merge adjacent link nodes as long as at least one element is new +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": true +} +!! html/parsoid +<a rel="mw:WikiLink" href="./Football">Foot</a><a rel="mw:WikiLink" href="./Football">ball</a> +<a data-parsoid="{}" rel="mw:WikiLink" href="./Football">Foot</a><a rel="mw:WikiLink" href="./Football">ball</a> +<a data-parsoid="{}" rel="mw:WikiLink" href="./Football">Foot</a><a data-parsoid="{}" rel="mw:WikiLink" href="./Football">ball</a> +!! wikitext +[[Football]] +[[Football]] +[[Football|Foot]][[Football|ball]] +!! end + +!! test +2. Merge adjacent link nodes and enable additional normalizations +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": true +} +!! html/parsoid +<a rel="mw:WikiLink" href="./Football"><i>Foot</i></a><a rel="mw:WikiLink" href="./Football"><i>ball</i></a> +!! wikitext +[[Football|''Football'']] +!! end + +!! test +3. Don't merge adjacent link nodes if scrubWikitext is false +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": false +} +!! html/parsoid +<a rel="mw:WikiLink" href="./Football">Foot</a><a rel="mw:WikiLink" href="./Football">ball</a> +!! wikitext +[[Football|Foot]][[Football|ball]] +!! end + +#------------------------------ +# End of tag minimization tests +#------------------------------ !!test Bug 54262: New entities !! options parsoid=html2wt +!! html/parsoid +<span typeof="mw:Entity"> </span> !! wikitext -!! html -<span typeof="mw:Entity"> </span> !! end ## Note that there is no wikitext output for 'unknownproperty' ## @@ -23720,7 +25093,7 @@ parsoid=html2wt Magic words !! options parsoid=html2wt -!! html +!! html/parsoid <meta property='mw:PageProp/toc' /> <meta property='mw:PageProp/notoc' /> <meta property='mw:PageProp/forcetoc' /> @@ -23747,7 +25120,7 @@ __NOCONTENTCONVERT__ Consecutive <pre>s should not get merged !! options parsoid=html2wt,html2html -!! html +!! html/parsoid <pre>a</pre><pre>b</pre> <pre>c @@ -23779,8 +25152,8 @@ f</pre> Edited ISBN links not serializable as ISBN links should serialize as wikilinks !! options parsoid=html2wt -!! html -<a rel="mw:ExtLink" href="./Special:BookSources/1234567890">ISBN 1234567895</a> +!! html/parsoid +<a href="./Special:BookSources/1234567890" rel="mw:ExtLink">ISBN 1234567895</a> !! wikitext [[Special:BookSources/1234567890|ISBN 1234567895]] !! end @@ -23789,7 +25162,7 @@ parsoid=html2wt Edited RFC links not serializable as RFC links should serialize as extlinks !! options parsoid=html2wt -!! html +!! html/parsoid <a href="//tools.ietf.org/html/rfc123" rel="mw:ExtLink">New RFC</a> !! wikitext [//tools.ietf.org/html/rfc123 New RFC] @@ -23799,7 +25172,7 @@ parsoid=html2wt Edited PMID links not serializable as PMID links should serialize as extlinks !! options parsoid=html2wt -!! html +!! html/parsoid <a href="//www.ncbi.nlm.nih.gov/pubmed/123?dopt=Abstract" rel="mw:ExtLink">New PMID</a> !! wikitext [//www.ncbi.nlm.nih.gov/pubmed/123?dopt=Abstract New PMID] @@ -23856,10 +25229,131 @@ 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 +WTS of autolink-like text after an autolink (T108563) +!! options +parsoid=html2wt +!! html/parsoid +<p><a rel="mw:ExtLink" href="http://example.com">http://example.com</a> http://example.com is not a link.</p> +!! wikitext +http://example.com<nowiki> http://example.com is not a link.</nowiki> +!! end + +!! test +Magic links inside links (not autolinked) +!! wikitext +[[Foo|http://example.com]] +[[Foo|RFC 1234]] +[[Foo|PMID 1234]] +[[Foo|ISBN 123456789x]] + +[http://foo.com http://example.com] +[http://foo.com RFC 1234] +[http://foo.com PMID 1234] +[http://foo.com ISBN 123456789x] +!! html+tidy +<p><a href="/wiki/Foo" title="Foo">http://example.com</a> <a href="/wiki/Foo" title="Foo">RFC 1234</a> <a href="/wiki/Foo" title="Foo">PMID 1234</a> <a href="/wiki/Foo" title="Foo">ISBN 123456789x</a></p> +<p><a rel="nofollow" class="external text" href="http://foo.com">http://example.com</a> <a rel="nofollow" class="external text" href="http://foo.com">RFC 1234</a> <a rel="nofollow" class="external text" href="http://foo.com">PMID 1234</a> <a rel="nofollow" class="external text" href="http://foo.com">ISBN 123456789x</a></p> +!! html/parsoid +<p><a rel="mw:WikiLink" href="./Foo" title="Foo">http://example.com</a> +<a rel="mw:WikiLink" href="./Foo" title="Foo">RFC 1234</a> +<a rel="mw:WikiLink" href="./Foo" title="Foo">PMID 1234</a> +<a rel="mw:WikiLink" href="./Foo" title="Foo">ISBN 123456789x</a></p> + +<p><a rel="mw:ExtLink" href="http://foo.com">http://example.com</a> +<a rel="mw:ExtLink" href="http://foo.com">RFC 1234</a> +<a rel="mw:ExtLink" href="http://foo.com">PMID 1234</a> +<a rel="mw:ExtLink" href="http://foo.com">ISBN 123456789x</a></p> +!! end + +!! test +Magic links inside image captions (autolinked) +!! wikitext +[[File:Foobar.jpg|thumb|http://example.com]] +[[File:Foobar.jpg|thumb|RFC 1234]] +[[File:Foobar.jpg|thumb|PMID 1234]] +[[File:Foobar.jpg|thumb|ISBN 123456789x]] +!! html+tidy +<div class="thumb tright"> +<div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> +<div class="thumbcaption"> +<div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div> +<a rel="nofollow" class="external free" href="http://example.com">http://example.com</a></div> +</div> +</div> +<div class="thumb tright"> +<div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> +<div class="thumbcaption"> +<div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div> +<a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc1234">RFC 1234</a></div> +</div> +</div> +<div class="thumb tright"> +<div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> +<div class="thumbcaption"> +<div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div> +<a class="external mw-magiclink-pmid" rel="nofollow" href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract">PMID 1234</a></div> +</div> +</div> +<div class="thumb tright"> +<div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> +<div class="thumbcaption"> +<div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div> +<a href="/wiki/Special:BookSources/123456789X" class="internal mw-magiclink-isbn">ISBN 123456789x</a></div> +</div> +</div> +!! html/parsoid +<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><a rel="mw:ExtLink" href="http://example.com">http://example.com</a></figcaption></figure> +<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><a href="//tools.ietf.org/html/rfc1234" rel="mw:ExtLink">RFC 1234</a></figcaption></figure> +<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><a href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract" rel="mw:ExtLink">PMID 1234</a></figcaption></figure> +<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><a href="./Special:BookSources/123456789X" rel="mw:WikiLink">ISBN 123456789x</a></figcaption></figure> +!! end + +!! test +WTS of magic word text (T109371) +!! options +parsoid=html2wt +!! html/parsoid +<p>RFC 1234</p> +<p><a href="http://foo.com" rel="mw:ExtLink">RFC 1234</a></p> +<p><a href="./Foo" rel="mw:WikiLink">RFC 1234</a></p> +!! wikitext +<nowiki>RFC 1234</nowiki> + +[http://foo.com RFC 1234] + +[[Foo|RFC 1234]] +!! end + +!! test Edited Redirect link should emit a non-piped wikitext link !! options parsoid=html2wt -!! html +!! html/parsoid <link rel="mw:PageProp/redirect" href="Bar" data-parsoid='{"a":{"href":"./Foo"},"sa":{"href":"Foo"}}'> !! wikitext #REDIRECT [[Bar]] @@ -23869,7 +25363,7 @@ parsoid=html2wt T75121: Infer extension name from typeOf if data-mw is not present !! options parsoid=html2wt -!! html +!! html/parsoid <div typeOf="mw:Extension/foo"></div> !! wikitext <foo /> @@ -23929,7 +25423,7 @@ parsoid=html2wt,wt2wt HTML id attribute with Parsoid-like element ids should not be serialized to wikitext !! options parsoid=html2wt -!! html +!! html/parsoid <table id='mwAb'> <td id='mwAc'>foo</td> <td id='serialize-this'>bar</td> @@ -23945,7 +25439,7 @@ parsoid=html2wt Parsoid-like element ids should not be serialized to wikitext unless shadowed !! options parsoid=html2wt -!! html +!! html/parsoid <div id="mwAQ" data-parsoid='{"stx":"html","a":{"id":"mwAQ"},"sa":{"id":"hello"}}'>ok</div> !! wikitext <div id="hello">ok</div> @@ -23972,12 +25466,608 @@ parsoid={ Never serialize a-tag as html, regardless of what data-parsoid has to say !! options parsoid=html2wt -!! html +!! html/parsoid <a rel="mw:WikiLink" href="./Foo" title="Foo" data-parsoid='{"stx":"html"}'>Foo</a> !! wikitext [[Foo]] !! end +## SSS FIXME: This is broken output nevertheless. +## What might be a reasonable non-broken output for this? +## This is an edge case unlikely to be seen in production +## that I am not wasting more time on this right now. +!! test +Never serialize a-tag as html, no matter what attributes it has +!! options +parsoid=html2wt +!! html/parsoid +<a bad='true' href='http://boo.org'><img src='http://boohoo.org' /></a> +!! wikitext +[http://boo.org http://boohoo.org] +!! end + +# Misnested is an indication that selser can reuse the source but these have +# shown to sneak through on occasion. See T101768. +# The original wikitext here is: [http://test.com [[one]] two three] +!! test +Strip span tags added to mark as misnested +!! options +parsoid=html2wt +!! html/parsoid +<p data-parsoid='{}'><a rel="mw:ExtLink" href="http://test.com" data-parsoid='{"targetOff":17,"contentOffsets":[17,34]}'></a><a rel="mw:WikiLink" href="./One" title="One" data-parsoid='{"stx":"simple","a":{"href":"./One"},"sa":{"href":"one"},"misnested":true}'>one</a><span data-parsoid='{"misnested":true}'> two three</span></p> +!! wikitext +[http://test.com][[one]] two three +!! end + +# -------------------------------------------- +# Tests spec'ing wikitext serialization norms | +# -------------------------------------------- + +!! test +Lists: Add space after bullets +!! options +parsoid=html2wt +!! html/parsoid +<ul> +<li>foo</li> +<li> bar</li> +<li><span> baz</span></li> +</ul> +!! wikitext +* foo +* bar +* <span> baz</span> +!! end + +!! test +1. Headings: Add space before/after == (T53744) +!! options +parsoid=html2wt +!! html/parsoid +<h2>foo</h2> +<h2> bar</h2> +<h2>baz </h2> +<h2><span> baz</span></h2> +!! wikitext +== foo == + +== bar == + +== baz == + +== <span> baz</span> == +!! end + +!! test +2. Headings: Add space before/after == even after hoisted content +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": true +} +!! html/parsoid +<h2> <link href="./Category:A2" rel="mw:PageProp/Category" />ok</h2> +!! wikitext + [[Category:A2]] + +== ok == +!! end + +!! test +1. Headings: suppress newly created empty headings +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": true +} +!! html/parsoid +<h2></h2> +!! wikitext +!! end + +!! test +2. Headings: don't suppress empty headings if scrubWikitext is false +!! options +parsoid=html2wt +!! html/parsoid +<h2></h2> +!! wikitext +==<nowiki/>== +!! end + +!! test +3. Headings: suppress empty headings on edits +!! options +parsoid={ + "modes": ["selser"], + "scrubWikitext": true, + "changes": [ + [ "#x", "remove"] + ] +} +!! wikitext +==<span id="x">foo</span>== +!! wikitext/edited +!! end + +!! test +1. WT Quote Tags: suppress newly created empty style tags +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": true +} +!! html/parsoid +<i></i><b></b> +!! wikitext +!! end + +!! test +2. WT Quote Tags: don't suppress empty style tags if scrubWikitext is false +!! options +parsoid=html2wt +!! html/parsoid +<i></i><b></b> +!! wikitext +''<nowiki/>'''''<nowiki/>''' +!! end + +!! test +3. WT Quote Tags: suppress empty style tags on edits +!! options +parsoid={ + "modes": ["selser"], + "scrubWikitext": true, + "changes": [ + [ "#x", "remove"] + ] +} +!! wikitext +'''<span id="x">foo</span>''' +!! wikitext/edited +!! end + +!! test +1. Anchors: suppress newly created empty anchors +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": true +} +!! html/parsoid +<a rel="mw:WikiLink" href="./Test" title="Test"></a> +!! wikitext +!! end + +!! test +2. Anchors: don't suppress empty anchors if scrubWikitext is false +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": false +} +!! html/parsoid +<a rel="mw:WikiLink" href="./Test" title="Test"></a> +!! wikitext +[[Test|<nowiki/>]] +!! end + +!! test +3. Anchors: suppress empty anchors on edits +!! options +parsoid={ + "modes": ["selser"], + "scrubWikitext": true, + "changes": [ + [ "#x", "remove"] + ] +} +!! wikitext +[[Test|<span id="x">foo</span>]] +!! wikitext/edited +!! end + +!! test +3a. Anchors: do not suppress numbered extlinks +!! options +parsoid={ + "modes": ["wt2wt"], + "scrubWikitext": true +} +!! wikitext +[http://foo.com] +!! html/parsoid +<a rel="mw:ExtLink" href="http://foo.com"></a> +!! end + +!! test +3b. Anchors: do not suppress numbered extlinks +!! options +parsoid={ + "modes": ["wt2wt"], + "scrubWikitext": true, + "changes": [ + [ "#x", "remove"] + ] +} +!! wikitext +[http://foo.com <span id="x">foo</span>] +!! wikitext/edited +[http://foo.com] +!! end + +!!test +Normalizations should be restricted to edited content +!!options +parsoid={ + "modes": ["selser"], + "scrubWikitext": true, + "changes": [ + [ "h1", "before", "<i></i>"] + ] +} +!!wikitext +a += = +b +!!wikitext/edited +a += = +b +!!end + +!! test +1. Multiple normalizations (html2wt) +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": true +} +!! html +<h2><i></i></h2> +<p><a href='Foo' rel='mw:WikiLink'>foo<i></i> + </a><b><i></i></b>x</p> +!! wikitext + +[[foo]] +x + +!! end + +!! test +2. Multiple normalizations (selser) +!! options +parsoid={ + "modes": ["selser"], + "scrubWikitext": true, + "changes": [ + [ "#x", "after", "<h1><i></i></h1>\n<p> x<b></b></p>"] + ] +} +!! wikitext +<span id="x">foo</span> +!! wikitext/edited +<span id="x">foo</span> + +x +!! end + +!! test +1. Indent Pre Nowiki: suppress whitespace at the start of new paragraph +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": true +} +!! html/parsoid +<p> hi</p> +<p> hello</p> +!! wikitext +hi + +hello +!! end + +!! test +2. Indent Pre Nowiki: don't suppress whitespace at the start of new paragraph if scrubWikitext is false +!! options +parsoid=html2wt +!! html/parsoid +<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 +4. Indent Pre Nowiki: suppress leading whitespace in edited paragraphs +!! options +parsoid={ + "modes": ["selser"], + "scrubWikitext": true, + "changes": [ + [ "p", "html", " a\n b" ] + ] +} +!! wikitext +xyz +!! wikitext/edited +a +b +!! end + +!! test +1. New links that end in spaces +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": false +} +!! html/parsoid +<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> +<p><a rel="mw:WikiLink" href="./Boston" title="Boston">Boston </a> is a city.</p> +!! wikitext +[[Berlin ]]<nowiki/>is the capital of Germany. + +[[Foo ]]'''bar''' + +[[Boston ]] is a city. +!! end + +!! test +2. New links that end in spaces +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": true +} +!! html/parsoid +<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> +<p><a rel="mw:WikiLink" href="./Boston" title="Boston">Boston </a> is a city.</p> +!! wikitext +[[Berlin]] is the capital of Germany. + +[[Foo]] '''bar''' + +[[Boston]] is a city. +!! end + +!! test +1. Table cells with escapable prefixes +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": false +} +!! html +<table> +<tr><td>a</td></tr> +<tr><td>-</td></tr> +<tr><td>+</td></tr> +</table> +!! wikitext +{| +|a +|- +|<nowiki>-</nowiki> +|- +|<nowiki>+</nowiki> +|} +!! end + +!! test +2. Table cells with escapable prefixes +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": true +} +!! html +<table> +<tr><td>a</td></tr> +<tr><td>-</td></tr> +<tr><td>+</td></tr> +</table> +!! wikitext +{| +|a +|- +| - +|- +| + +|} +!! end + +!! test +3a. Table cells with escapable prefixes after edits +!! options +parsoid={ + "modes": ["selser"], + "scrubWikitext": true, + "changes": [ + [ "table tbody tr:first-child td:first-child", "remove"] + ] +} +!! wikitext +{| +|a||- +|} +!! wikitext/edited +{| +| - +|} +!! end + +!! test +3b. Table cells with escapable prefixes after edits +!! options +parsoid={ + "modes": ["selser"], + "scrubWikitext": true, + "changes": [ + [ "table tbody tr:first-child td:first-child", "html", "-" ], + [ "#x", "remove" ] + ] +} +!! wikitext +{| +|pqr +|<span id="x">foo</span>+ +|} +!! wikitext/edited +{| +| - +| + +|} +!! end + +# FIXME: This test will fail because +# normalization doesn't realize that the id attribute +# will eliminate the escapable scenario +!! test +4a. Table cells without escapable prefixes after edits +!! options +parsoid={ + "modes": ["selser"], + "scrubWikitext": true, + "changes": [ + [ "#x", "html", "-" ] + ] +} +!! wikitext +{| +| id="x" |abcd +|} +!! wikitext/edited +{| +| id="x" |- +|} +!! end + +## This tests normalizer's ability to discriminate between +## cells having identical content. +!! test +4b. Table cells without escapable prefixes after edits +!! options +parsoid={ + "modes": ["selser"], + "scrubWikitext": true, + "changes": [ + [ "td", "html", "-" ] + ] +} +!! wikitext +{| +|a||b +|} +!! wikitext/edited +{| +| -||- +|} +!! end + +## This tests normalizer's ability to not be tripped by +## comments (and whitespace) +!! test +4c. Table cells without escapable prefixes after edits +!! options +parsoid={ + "modes": ["selser"], + "scrubWikitext": true, + "changes": [ + [ "table tbody tr td:first-child", "remove" ] + ] +} +!! wikitext +{| +|- +<!--foo--> |a||- +|} +!! wikitext/edited +{| +|- +<!--foo--> | - +|} +!! end + +## This tests normalizer's ability to handle HTML cells +!! test +4d. Table cells without escapable prefixes after edits +!! options +parsoid={ + "modes": ["selser"], + "scrubWikitext": true, + "changes": [ + [ "td", "html", "-" ] + ] +} +!! wikitext +<table> +<tr><td>a</td></tr> +</table> +!! wikitext/edited +<table> +<tr><td>-</td></tr> +</table> +!! end + +!! test +Escape nowiki DOM elements +!! options +parsoid=html2wt +!! html/parsoid +<nowiki><i>foo</i></nowiki> +!! wikitext +<nowiki>''foo''</nowiki> +!! end + +# --------------------------------------------------- +# End of tests spec'ing wikitext serialization norms | +# --------------------------------------------------- + # ----------------------------------------------------------------- # End of section for Parsoid-only html2wt tests for serialization # of new content @@ -23993,7 +26083,7 @@ parsoid=html2wt ## T90517 !! test -1. Selser: New comments should not be lost +Selser: New comments should not be lost !! options parsoid={ "modes": ["selser"], @@ -24014,7 +26104,7 @@ parsoid={ ## T89383 !! test -2. Selser: Check for validity of DSR before using it +Selser: Check for validity of DSR before using it !! options parsoid={ "modes": ["selser"], @@ -24029,10 +26119,55 @@ parsoid={ <span id="a">a</span> !! end +!! test +1. DOMDiff: Changes to <ref> content should be looked up using id +!! options +parsoid={ + "modes": ["selser"], + "changes": [ + ["#X", "after", "bar"], + ["#Y", "after", "baz"] + ] +} +!! wikitext +X <ref><span id="X">foo</span></ref> +Y <ref name="a" /> +<references> +<ref name="a"><span id="Y">foo</span></ref> +</references> +!! wikitext/edited +X <ref><span id="X">foo</span>bar</ref> +Y <ref name="a" /> +<references> +<ref name="a"><span id="Y">foo</span>baz</ref> +</references> +!! end + +!! test +2. DOMDiff: Changes to <ref> content should be looked up using id +!! options +parsoid={ + "modes": ["selser"], + "changes": [ + ["#Z", "after", "bar"] + ] +} +!! wikitext +A <ref>foo bar for a</ref> +B <ref group="X" name="b" /> + +<references /> + +<references group="X"> +<ref name="b"><span id="Z">foo</span></ref> +</references> +!! wikitext/edited +A <ref>foo bar for a</ref> +B <ref group="X" name="b" /> -TODO: -more images -more tables -character entities -and much more -Try for 100% code coverage +<references /> + +<references group="X"> +<ref name="b"><span id="Z">foo</span>bar</ref> +</references> +!! end diff --git a/tests/parser/preprocess/All_system_messages.expected b/tests/parser/preprocess/All_system_messages.expected index 3665e3c1..57223da6 100644 --- a/tests/parser/preprocess/All_system_messages.expected +++ b/tests/parser/preprocess/All_system_messages.expected @@ -1370,12 +1370,12 @@ Message </td><td> <template lineStart="1"><title>int:Emailmessage</title></template> </td></tr><tr><td> -[http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Emailpage&action=edit emailpage]<br> -[[MediaWiki_talk:Emailpage|Talk]] +[http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Emailuser&action=edit emailuser]<br> +[[MediaWiki_talk:Emailuser|Talk]] </td><td> E-mail user </td><td> -<template lineStart="1"><title>int:Emailpage</title></template> +<template lineStart="1"><title>int:Emailuser</title></template> </td></tr><tr><td> [http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Emailpagetext&action=edit emailpagetext]<br> [[MediaWiki_talk:Emailpagetext|Talk]] diff --git a/tests/parser/preprocess/All_system_messages.txt b/tests/parser/preprocess/All_system_messages.txt index c619df7b..cdc223a9 100644 --- a/tests/parser/preprocess/All_system_messages.txt +++ b/tests/parser/preprocess/All_system_messages.txt @@ -1370,12 +1370,12 @@ Message </td><td> {{int:Emailmessage}} </td></tr><tr><td> -[http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Emailpage&action=edit emailpage]<br> -[[MediaWiki_talk:Emailpage|Talk]] +[http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Emailuser&action=edit emailuser]<br> +[[MediaWiki_talk:Emailuser|Talk]] </td><td> E-mail user </td><td> -{{int:Emailpage}} +{{int:Emailuser}} </td></tr><tr><td> [http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Emailpagetext&action=edit emailpagetext]<br> [[MediaWiki_talk:Emailpagetext|Talk]] diff --git a/tests/parserTests.php b/tests/parserTests.php index 5d21319b..4d84025b 100644 --- a/tests/parserTests.php +++ b/tests/parserTests.php @@ -80,7 +80,7 @@ if ( isset( $options['file'] ) ) { } # Print out software version to assist with locating regressions -$version = SpecialVersion::getVersion(); +$version = SpecialVersion::getVersion( 'nodb' ); echo "This is MediaWiki version {$version}.\n\n"; if ( isset( $options['fuzz'] ) ) { diff --git a/tests/phpunit/LessFileCompilationTest.php b/tests/phpunit/LessFileCompilationTest.php index df4690a4..eec02edc 100644 --- a/tests/phpunit/LessFileCompilationTest.php +++ b/tests/phpunit/LessFileCompilationTest.php @@ -45,11 +45,7 @@ class LessFileCompilationTest extends ResourceLoaderTestCase { $method->setAccessible( true ); $compiler = $method->invoke( $this->module, $rlContext ); - $this->assertNotNull( $compiler->compileFile( $this->file ) ); - } - - public function getName( $withDataSet = true ) { - return $this->toString(); + $this->assertNotNull( $compiler->parseFile( $this->file )->getCss() ); } public function toString() { diff --git a/tests/phpunit/Makefile b/tests/phpunit/Makefile index a33b86a3..e1537bf5 100644 --- a/tests/phpunit/Makefile +++ b/tests/phpunit/Makefile @@ -73,7 +73,6 @@ help: # # Targets: # phpunit (default) Run all the tests with phpunit - # install Install PHPUnit from phpunit.de # tap Run the tests individually through Test::Harness's prove(1) # help You're looking at it! # coverage Run the tests and generates an HTML code coverage report diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php index 72cac051..7dc7027a 100644 --- a/tests/phpunit/MediaWikiTestCase.php +++ b/tests/phpunit/MediaWikiTestCase.php @@ -204,13 +204,11 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { while ( $this->db->trxLevel() > 0 ) { $this->db->rollback(); } - - // don't ignore DB errors - $this->db->ignoreErrors( false ); } DeferredUpdates::clearPendingUpdates(); + ob_start( 'MediaWikiTestCase::wfResetOutputBuffersBarrier' ); } protected function addTmpFiles( $files ) { @@ -218,6 +216,11 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { } protected function tearDown() { + $status = ob_get_status(); + if ( isset( $status['name'] ) && $status['name'] === 'MediaWikiTestCase::wfResetOutputBuffersBarrier' ) { + ob_end_flush(); + } + $this->called['tearDown'] = true; // Cleaning up temporary files foreach ( $this->tmpFiles as $fileName ) { @@ -233,9 +236,6 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { while ( $this->db->trxLevel() > 0 ) { $this->db->rollback(); } - - // don't ignore DB errors - $this->db->ignoreErrors( false ); } // Restore mw globals @@ -716,9 +716,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * @param string $function */ public function hideDeprecated( $function ) { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); wfDeprecated( $function ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); } /** @@ -1002,9 +1002,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { # This check may also protect against code injection in # case of broken installations. - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( !$haveDiff3 ) { $this->markTestSkipped( "Skip test, since diff3 is not configured" ); @@ -1117,7 +1117,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { // of tidy. In that case however, we can not reliably detect whether a failing validation // is due to malformed HTML, or caused by tidy not being installed as a command line tool. // That would cause all HTML assertions to fail on a system that has no tidy installed. - if ( !$GLOBALS['wgTidyInternal'] ) { + if ( !$GLOBALS['wgTidyInternal'] || !MWTidy::isEnabled() ) { $this->markTestSkipped( 'Tidy extension not installed' ); } @@ -1180,4 +1180,12 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { self::assertFalse( self::tagMatch( $matcher, $actual, $isHtml ), $message ); } + + /** + * Used as a marker to prevent wfResetOutputBuffers from breaking PHPUnit. + * @return string + */ + public static function wfResetOutputBuffersBarrier( $buffer ) { + return $buffer; + } } diff --git a/tests/phpunit/README b/tests/phpunit/README index 0a32ba17..f555812d 100644 --- a/tests/phpunit/README +++ b/tests/phpunit/README @@ -14,16 +14,13 @@ TO RETAIN YOUR DATA. == Installation == -If PHPUnit is not installed, follow the installation instructions in the -PHPUnit Manual at: - - http://www.phpunit.de/manual/current/en/installation.html +If you used composer to install MediaWiki's dependencies PHPUnit will already be available, unless +you explicitly specified the --no-dev flag during the install. In this case just run "composer update". -- or - - -On Unix-like operating systems, run: +Otherwise follow the installation instructions in the +PHPUnit Manual at: - make install + https://phpunit.de/manual/current/en/installation.html == Running tests == @@ -47,7 +44,7 @@ On Windows-family operating systems, run the 'run-tests.bat' batch file. === Writing tests === -A guide to writing unit tests for MediaWiki can be found at: +A guide to writing PHP unit tests for MediaWiki can be found at: - http://mediawiki.org/wiki/Unit_Testing + https://www.mediawiki.org/wiki/Manual:PHP_unit_testing diff --git a/tests/phpunit/ResourceLoaderTestCase.php b/tests/phpunit/ResourceLoaderTestCase.php index deecb31e..325b20ee 100644 --- a/tests/phpunit/ResourceLoaderTestCase.php +++ b/tests/phpunit/ResourceLoaderTestCase.php @@ -25,27 +25,35 @@ abstract class ResourceLoaderTestCase extends MediaWikiTestCase { return $ctx; } - protected function setUp() { - parent::setUp(); - - ResourceLoader::clearCache(); - - $this->setMwGlobals( array( + public static function getSettings() { + return array( // For ResourceLoader::inDebugMode since it doesn't have context - 'wgResourceLoaderDebug' => true, + 'ResourceLoaderDebug' => true, // Avoid influence from wgInvalidateCacheOnLocalSettingsChange - 'wgCacheEpoch' => '20140101000000', + 'CacheEpoch' => '20140101000000', // For ResourceLoader::__construct() - 'wgResourceLoaderSources' => array(), + 'ResourceLoaderSources' => array(), // For wfScript() - 'wgScriptPath' => '/w', - 'wgScriptExtension' => '.php', - 'wgScript' => '/w/index.php', - 'wgLoadScript' => '/w/load.php', - ) ); + 'ScriptPath' => '/w', + 'ScriptExtension' => '.php', + 'Script' => '/w/index.php', + 'LoadScript' => '/w/load.php', + ); + } + + protected function setUp() { + parent::setUp(); + + ResourceLoader::clearCache(); + + $globals = array(); + foreach ( self::getSettings() as $key => $value ) { + $globals['wg' . $key] = $value; + } + $this->setMwGlobals( $globals ); } } @@ -68,14 +76,14 @@ class ResourceLoaderTestModule extends ResourceLoaderModule { } public function getScript( ResourceLoaderContext $context ) { - return $this->script; + return $this->validateScriptFile( 'input', $this->script ); } public function getStyles( ResourceLoaderContext $context ) { return array( '' => $this->styles ); } - public function getDependencies() { + public function getDependencies( ResourceLoaderContext $context = null ) { return $this->dependencies; } @@ -94,6 +102,10 @@ class ResourceLoaderTestModule extends ResourceLoaderModule { public function isRaw() { return $this->isRaw; } + + public function enableModuleContentVersion() { + return true; + } } class ResourceLoaderFileModuleTestModule extends ResourceLoaderFileModule { diff --git a/tests/phpunit/data/css/comments.css b/tests/phpunit/data/css/comments.css new file mode 100644 index 00000000..744a14c7 --- /dev/null +++ b/tests/phpunit/data/css/comments.css @@ -0,0 +1,7 @@ +/* url expressions in comments should be ignored */ + +.selector { /*@noflip*/ background-image: /*@embed*/ url(not-commented.gif); } + +/* +.selector { background-image: url(commented-out.gif); } +*/ diff --git a/tests/phpunit/data/helpers/WellProtectedClass.php b/tests/phpunit/data/helpers/WellProtectedClass.php index 99c7f642..a45cfbbf 100644 --- a/tests/phpunit/data/helpers/WellProtectedClass.php +++ b/tests/phpunit/data/helpers/WellProtectedClass.php @@ -1,20 +1,47 @@ <?php -class WellProtectedClass { +class WellProtectedParentClass { + private $privateParentProperty; + + public function __construct() { + $this->privateParentProperty = 9000; + } + + private function incrementPrivateParentPropertyValue() { + $this->privateParentProperty++; + } + + public function getPrivateParentProperty() { + return $this->privateParentProperty; + } +} + +class WellProtectedClass extends WellProtectedParentClass { protected $property; + private $privateProperty; public function __construct() { + parent::__construct(); $this->property = 1; + $this->privateProperty = 42; } protected function incrementPropertyValue() { $this->property++; } + private function incrementPrivatePropertyValue() { + $this->privateProperty++; + } + public function getProperty() { return $this->property; } + public function getPrivateProperty() { + return $this->privateProperty; + } + protected function whatSecondArg( $a, $b = false ) { return $b; } diff --git a/tests/phpunit/data/import/ImportLinkCacheIntegrationTest.xml b/tests/phpunit/data/import/ImportLinkCacheIntegrationTest.xml new file mode 100644 index 00000000..8949f406 --- /dev/null +++ b/tests/phpunit/data/import/ImportLinkCacheIntegrationTest.xml @@ -0,0 +1,43 @@ +<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.6/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.6/ http://www.mediawiki.org/xml/export-0.6.xsd" version="0.6" xml:lang="en-gb"> + <siteinfo> + <sitename>MW-19</sitename> + <base>http://localhost:8080/w/index.php/Main_Page</base> + <generator>MediaWiki 1.19.7</generator> + <case>first-letter</case> + </siteinfo> + <page> + <title>Lorem ipsum</title> + <ns>0</ns> + <id>493</id> + <sha1>94lztkh4kgb0mvjr87iyjfq4iv7ltlh</sha1> + <revision> + <id>1358</id> + <timestamp>2014-04-04T22:55:04Z</timestamp> + <contributor> + <username>Tester</username> + <id>1</id> + </contributor> + <text xml:space="preserve" bytes="979">[[Has text::Lorem ipsum dolor sit amet consectetuer Maecenas adipiscing Pellentesque id sem]]. [[Has page::Elit Aliquam urna interdum]] morbi faucibus id tellus ipsum semper wisi. [[Has page::Platea enim hendrerit]] pellentesque consectetuer scelerisque Sed est felis felis quis. Auctor Proin In dolor id et ipsum vel at vitae ut. Praesent elit convallis Praesent aliquet pellentesque vel dolor pellentesque lacinia vitae. At tortor lacus Sed In interdum pulvinar et. + +[[Has number::1001]] [[Has quantity::10.25 km²]] [[Has date::1 Jan 2014]] [[Has Url::http://loremipsum.org/]] [[Has annotation uri::http://loremipsum.org/foaf.rdf]] [[Has email::Lorem@ipsum.org]] [[Has temperature::100 °C]] [[Has boolean::true]] + +[[Category:Lorem ipsum]]</text> + </revision> + </page> + <page> + <title>Category:Lorem ipsum</title> + <ns>14</ns> + <id>496</id> + <sha1>sir97j6uzt9ev2uyhaz1aj4i3spogih</sha1> + <revision> + <id>1355</id> + <timestamp>2014-04-04T22:29:18Z</timestamp> + <contributor> + <username>Tester</username> + <id>1</id> + </contributor> + <text xml:space="preserve" bytes="17">[[Category:Main]]</text> + </revision> + </page> +</mediawiki> + diff --git a/tests/phpunit/data/less/common/test.common.mixins.less b/tests/phpunit/data/less/common/test.common.mixins.less index 2fbe9b79..40647291 100644 --- a/tests/phpunit/data/less/common/test.common.mixins.less +++ b/tests/phpunit/data/less/common/test.common.mixins.less @@ -1,5 +1,4 @@ .test-mixin (@value) { color: @value; border: @foo solid @Foo; - line-height: test-sum(@bar, 10, 20); } diff --git a/tests/phpunit/data/less/module/styles.css b/tests/phpunit/data/less/module/styles.css index b78780a9..bac695b9 100644 --- a/tests/phpunit/data/less/module/styles.css +++ b/tests/phpunit/data/less/module/styles.css @@ -1,6 +1,5 @@ /* @noflip */ .unit-tests { - color: green; + color: #008000; border: 2px solid #eeeeee; - line-height: 35; } diff --git a/tests/phpunit/data/media/2_webp_a.webp b/tests/phpunit/data/media/2_webp_a.webp Binary files differnew file mode 100644 index 00000000..8764f066 --- /dev/null +++ b/tests/phpunit/data/media/2_webp_a.webp diff --git a/tests/phpunit/data/media/2_webp_ll.webp b/tests/phpunit/data/media/2_webp_ll.webp Binary files differnew file mode 100644 index 00000000..5794bbf2 --- /dev/null +++ b/tests/phpunit/data/media/2_webp_ll.webp diff --git a/tests/phpunit/data/media/srgb.jpg b/tests/phpunit/data/media/srgb.jpg Binary files differnew file mode 100644 index 00000000..b965dc4f --- /dev/null +++ b/tests/phpunit/data/media/srgb.jpg diff --git a/tests/phpunit/data/media/tinyrgb.icc b/tests/phpunit/data/media/tinyrgb.icc Binary files differnew file mode 100644 index 00000000..eab973f5 --- /dev/null +++ b/tests/phpunit/data/media/tinyrgb.icc diff --git a/tests/phpunit/data/media/tinyrgb.jpg b/tests/phpunit/data/media/tinyrgb.jpg Binary files differnew file mode 100644 index 00000000..12a8e09f --- /dev/null +++ b/tests/phpunit/data/media/tinyrgb.jpg diff --git a/tests/phpunit/data/media/webp_animated.webp b/tests/phpunit/data/media/webp_animated.webp Binary files differnew file mode 100644 index 00000000..25c6a4dd --- /dev/null +++ b/tests/phpunit/data/media/webp_animated.webp diff --git a/tests/phpunit/data/templates/bad_partial.mustache b/tests/phpunit/data/templates/bad_partial.mustache new file mode 100644 index 00000000..d2767f0f --- /dev/null +++ b/tests/phpunit/data/templates/bad_partial.mustache @@ -0,0 +1 @@ +Partial {{>nonexistenttemplate}} in here diff --git a/tests/phpunit/data/templates/has_partial.mustache b/tests/phpunit/data/templates/has_partial.mustache new file mode 100644 index 00000000..504387a4 --- /dev/null +++ b/tests/phpunit/data/templates/has_partial.mustache @@ -0,0 +1 @@ +Partial {{>foobar_args}} in here diff --git a/tests/phpunit/includes/BlockTest.php b/tests/phpunit/includes/BlockTest.php index 19741621..7b0de866 100644 --- a/tests/phpunit/includes/BlockTest.php +++ b/tests/phpunit/includes/BlockTest.php @@ -38,9 +38,13 @@ class BlockTest extends MediaWikiLangTestCase { $oldBlock->delete(); } - $this->block = new Block( 'UTBlockee', $user->getID(), 0, - 'Parce que', 0, false, time() + 100500 + $blockOptions = array( + 'address' => 'UTBlockee', + 'user' => $user->getID(), + 'reason' => 'Parce que', + 'expiry' => time() + 100500, ); + $this->block = new Block( $blockOptions ); $this->madeAt = wfTimestamp( TS_MW ); $this->block->insert(); @@ -151,22 +155,19 @@ class BlockTest extends MediaWikiLangTestCase { ); // Foreign perspective (blockee not on current wiki)... - $block = new Block( - /* $address */ $username, - /* $user */ 14146, - /* $by */ 0, - /* $reason */ 'crosswiki block...', - /* $timestamp */ wfTimestampNow(), - /* $auto */ false, - /* $expiry */ $this->db->getInfinity(), - /* anonOnly */ false, - /* $createAccount */ true, - /* $enableAutoblock */ true, - /* $hideName (ipb_deleted) */ true, - /* $blockEmail */ true, - /* $allowUsertalk */ false, - /* $byName */ 'MetaWikiUser' + $blockOptions = array( + 'address' => $username, + 'user' => 14146, + 'reason' => 'crosswiki block...', + 'timestamp' => wfTimestampNow(), + 'expiry' => $this->db->getInfinity(), + 'createAccount' => true, + 'enableAutoblock' => true, + 'hideName' => true, + 'blockEmail' => true, + 'byText' => 'MetaWikiUser', ); + $block = new Block( $blockOptions ); $block->insert(); // Reload block from DB @@ -208,22 +209,19 @@ class BlockTest extends MediaWikiLangTestCase { $this->db->update( 'user', array( 'user_id' => 14146 ), array( 'user_id' => $user->getId() ) ); // Foreign perspective (blockee not on current wiki)... - $block = new Block( - /* $address */ 'UserOnForeignWiki', - /* $user */ 14146, - /* $by */ 0, - /* $reason */ 'crosswiki block...', - /* $timestamp */ wfTimestampNow(), - /* $auto */ false, - /* $expiry */ $this->db->getInfinity(), - /* anonOnly */ false, - /* $createAccount */ true, - /* $enableAutoblock */ true, - /* $hideName (ipb_deleted) */ true, - /* $blockEmail */ true, - /* $allowUsertalk */ false, - /* $byName */ 'MetaWikiUser' + $blockOptions = array( + 'address' => 'UserOnForeignWiki', + 'user' => 14146, + 'reason' => 'crosswiki block...', + 'timestamp' => wfTimestampNow(), + 'expiry' => $this->db->getInfinity(), + 'createAccount' => true, + 'enableAutoblock' => true, + 'hideName' => true, + 'blockEmail' => true, + 'byText' => 'MetaWikiUser', ); + $block = new Block( $blockOptions ); $res = $block->insert( $this->db ); $this->assertTrue( (bool)$res['id'], 'Block succeeded' ); @@ -367,4 +365,56 @@ class BlockTest extends MediaWikiLangTestCase { $block = Block::chooseBlock( $xffblocks, $list ); $this->assertEquals( $exResult, $block->mReason, 'Correct block type for XFF header ' . $xff ); } + + public function testDeprecatedConstructor() { + $this->hideDeprecated( 'Block::__construct with multiple arguments' ); + $username = 'UnthinkablySecretRandomUsername'; + $reason = 'being irrational'; + + # Set up the target + $u = User::newFromName( $username ); + if ( $u->getID() == 0 ) { + $u->setPassword( 'TotallyObvious' ); + $u->addToDatabase(); + } + unset( $u ); + + # Make sure the user isn't blocked + $this->assertNull( + Block::newFromTarget( $username ), + "$username should not be blocked" + ); + + # Perform the block + $block = new Block( + /* address */ $username, + /* user */ 0, + /* by */ 0, + /* reason */ $reason, + /* timestamp */ 0, + /* auto */ false, + /* expiry */ 0 + ); + $block->insert(); + + # Check target + $this->assertEquals( + $block->getTarget()->getName(), + $username, + "Target should be set properly" + ); + + # Check supplied parameter + $this->assertEquals( + $block->mReason, + $reason, + "Reason should be non-default" + ); + + # Check default parameter + $this->assertFalse( + (bool)$block->prevents( 'createaccount' ), + "Account creation should not be blocked by default" + ); + } } diff --git a/tests/phpunit/includes/ConsecutiveParametersMatcher.php b/tests/phpunit/includes/ConsecutiveParametersMatcher.php new file mode 100644 index 00000000..adf74bb4 --- /dev/null +++ b/tests/phpunit/includes/ConsecutiveParametersMatcher.php @@ -0,0 +1,123 @@ +<?php +/* + * This file is part of the PHPUnit_MockObject package. + * + * (c) Sebastian Bergmann <sebastian@phpunit.de> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Invocation matcher which looks for sets of specific parameters in the invocations. + * + * Checks the parameters of the incoming invocations, the parameter list is + * checked against the defined constraints in $parameters. If the constraint + * is met it will return true in matches(). + * + * It takes a list of match groups and and increases a call index after each invocation. + * So the first invocation uses the first group of constraints, the second the next and so on. + */ +class PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters extends PHPUnit_Framework_MockObject_Matcher_StatelessInvocation +{ + /** + * @var array + */ + private $_parameterGroups = array(); + + /** + * @var array + */ + private $_invocations = array(); + + /** + * @param array $parameterGroups + */ + public function __construct(array $parameterGroups) + { + foreach ($parameterGroups as $index => $parameters) { + foreach ($parameters as $parameter) { + if (!($parameter instanceof \PHPUnit_Framework_Constraint)) { + $parameter = new \PHPUnit_Framework_Constraint_IsEqual($parameter); + } + $this->_parameterGroups[$index][] = $parameter; + } + } + } + + /** + * @return string + */ + public function toString() + { + $text = 'with consecutive parameters'; + + return $text; + } + + /** + * @param PHPUnit_Framework_MockObject_Invocation $invocation + * @return bool + */ + public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) + { + $this->_invocations[] = $invocation; + $callIndex = count($this->_invocations) - 1; + $this->verifyInvocation($invocation, $callIndex); + + return false; + } + + public function verify() + { + foreach ($this->_invocations as $callIndex => $invocation) { + $this->verifyInvocation($invocation, $callIndex); + } + } + + /** + * Verify a single invocation + * + * @param PHPUnit_Framework_MockObject_Invocation $invocation + * @param int $callIndex + * @throws PHPUnit_Framework_ExpectationFailedException + */ + private function verifyInvocation(PHPUnit_Framework_MockObject_Invocation $invocation, $callIndex) + { + + if (isset($this->_parameterGroups[$callIndex])) { + $parameters = $this->_parameterGroups[$callIndex]; + } else { + // no parameter assertion for this call index + return; + } + + if ($invocation === null) { + throw new PHPUnit_Framework_ExpectationFailedException( + 'Mocked method does not exist.' + ); + } + + if (count($invocation->parameters) < count($parameters)) { + throw new PHPUnit_Framework_ExpectationFailedException( + sprintf( + 'Parameter count for invocation %s is too low.', + $invocation->toString() + ) + ); + } + + foreach ($parameters as $i => $parameter) { + $parameter->evaluate( + $invocation->parameters[$i], + sprintf( + 'Parameter %s for invocation #%d %s does not match expected ' . + 'value.', + $i, + $callIndex, + $invocation->toString() + ) + ); + } + } +} diff --git a/tests/phpunit/includes/EditPageTest.php b/tests/phpunit/includes/EditPageTest.php index 15778e40..27959b1d 100644 --- a/tests/phpunit/includes/EditPageTest.php +++ b/tests/phpunit/includes/EditPageTest.php @@ -11,6 +11,28 @@ */ class EditPageTest extends MediaWikiLangTestCase { + protected function setUp() { + global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang; + + parent::setUp(); + + $this->setMwGlobals( array( + 'wgExtraNamespaces' => $wgExtraNamespaces, + 'wgNamespaceContentModels' => $wgNamespaceContentModels, + 'wgContentHandlers' => $wgContentHandlers, + 'wgContLang' => $wgContLang, + ) ); + + $wgExtraNamespaces[12312] = 'Dummy'; + $wgExtraNamespaces[12313] = 'Dummy_talk'; + + $wgNamespaceContentModels[12312] = "testing"; + $wgContentHandlers["testing"] = 'DummyContentHandlerForTesting'; + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + } + /** * @dataProvider provideExtractSectionTitle * @covers EditPage::extractSectionTitle @@ -499,4 +521,37 @@ hello $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Berta', $bertasEdit, $expectedCode, $expectedText, $message ); } + + /** + * @depends testAutoMerge + */ + public function testCheckDirectEditingDisallowed_forNonTextContent() { + $title = Title::newFromText( 'Dummy:NonTextPageForEditPage' ); + $page = WikiPage::factory( $title ); + + $article = new Article( $title ); + $article->getContext()->setTitle( $title ); + $ep = new EditPage( $article ); + $ep->setContextTitle( $title ); + + $user = $GLOBALS['wgUser']; + + $edit = array( + 'wpTextbox1' => serialize( 'non-text content' ), + 'wpEditToken' => $user->getEditToken(), + 'wpEdittime' => '', + 'wpStarttime' => wfTimestampNow() + ); + + $req = new FauxRequest( $edit, true ); + $ep->importFormData( $req ); + + $this->setExpectedException( + 'MWException', + 'This content model is not supported: testing' + ); + + $ep->internalAttemptSave( $result, false ); + } + } diff --git a/tests/phpunit/includes/ExtraParserTest.php b/tests/phpunit/includes/ExtraParserTest.php index 4a4130e0..7b60fb3f 100644 --- a/tests/phpunit/includes/ExtraParserTest.php +++ b/tests/phpunit/includes/ExtraParserTest.php @@ -2,6 +2,8 @@ /** * Parser-related tests that don't suit for parserTests.txt + * + * @group Database */ class ExtraParserTest extends MediaWikiTestCase { @@ -20,7 +22,6 @@ class ExtraParserTest extends MediaWikiTestCase { 'wgContLang' => $contLang, 'wgLang' => Language::factory( 'en' ), 'wgMemc' => new EmptyBagOStuff, - 'wgAlwaysUseTidy' => false, 'wgCleanSignatures' => true, ) ); diff --git a/tests/phpunit/includes/FauxRequestTest.php b/tests/phpunit/includes/FauxRequestTest.php index 745a5b42..07214b21 100644 --- a/tests/phpunit/includes/FauxRequestTest.php +++ b/tests/phpunit/includes/FauxRequestTest.php @@ -6,13 +6,46 @@ class FauxRequestTest extends MediaWikiTestCase { * @covers FauxRequest::getHeader */ public function testGetSetHeader() { - $value = 'test/test'; + $value = 'text/plain, text/html'; $request = new FauxRequest(); - $request->setHeader( 'Content-Type', $value ); + $request->setHeader( 'Accept', $value ); - $this->assertEquals( $request->getHeader( 'Content-Type' ), $value ); - $this->assertEquals( $request->getHeader( 'CONTENT-TYPE' ), $value ); - $this->assertEquals( $request->getHeader( 'content-type' ), $value ); + $this->assertEquals( $request->getHeader( 'Nonexistent' ), false ); + $this->assertEquals( $request->getHeader( 'Accept' ), $value ); + $this->assertEquals( $request->getHeader( 'ACCEPT' ), $value ); + $this->assertEquals( $request->getHeader( 'accept' ), $value ); + $this->assertEquals( + $request->getHeader( 'Accept', WebRequest::GETHEADER_LIST ), + array( 'text/plain', 'text/html' ) + ); + } + + /** + * @covers FauxRequest::getAllHeaders + */ + public function testGetAllHeaders() { + $_SERVER['HTTP_TEST'] = 'Example'; + + $request = new FauxRequest(); + + $this->assertEquals( + array(), + $request->getAllHeaders() + ); + } + + /** + * @covers FauxRequest::getHeader + */ + public function testGetHeader() { + $_SERVER['HTTP_TEST'] = 'Example'; + + $request = new FauxRequest(); + + $this->assertEquals( + false, + $request->getHeader( 'test' ) + ); } } diff --git a/tests/phpunit/includes/FauxResponseTest.php b/tests/phpunit/includes/FauxResponseTest.php index 4a974ba2..39a0effa 100644 --- a/tests/phpunit/includes/FauxResponseTest.php +++ b/tests/phpunit/includes/FauxResponseTest.php @@ -108,6 +108,13 @@ class FauxResponseTest extends MediaWikiTestCase { 'Third parameter overrides the HTTP/... header' ); + $this->response->statusHeader( 210 ); + $this->assertEquals( + 210, + $this->response->getStatusCode(), + 'Handle statusHeader method' + ); + $this->response->header( 'Location: http://localhost/', false, 206 ); $this->assertEquals( 206, diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php index 1e30273e..e89e36f6 100644 --- a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php +++ b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php @@ -666,9 +666,9 @@ class GlobalTest extends MediaWikiTestCase { public function testWfMkdirParents() { // Should not return true if file exists instead of directory $fname = $this->getNewTempFile(); - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $ok = wfMkdirParents( $fname ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); $this->assertFalse( $ok ); } @@ -687,6 +687,105 @@ class GlobalTest extends MediaWikiTestCase { $this->assertEquals( $expected, $actual, $description ); } + public function wfWikiID() { + $this->setMwGlobals( array( + 'wgDBname' => 'example', + 'wgDBprefix' => '', + ) ); + $this->assertEquals( + wfWikiID(), + 'example' + ); + + $this->setMwGlobals( array( + 'wgDBname' => 'example', + 'wgDBprefix' => 'mw_', + ) ); + $this->assertEquals( + wfWikiID(), + 'example-mw_' + ); + } + + public function testWfMemcKey() { + // Just assert the exact output so we can catch unintentional changes to key + // construction, which would effectively invalidate all existing cache. + + $this->setMwGlobals( array( + 'wgCachePrefix' => false, + 'wgDBname' => 'example', + 'wgDBprefix' => '', + ) ); + $this->assertEquals( + wfMemcKey( 'foo', '123', 'bar' ), + 'example:foo:123:bar' + ); + + $this->setMwGlobals( array( + 'wgCachePrefix' => false, + 'wgDBname' => 'example', + 'wgDBprefix' => 'mw_', + ) ); + $this->assertEquals( + wfMemcKey( 'foo', '123', 'bar' ), + 'example-mw_:foo:123:bar' + ); + + $this->setMwGlobals( array( + 'wgCachePrefix' => 'custom', + 'wgDBname' => 'example', + 'wgDBprefix' => 'mw_', + ) ); + $this->assertEquals( + wfMemcKey( 'foo', '123', 'bar' ), + 'custom:foo:123:bar' + ); + } + + public function testWfForeignMemcKey() { + $this->setMwGlobals( array( + 'wgCachePrefix' => false, + 'wgDBname' => 'example', + 'wgDBprefix' => '', + ) ); + $local = wfMemcKey( 'foo', 'bar' ); + + $this->setMwGlobals( array( + 'wgDBname' => 'other', + 'wgDBprefix' => 'mw_', + ) ); + $this->assertEquals( + wfForeignMemcKey( 'example', '', 'foo', 'bar' ), + $local, + 'Match output of wfMemcKey from local wiki' + ); + } + + public function testWfGlobalCacheKey() { + $this->setMwGlobals( array( + 'wgCachePrefix' => 'ignored', + 'wgDBname' => 'example', + 'wgDBprefix' => '' + ) ); + $one = wfGlobalCacheKey( 'some', 'thing' ); + $this->assertEquals( + $one, + 'global:some:thing' + ); + + $this->setMwGlobals( array( + 'wgDBname' => 'other', + 'wgDBprefix' => 'mw_' + ) ); + $two = wfGlobalCacheKey( 'some', 'thing' ); + + $this->assertEquals( + $one, + $two, + 'Not fragmented by wiki id' + ); + } + public static function provideWfShellWikiCmdList() { global $wgPhpCli; diff --git a/tests/phpunit/includes/GlobalFunctions/wfArrayPlus2dTest.php b/tests/phpunit/includes/GlobalFunctions/wfArrayPlus2dTest.php new file mode 100644 index 00000000..88875bb0 --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfArrayPlus2dTest.php @@ -0,0 +1,94 @@ +<?php +/** + * @group GlobalFunctions + * @covers ::wfArrayPlus2d + */ +class WfArrayPlus2dTest extends MediaWikiTestCase { + /** + * @dataProvider provideArrays + */ + public function testWfArrayPlus2d( $baseArray, $newValues, $expected, $testName ) { + $this->assertEquals( + $expected, + wfArrayPlus2d( $baseArray, $newValues ), + $testName + ); + } + + /** + * Provider for testing wfArrayPlus2d + * + * @return array + */ + public static function provideArrays() { + return array( + // target array, new values array, expected result + array( + array( 0 => '1dArray' ), + array( 1 => '1dArray' ), + array( 0 => '1dArray', 1 => '1dArray' ), + "Test simple union of two arrays with different keys", + ), + array( + array( + 0 => array( 0 => '2dArray' ), + ), + array( + 0 => array( 1 => '2dArray' ), + ), + array( + 0 => array( 0 => '2dArray', 1 => '2dArray' ), + ), + "Test union of 2d arrays with different keys in the value array", + ), + array( + array( + 0 => array( 0 => '2dArray' ), + ), + array( + 0 => array( 0 => '1dArray' ), + ), + array( + 0 => array( 0 => '2dArray' ), + ), + "Test union of 2d arrays with same keys in the value array", + ), + array( + array( + 0 => array( 0 => array( 0 => '3dArray' ) ), + ), + array( + 0 => array( 0 => array( 1 => '2dArray' ) ), + ), + array( + 0 => array( 0 => array( 0 => '3dArray' ) ), + ), + "Test union of 3d array with different keys", + ), + array( + array( + 0 => array( 0 => array( 0 => '3dArray' ) ), + ), + array( + 0 => array( 1 => array( 0 => '2dArray' ) ), + ), + array( + 0 => array( 0 => array( 0 => '3dArray' ), 1 => array( 0 => '2dArray' ) ), + ), + "Test union of 3d array with different keys in the value array", + ), + array( + array( + 0 => array( 0 => array( 0 => '3dArray' ) ), + ), + array( + 0 => array( 0 => array( 0 => '2dArray' ) ), + ), + array( + 0 => array( 0 => array( 0 => '3dArray' ) ), + ), + "Test union of 3d array with same keys in the value array", + ), + ); + } +} diff --git a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php index bea496c4..4ce51c6a 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php @@ -21,6 +21,7 @@ class WfTimestampTest extends MediaWikiTestCase { array( -30281104, TS_MW, '19690115123456', 'Negative TS_UNIX to TS_MW' ), array( $t, TS_UNIX, 979562096, 'TS_UNIX to TS_UNIX' ), array( $t, TS_DB, '2001-01-15 12:34:56', 'TS_UNIX to TS_DB' ), + array( $t + .01, TS_MW, '20010115123456', 'TS_UNIX float to TS_MW' ), array( $t, TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_ISO_8601_BASIC to TS_DB' ), diff --git a/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php b/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php index d11668b7..d4df7b00 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php @@ -112,6 +112,8 @@ class WfUrlencodeTest extends MediaWikiTestCase { ### Other tests // slash remain unchanged. %2F seems to break things array( '/', '/' ), + // T105265 + array( '~', '~' ), // Other 'funnies' chars array( '[]', '%5B%5D' ), diff --git a/tests/phpunit/includes/ImportLinkCacheIntegrationTest.php b/tests/phpunit/includes/ImportLinkCacheIntegrationTest.php new file mode 100644 index 00000000..1433b898 --- /dev/null +++ b/tests/phpunit/includes/ImportLinkCacheIntegrationTest.php @@ -0,0 +1,112 @@ +<?php +/** + * Integration test that checks import success and + * LinkCache integration. + * + * @group medium + * @group Database + * + * @author mwjames + */ +class ImportLinkCacheIntegrationTest extends MediaWikiTestCase { + + private $importStreamSource; + + protected function setUp() { + parent::setUp(); + + $file = dirname( __DIR__ ) . '/data/import/ImportLinkCacheIntegrationTest.xml'; + + $this->importStreamSource = ImportStreamSource::newFromFile( $file ); + + if ( !$this->importStreamSource->isGood() ) { + throw new Exception( "Import source for {$file} failed" ); + } + } + + public function testImportForImportSource() { + + $this->doImport( $this->importStreamSource ); + + // Imported title + $loremIpsum = Title::newFromText( 'Lorem ipsum' ); + + $this->assertSame( + $loremIpsum->getArticleID(), + $loremIpsum->getArticleID( Title::GAID_FOR_UPDATE ) + ); + + $categoryLoremIpsum = Title::newFromText( 'Category:Lorem ipsum' ); + + $this->assertSame( + $categoryLoremIpsum->getArticleID(), + $categoryLoremIpsum->getArticleID( Title::GAID_FOR_UPDATE ) + ); + + $page = new WikiPage( $loremIpsum ); + $page->doDeleteArticle( 'import test: delete page' ); + + $page = new WikiPage( $categoryLoremIpsum ); + $page->doDeleteArticle( 'import test: delete page' ); + } + + /** + * @depends testImportForImportSource + */ + public function testReImportForImportSource() { + + $this->doImport( $this->importStreamSource ); + + // ReImported title + $loremIpsum = Title::newFromText( 'Lorem ipsum' ); + + $this->assertSame( + $loremIpsum->getArticleID(), + $loremIpsum->getArticleID( Title::GAID_FOR_UPDATE ) + ); + + $categoryLoremIpsum = Title::newFromText( 'Category:Lorem ipsum' ); + + $this->assertSame( + $categoryLoremIpsum->getArticleID(), + $categoryLoremIpsum->getArticleID( Title::GAID_FOR_UPDATE ) + ); + } + + private function doImport( $importStreamSource ) { + + $importer = new WikiImporter( + $importStreamSource->value, + ConfigFactory::getDefaultInstance()->makeConfig( 'main' ) + ); + $importer->setDebug( true ); + + $reporter = new ImportReporter( + $importer, + false, + '', + false + ); + + $reporter->setContext( new RequestContext() ); + $reporter->open(); + $exception = false; + + try { + $importer->doImport(); + } catch ( Exception $e ) { + $exception = $e; + } + + $result = $reporter->close(); + + $this->assertFalse( + $exception + ); + + $this->assertTrue( + $result->isGood() + ); + } + +} diff --git a/tests/phpunit/includes/LinkFilterTest.php b/tests/phpunit/includes/LinkFilterTest.php index f2c9cb43..68081059 100644 --- a/tests/phpunit/includes/LinkFilterTest.php +++ b/tests/phpunit/includes/LinkFilterTest.php @@ -76,7 +76,7 @@ class LinkFilterTest extends MediaWikiLangTestCase { array( 'https://', '*.com', 'https://name:pass@secure.com/index.html' ), array( 'http://', 'name:pass@test.com', 'http://test.com' ), array( 'http://', 'test.com', 'http://name:pass@test.com' ), - array( 'http://', '*.test.com', 'http://a.b.c.test.com/dir/dir/file?a=6'), + array( 'http://', '*.test.com', 'http://a.b.c.test.com/dir/dir/file?a=6' ), array( null, 'http://*.test.com', 'http://www.test.com' ), array( 'mailto:', 'name@mail.test123.com', 'mailto:name@mail.test123.com' ), array( '', @@ -122,8 +122,8 @@ class LinkFilterTest extends MediaWikiLangTestCase { array( '', 'git://github.com/prwef/abc-def.git', 'git://github.com/prwef/abc-def.git' ), array( 'git://', 'github.com/', 'git://github.com/prwef/abc-def.git' ), array( 'git://', '*.github.com/', 'git://a.b.c.d.e.f.github.com/prwef/abc-def.git' ), - array( '', 'gopher://*.test.com/', 'gopher://gopher.test.com/0/v2/vstat'), - array( 'telnet://', '*.test.com', 'telnet://shell.test.com/~home/'), + array( '', 'gopher://*.test.com/', 'gopher://gopher.test.com/0/v2/vstat' ), + array( 'telnet://', '*.test.com', 'telnet://shell.test.com/~home/' ), // // The following only work in PHP >= 5.3.7, due to a bug in parse_url which eats @@ -243,10 +243,10 @@ class LinkFilterTest extends MediaWikiLangTestCase { array( 'http://*.test.*' ), array( 'http://*test.com' ), array( 'https://*' ), - array( '*://test.com'), + array( '*://test.com' ), array( 'mailto:name:pass@t*est.com' ), - array( 'http://*:888/'), - array( '*http://'), + array( 'http://*:888/' ), + array( '*http://' ), array( 'test.com/*/index' ), array( 'test.com/dir/index?arg=*' ), ); diff --git a/tests/phpunit/includes/LinkerTest.php b/tests/phpunit/includes/LinkerTest.php index 823c9330..a3efbb8d 100644 --- a/tests/phpunit/includes/LinkerTest.php +++ b/tests/phpunit/includes/LinkerTest.php @@ -93,12 +93,26 @@ class LinkerTest extends MediaWikiLangTestCase { * @covers Linker::formatAutocomments * @covers Linker::formatLinksInComment */ - public function testFormatComment( $expected, $comment, $title = false, $local = false ) { + public function testFormatComment( $expected, $comment, $title = false, $local = false, $wikiId = null ) { + $conf = new SiteConfiguration(); + $conf->settings = array( + 'wgServer' => array( + 'enwiki' => '//en.example.org', + 'dewiki' => '//de.example.org', + ), + 'wgArticlePath' => array( + 'enwiki' => '/w/$1', + 'dewiki' => '/w/$1', + ), + ); + $conf->suffixes = array( 'wiki' ); + $this->setMwGlobals( array( 'wgScript' => '/wiki/index.php', 'wgArticlePath' => '/wiki/$1', 'wgWellFormedXml' => true, 'wgCapitalLinks' => true, + 'wgConf' => $conf, ) ); if ( $title === false ) { @@ -108,11 +122,13 @@ class LinkerTest extends MediaWikiLangTestCase { $this->assertEquals( $expected, - Linker::formatComment( $comment, $title, $local ) + Linker::formatComment( $comment, $title, $local, $wikiId ) ); } - public static function provideCasesForFormatComment() { + public function provideCasesForFormatComment() { + $wikiId = 'enwiki'; // $wgConf has a fake entry for this + return array( // Linker::formatComment array( @@ -127,6 +143,10 @@ class LinkerTest extends MediaWikiLangTestCase { "'''not bolded'''", "'''not bolded'''", ), + array( + "try <script>evil</scipt> things", + "try <script>evil</scipt> things", + ), // Linker::formatAutocomments array( '<a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→</a><span dir="auto"><span class="autocomment">autocomment</span></span>', @@ -157,6 +177,14 @@ class LinkerTest extends MediaWikiLangTestCase { "/* autocomment containing /* */ T70361" ), array( + '<a href="/wiki/Special:BlankPage#autocomment_containing_.22quotes.22" title="Special:BlankPage">→</a><span dir="auto"><span class="autocomment">autocomment containing "quotes"</span></span>', + "/* autocomment containing \"quotes\" */" + ), + array( + '<a href="/wiki/Special:BlankPage#autocomment_containing_.3Cscript.3Etags.3C.2Fscript.3E" title="Special:BlankPage">→</a><span dir="auto"><span class="autocomment">autocomment containing <script>tags</script></span></span>', + "/* autocomment containing <script>tags</script> */" + ), + array( '<a href="#autocomment">→</a><span dir="auto"><span class="autocomment">autocomment</span></span>', "/* autocomment */", false, true @@ -166,6 +194,16 @@ class LinkerTest extends MediaWikiLangTestCase { "/* autocomment */", null ), + array( + '<a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→</a><span dir="auto"><span class="autocomment">autocomment</span></span>', + "/* autocomment */", + false, false + ), + array( + '<a class="external" rel="nofollow" href="//en.example.org/w/Special:BlankPage#autocomment">→</a><span dir="auto"><span class="autocomment">autocomment</span></span>', + "/* autocomment */", + false, false, $wikiId + ), // Linker::formatLinksInComment array( 'abc <a href="/wiki/index.php?title=Link&action=edit&redlink=1" class="new" title="Link (page does not exist)">link</a> def', @@ -191,6 +229,28 @@ class LinkerTest extends MediaWikiLangTestCase { 'abc <a href="/wiki/index.php?title=/subpage&action=edit&redlink=1" class="new" title="/subpage (page does not exist)">/subpage</a> def', "abc [[/subpage]] def", ), + array( + 'abc <a href="/wiki/index.php?title=%22evil!%22&action=edit&redlink=1" class="new" title=""evil!" (page does not exist)">"evil!"</a> def', + "abc [[\"evil!\"]] def", + ), + array( + 'abc [[<script>very evil</script>]] def', + "abc [[<script>very evil</script>]] def", + ), + array( + 'abc [[|]] def', + "abc [[|]] def", + ), + array( + 'abc <a href="/wiki/index.php?title=Link&action=edit&redlink=1" class="new" title="Link (page does not exist)">link</a> def', + "abc [[link]] def", + false, false + ), + array( + 'abc <a class="external" rel="nofollow" href="//en.example.org/w/Link">link</a> def', + "abc [[link]] def", + false, false, $wikiId + ) ); } diff --git a/tests/phpunit/includes/MediaWikiTest.php b/tests/phpunit/includes/MediaWikiTest.php new file mode 100644 index 00000000..e1962436 --- /dev/null +++ b/tests/phpunit/includes/MediaWikiTest.php @@ -0,0 +1,157 @@ +<?php + +class MediaWikiTest extends MediaWikiTestCase { + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( array( + 'wgServer' => 'http://example.org', + 'wgScriptPath' => '/w', + 'wgScript' => '/w/index.php', + 'wgArticlePath' => '/wiki/$1', + 'wgActionPaths' => array(), + ) ); + } + + public static function provideTryNormaliseRedirect() { + return array( + array( + // View: Canonical + 'url' => 'http://example.org/wiki/Foo_Bar', + 'query' => array(), + 'title' => 'Foo_Bar', + 'redirect' => false, + ), + array( + // View: Escaped title + 'url' => 'http://example.org/wiki/Foo%20Bar', + 'query' => array(), + 'title' => 'Foo_Bar', + 'redirect' => 'http://example.org/wiki/Foo_Bar', + ), + array( + // View: Script path + 'url' => 'http://example.org/w/index.php?title=Foo_Bar', + 'query' => array( 'title' => 'Foo_Bar' ), + 'title' => 'Foo_Bar', + 'redirect' => 'http://example.org/wiki/Foo_Bar', + ), + array( + // View: Script path with implicit title from page id + 'url' => 'http://example.org/w/index.php?curid=123', + 'query' => array( 'curid' => '123' ), + 'title' => 'Foo_Bar', + 'redirect' => false, + ), + array( + // View: Script path with implicit title from revision id + 'url' => 'http://example.org/w/index.php?oldid=123', + 'query' => array( 'oldid' => '123' ), + 'title' => 'Foo_Bar', + 'redirect' => false, + ), + array( + // View: Script path without title + 'url' => 'http://example.org/w/index.php', + 'query' => array(), + 'title' => 'Main_Page', + 'redirect' => 'http://example.org/wiki/Main_Page', + ), + array( + // View: Script path with empty title + 'url' => 'http://example.org/w/index.php?title=', + 'query' => array( 'title' => '' ), + 'title' => 'Main_Page', + 'redirect' => 'http://example.org/wiki/Main_Page', + ), + array( + // View: Index with escaped title + 'url' => 'http://example.org/w/index.php?title=Foo%20Bar', + 'query' => array( 'title' => 'Foo Bar' ), + 'title' => 'Foo_Bar', + 'redirect' => 'http://example.org/wiki/Foo_Bar', + ), + array( + // View: Script path with escaped title + 'url' => 'http://example.org/w/?title=Foo_Bar', + 'query' => array( 'title' => 'Foo_Bar' ), + 'title' => 'Foo_Bar', + 'redirect' => 'http://example.org/wiki/Foo_Bar', + ), + array( + // View: Root path with escaped title + 'url' => 'http://example.org/?title=Foo_Bar', + 'query' => array( 'title' => 'Foo_Bar' ), + 'title' => 'Foo_Bar', + 'redirect' => 'http://example.org/wiki/Foo_Bar', + ), + array( + // View: Canonical with redundant query + 'url' => 'http://example.org/wiki/Foo_Bar?action=view', + 'query' => array( 'action' => 'view' ), + 'title' => 'Foo_Bar', + 'redirect' => 'http://example.org/wiki/Foo_Bar', + ), + array( + // Edit: Canonical view url with action query + 'url' => 'http://example.org/wiki/Foo_Bar?action=edit', + 'query' => array( 'action' => 'edit' ), + 'title' => 'Foo_Bar', + 'redirect' => false, + ), + array( + // View: Index with action query + 'url' => 'http://example.org/w/index.php?title=Foo_Bar&action=view', + 'query' => array( 'title' => 'Foo_Bar', 'action' => 'view' ), + 'title' => 'Foo_Bar', + 'redirect' => 'http://example.org/wiki/Foo_Bar', + ), + array( + // Edit: Index with action query + 'url' => 'http://example.org/w/index.php?title=Foo_Bar&action=edit', + 'query' => array( 'title' => 'Foo_Bar', 'action' => 'edit' ), + 'title' => 'Foo_Bar', + 'redirect' => false, + ), + ); + } + + /** + * @dataProvider provideTryNormaliseRedirect + * @covers MediaWiki::tryNormaliseRedirect + */ + public function testTryNormaliseRedirect( $url, $query, $title, $expectedRedirect = false ) { + // Set SERVER because interpolateTitle() doesn't use getRequestURL(), + // whereas tryNormaliseRedirect does(). + $_SERVER['REQUEST_URI'] = $url; + + $req = new FauxRequest( $query ); + $req->setRequestURL( $url ); + // This adds a virtual 'title' query parameter. Normally called from Setup.php + $req->interpolateTitle(); + + $titleObj = Title::newFromText( $title ); + + // Set global context since some involved code paths don't yet have context + $context = RequestContext::getMain(); + $context->setRequest( $req ); + $context->setTitle( $titleObj ); + + $mw = new MediaWiki( $context ); + + $method = new ReflectionMethod( $mw, 'tryNormaliseRedirect' ); + $method->setAccessible( true ); + $ret = $method->invoke( $mw, $titleObj ); + + $this->assertEquals( + $expectedRedirect !== false, + $ret, + 'Return true only when redirecting' + ); + + $this->assertEquals( + $expectedRedirect ?: '', + $context->getOutput()->getRedirect() + ); + } +} diff --git a/tests/phpunit/includes/MessageTest.php b/tests/phpunit/includes/MessageTest.php index 99ec2e42..9c953a6d 100644 --- a/tests/phpunit/includes/MessageTest.php +++ b/tests/phpunit/includes/MessageTest.php @@ -21,6 +21,17 @@ class MessageTest extends MediaWikiLangTestCase { $this->assertEquals( $key, $message->getKey() ); $this->assertEquals( $params, $message->getParams() ); $this->assertEquals( $expectedLang, $message->getLanguage() ); + + $messageSpecifier = $this->getMockForAbstractClass( 'MessageSpecifier' ); + $messageSpecifier->expects( $this->any() ) + ->method( 'getKey' )->will( $this->returnValue( $key ) ); + $messageSpecifier->expects( $this->any() ) + ->method( 'getParams' )->will( $this->returnValue( $params ) ); + $message = new Message( $messageSpecifier, array(), $language ); + + $this->assertEquals( $key, $message->getKey() ); + $this->assertEquals( $params, $message->getParams() ); + $this->assertEquals( $expectedLang, $message->getLanguage() ); } public static function provideConstructor() { @@ -548,4 +559,26 @@ class MessageTest extends MediaWikiLangTestCase { public function testInLanguageThrows() { wfMessage( 'foo' )->inLanguage( 123 ); } + + /** + * @covers Message::serialize + * @covers Message::unserialize + */ + public function testSerialization() { + $msg = new Message( 'parentheses' ); + $msg->rawParams( '<a>foo</a>' ); + $msg->title( Title::newFromText( 'Testing' ) ); + $this->assertEquals( '(<a>foo</a>)', $msg->parse(), 'Sanity check' ); + $msg = unserialize( serialize( $msg ) ); + $this->assertEquals( '(<a>foo</a>)', $msg->parse() ); + $title = TestingAccessWrapper::newFromObject( $msg )->title; + $this->assertInstanceOf( 'Title', $title ); + $this->assertEquals( 'Testing', $title->getFullText() ); + + $msg = new Message( 'mainpage' ); + $msg->inLanguage( 'de' ); + $this->assertEquals( 'Hauptseite', $msg->plain(), 'Sanity check' ); + $msg = unserialize( serialize( $msg ) ); + $this->assertEquals( 'Hauptseite', $msg->plain() ); + } } diff --git a/tests/phpunit/includes/MimeMagicTest.php b/tests/phpunit/includes/MimeMagicTest.php index 742d3827..3c45f305 100644 --- a/tests/phpunit/includes/MimeMagicTest.php +++ b/tests/phpunit/includes/MimeMagicTest.php @@ -1,5 +1,5 @@ <?php -class MimeMagicTest extends MediaWikiTestCase { +class MimeMagicTest extends PHPUnit_Framework_TestCase { /** @var MimeMagic */ private $mimeMagic; diff --git a/tests/phpunit/includes/MovePageTest.php b/tests/phpunit/includes/MovePageTest.php index 9501e452..0ef2fa63 100644 --- a/tests/phpunit/includes/MovePageTest.php +++ b/tests/phpunit/includes/MovePageTest.php @@ -57,7 +57,7 @@ class MovePageTest extends MediaWikiTestCase { WikiPage::factory( $oldTitle )->getRevision() ); $this->assertNotNull( - WikiPage::factory( $newTitle)->getRevision() + WikiPage::factory( $newTitle )->getRevision() ); } } diff --git a/tests/phpunit/includes/OutputPageTest.php b/tests/phpunit/includes/OutputPageTest.php index 6c6d95ee..f0d905e5 100644 --- a/tests/phpunit/includes/OutputPageTest.php +++ b/tests/phpunit/includes/OutputPageTest.php @@ -141,53 +141,36 @@ class OutputPageTest extends MediaWikiTestCase { // Load module script only array( array( 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS ), - '<script>if(window.mw){ -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"); -}</script> -' + "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n" + . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.foo\u0026only=scripts\u0026skin=fallback");' + . "\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.baz', 'test.foo', 'test.bar' ), ResourceLoaderModule::TYPE_STYLES ), - '<link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.bar%2Cbaz%2Cfoo&only=styles&skin=fallback&*"> -' + + '<link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.bar%2Cbaz%2Cfoo&only=styles&skin=fallback">' ), // Load private module (only=scripts) array( array( 'test.quux', ResourceLoaderModule::TYPE_SCRIPTS ), - '<script>if(window.mw){ -mw.test.baz({token:123});mw.loader.state({"test.quux":"ready"}); - -}</script> -' + "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n" + . "mw.test.baz({token:123});mw.loader.state({\"test.quux\":\"ready\"});\n" + . "} );</script>" ), // Load private module (combined) array( array( 'test.quux', ResourceLoaderModule::TYPE_COMBINED ), - '<script>if(window.mw){ -mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{"css":[".mw-icon{transition:none}\n"]}); - -}</script> -' - ), - // Load module script with ESI - array( - array( 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS, true ), - '<script><esi:include src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.foo&only=scripts&skin=fallback&*" /></script> -' - ), - // Load module styles with ESI - array( - array( 'test.foo', ResourceLoaderModule::TYPE_STYLES, true ), - '<style><esi:include src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.foo&only=styles&skin=fallback&*" /></style> -', + "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n" + . "mw.loader.implement(\"test.quux\",function($,jQuery){" + . "mw.test.baz({token:123});},{\"css\":[\".mw-icon{transition:none}" + . "\"]});\n} );</script>" ), // Load no modules array( @@ -197,19 +180,17 @@ mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{" // noscript group array( array( 'test.noscript', ResourceLoaderModule::TYPE_STYLES ), - '<noscript><link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.noscript&only=styles&skin=fallback&*"></noscript> -' + '<noscript><link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.noscript&only=styles&skin=fallback"></noscript>' ), // Load two modules in separate groups array( array( array( 'test.group.foo', 'test.group.bar' ), ResourceLoaderModule::TYPE_COMBINED ), - '<script>if(window.mw){ -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"); -}</script> -<script>if(window.mw){ -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"); -}</script> -' + "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n" + . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.group.bar\u0026skin=fallback");' + . "\n} );</script>\n" + . "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n" + . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.group.foo\u0026skin=fallback");' + . "\n} );</script>" ), ); } @@ -226,7 +207,6 @@ document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?debug=false\ public function testMakeResourceLoaderLink( $args, $expectedHtml ) { $this->setMwGlobals( array( 'wgResourceLoaderDebug' => false, - 'wgResourceLoaderUseESI' => true, 'wgLoadScript' => 'http://127.0.0.1:8080/w/load.php', // Affects whether CDATA is inserted 'wgWellFormedXml' => false, @@ -244,63 +224,141 @@ document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?debug=false\ 'test.foo' => new ResourceLoaderTestModule( array( 'script' => 'mw.test.foo( { a: true } );', 'styles' => '.mw-test-foo { content: "style"; }', - )), + ) ), 'test.bar' => new ResourceLoaderTestModule( array( 'script' => 'mw.test.bar( { a: true } );', 'styles' => '.mw-test-bar { content: "style"; }', - )), + ) ), 'test.baz' => new ResourceLoaderTestModule( array( 'script' => 'mw.test.baz( { a: true } );', 'styles' => '.mw-test-baz { content: "style"; }', - )), + ) ), 'test.quux' => new ResourceLoaderTestModule( array( 'script' => 'mw.test.baz( { token: 123 } );', 'styles' => '/* pref-animate=off */ .mw-icon { transition: none; }', 'group' => 'private', - )), + ) ), 'test.raw' => new ResourceLoaderTestModule( array( 'script' => 'mw.test.baz( { token: 123 } );', 'isRaw' => true, - )), + ) ), 'test.noscript' => new ResourceLoaderTestModule( array( 'styles' => '.mw-test-noscript { content: "style"; }', 'group' => 'noscript', - )), + ) ), 'test.group.bar' => new ResourceLoaderTestModule( array( 'styles' => '.mw-group-bar { content: "style"; }', 'group' => 'bar', - )), + ) ), 'test.group.foo' => new ResourceLoaderTestModule( array( 'styles' => '.mw-group-foo { content: "style"; }', 'group' => 'foo', - )), + ) ), ) ); $links = $method->invokeArgs( $out, $args ); - // Strip comments to avoid variation due to wgDBname in WikiID and cache key - $actualHtml = preg_replace( '#/\*[^*]+\*/#', '', $links['html'] ); + $actualHtml = implode( "\n", $links['html'] ); $this->assertEquals( $expectedHtml, $actualHtml ); } + + /** + * @dataProvider provideVaryHeaders + * @covers OutputPage::addVaryHeader + * @covers OutputPage::getVaryHeader + * @covers OutputPage::getXVO + */ + public function testVaryHeaders( $calls, $vary, $xvo ) { + // get rid of default Vary fields + $outputPage = $this->getMockBuilder( 'OutputPage' ) + ->setConstructorArgs( array( new RequestContext() ) ) + ->setMethods( array( 'getCacheVaryCookies' ) ) + ->getMock(); + $outputPage->expects( $this->any() ) + ->method( 'getCacheVaryCookies' ) + ->will( $this->returnValue( array() ) ); + TestingAccessWrapper::newFromObject( $outputPage )->mVaryHeader = array(); + + foreach ( $calls as $call ) { + call_user_func_array( array( $outputPage, 'addVaryHeader' ), $call ); + } + $this->assertEquals( $vary, $outputPage->getVaryHeader(), 'Vary:' ); + $this->assertEquals( $xvo, $outputPage->getXVO(), 'X-Vary-Options:' ); + } + + public function provideVaryHeaders() { + // note: getXVO() automatically adds Vary: Cookie + return array( + array( // single header + array( + array( 'Cookie' ), + ), + 'Vary: Cookie', + 'X-Vary-Options: Cookie', + ), + array( // non-unique headers + array( + array( 'Cookie' ), + array( 'Accept-Language' ), + array( 'Cookie' ), + ), + 'Vary: Cookie, Accept-Language', + 'X-Vary-Options: Cookie,Accept-Language', + ), + array( // two headers with single options + array( + array( 'Cookie', array( 'string-contains=phpsessid' ) ), + array( 'Accept-Language', array( 'string-contains=en' ) ), + ), + 'Vary: Cookie, Accept-Language', + 'X-Vary-Options: Cookie;string-contains=phpsessid,Accept-Language;string-contains=en', + ), + array( // one header with multiple options + array( + array( 'Cookie', array( 'string-contains=phpsessid', 'string-contains=userId' ) ), + ), + 'Vary: Cookie', + 'X-Vary-Options: Cookie;string-contains=phpsessid;string-contains=userId', + ), + array( // Duplicate option + array( + array( 'Cookie', array( 'string-contains=phpsessid' ) ), + array( 'Cookie', array( 'string-contains=phpsessid' ) ), + array( 'Accept-Language', array( 'string-contains=en', 'string-contains=en' ) ), + + + ), + 'Vary: Cookie, Accept-Language', + 'X-Vary-Options: Cookie;string-contains=phpsessid,Accept-Language;string-contains=en', + ), + array( // Same header, different options + array( + array( 'Cookie', array( 'string-contains=phpsessid' ) ), + array( 'Cookie', array( 'string-contains=userId' ) ), + ), + 'Vary: Cookie', + 'X-Vary-Options: Cookie;string-contains=phpsessid;string-contains=userId', + ), + ); + } } /** * MessageBlobStore that doesn't do anything */ class NullMessageBlobStore extends MessageBlobStore { - public function get ( ResourceLoader $resourceLoader, $modules, $lang ) { + public function get( ResourceLoader $resourceLoader, $modules, $lang ) { return array(); } - public function insertMessageBlob ( $name, ResourceLoaderModule $module, $lang ) { + public function insertMessageBlob( $name, ResourceLoaderModule $module, $lang ) { return false; } - public function updateModule ( $name, ResourceLoaderModule $module, $lang ) { + public function updateModule( $name, ResourceLoaderModule $module, $lang ) { return; } - public function updateMessage ( $key ) { + public function updateMessage( $key ) { } public function clear() { } } - diff --git a/tests/phpunit/includes/PrefixSearchTest.php b/tests/phpunit/includes/PrefixSearchTest.php index d63541b7..afd10e9a 100644 --- a/tests/phpunit/includes/PrefixSearchTest.php +++ b/tests/phpunit/includes/PrefixSearchTest.php @@ -6,6 +6,11 @@ class PrefixSearchTest extends MediaWikiLangTestCase { public function addDBData() { + if ( !$this->isWikitextNS( NS_MAIN ) ) { + // tests are skipped if NS_MAIN is not wikitext + return; + } + $this->insertPage( 'Sandbox' ); $this->insertPage( 'Bar' ); $this->insertPage( 'Example' ); diff --git a/tests/phpunit/includes/SanitizerTest.php b/tests/phpunit/includes/SanitizerTest.php index c615c460..d3dc512b 100644 --- a/tests/phpunit/includes/SanitizerTest.php +++ b/tests/phpunit/includes/SanitizerTest.php @@ -6,6 +6,11 @@ */ class SanitizerTest extends MediaWikiTestCase { + protected function tearDown() { + MWTidy::destroySingleton(); + parent::tearDown(); + } + /** * @covers Sanitizer::decodeCharReferences */ @@ -93,9 +98,7 @@ class SanitizerTest extends MediaWikiTestCase { * @param bool $escaped Whether sanitizer let the tag in or escape it (ie: '<video>') */ public function testRemovehtmltagsOnHtml5Tags( $tag, $escaped ) { - $this->setMwGlobals( array( - 'wgUseTidy' => false - ) ); + MWTidy::setInstance( false ); if ( $escaped ) { $this->assertEquals( "<$tag>", @@ -157,7 +160,7 @@ class SanitizerTest extends MediaWikiTestCase { * @covers Sanitizer::removeHTMLtags */ public function testRemoveHTMLtags( $input, $output, $msg = null ) { - $GLOBALS['wgUseTidy'] = false; + MWTidy::setInstance( false ); $this->assertEquals( $output, Sanitizer::removeHTMLtags( $input ), $msg ); } @@ -360,5 +363,4 @@ class SanitizerTest extends MediaWikiTestCase { array( '<script>foo</script>', '<script>foo</script>' ), ); } - } diff --git a/tests/phpunit/includes/SanitizerValidateEmailTest.php b/tests/phpunit/includes/SanitizerValidateEmailTest.php index 14911f04..f47e74e2 100644 --- a/tests/phpunit/includes/SanitizerValidateEmailTest.php +++ b/tests/phpunit/includes/SanitizerValidateEmailTest.php @@ -5,7 +5,7 @@ * @todo all test methods in this class should be refactored and... * use a single test method and a single data provider... */ -class SanitizerValidateEmailTest extends MediaWikiTestCase { +class SanitizerValidateEmailTest extends PHPUnit_Framework_TestCase { private function checkEmail( $addr, $expected = true, $msg = '' ) { if ( $msg == '' ) { diff --git a/tests/phpunit/includes/StatusTest.php b/tests/phpunit/includes/StatusTest.php index c013f4fc..291ed315 100644 --- a/tests/phpunit/includes/StatusTest.php +++ b/tests/phpunit/includes/StatusTest.php @@ -372,7 +372,7 @@ class StatusTest extends MediaWikiLangTestCase { ); $status = new Status(); - $status->warning( new Message( 'fooBar!', array( 'foo', 'bar' ) ) ); + $status->warning( new Message( 'fooBar!', array( 'foo', 'bar' ) ) ); $testCases['1MessageWarning'] = array( $status, "<fooBar!>", @@ -449,7 +449,7 @@ class StatusTest extends MediaWikiLangTestCase { // ); $status = new Status(); - $status->warning( new Message( 'fooBar!', array( 'foo', 'bar' ) ) ); + $status->warning( new Message( 'fooBar!', array( 'foo', 'bar' ) ) ); $testCases['1MessageWarning'] = array( $status, array( 'foo', 'bar' ), diff --git a/tests/phpunit/includes/TemplateParserTest.php b/tests/phpunit/includes/TemplateParserTest.php index 81854ff3..3b37f4a4 100644 --- a/tests/phpunit/includes/TemplateParserTest.php +++ b/tests/phpunit/includes/TemplateParserTest.php @@ -57,7 +57,20 @@ class TemplateParserTest extends MediaWikiTestCase { array(), false, 'RuntimeException', - ) + ), + array( + 'has_partial', + array( + 'planet' => 'world', + ), + "Partial hello world!\n in here\n", + ), + array( + 'bad_partial', + array(), + false, + 'Exception', + ), ); } } diff --git a/tests/phpunit/includes/TestingAccessWrapper.php b/tests/phpunit/includes/TestingAccessWrapper.php index 84c0f9b5..63d89719 100644 --- a/tests/phpunit/includes/TestingAccessWrapper.php +++ b/tests/phpunit/includes/TestingAccessWrapper.php @@ -34,16 +34,42 @@ class TestingAccessWrapper { return $methodReflection->invokeArgs( $this->object, $args ); } - public function __set( $name, $value ) { + /** + * ReflectionClass::getProperty() fails if the private property is defined + * in a parent class. This works more like ReflectionClass::getMethod(). + */ + private function getProperty( $name ) { $classReflection = new ReflectionClass( $this->object ); - $propertyReflection = $classReflection->getProperty( $name ); + try { + return $classReflection->getProperty( $name ); + } catch ( ReflectionException $ex ) { + while ( true ) { + $classReflection = $classReflection->getParentClass(); + if ( !$classReflection ) { + throw $ex; + } + try { + $propertyReflection = $classReflection->getProperty( $name ); + } catch ( ReflectionException $ex2 ) { + continue; + } + if ( $propertyReflection->isPrivate() ) { + return $propertyReflection; + } else { + throw $ex; + } + } + } + } + + public function __set( $name, $value ) { + $propertyReflection = $this->getProperty( $name ); $propertyReflection->setAccessible( true ); $propertyReflection->setValue( $this->object, $value ); } public function __get( $name ) { - $classReflection = new ReflectionClass( $this->object ); - $propertyReflection = $classReflection->getProperty( $name ); + $propertyReflection = $this->getProperty( $name ); $propertyReflection->setAccessible( true ); return $propertyReflection->getValue( $this->object ); } diff --git a/tests/phpunit/includes/TestingAccessWrapperTest.php b/tests/phpunit/includes/TestingAccessWrapperTest.php index 7e5b91a1..fc54afae 100644 --- a/tests/phpunit/includes/TestingAccessWrapperTest.php +++ b/tests/phpunit/includes/TestingAccessWrapperTest.php @@ -14,18 +14,36 @@ class TestingAccessWrapperTest extends MediaWikiTestCase { function testGetProperty() { $this->assertSame( 1, $this->wrapped->property ); + $this->assertSame( 42, $this->wrapped->privateProperty ); + $this->assertSame( 9000, $this->wrapped->privateParentProperty ); } function testSetProperty() { $this->wrapped->property = 10; $this->assertSame( 10, $this->wrapped->property ); $this->assertSame( 10, $this->raw->getProperty() ); + + $this->wrapped->privateProperty = 11; + $this->assertSame( 11, $this->wrapped->privateProperty ); + $this->assertSame( 11, $this->raw->getPrivateProperty() ); + + $this->wrapped->privateParentProperty = 12; + $this->assertSame( 12, $this->wrapped->privateParentProperty ); + $this->assertSame( 12, $this->raw->getPrivateParentProperty() ); } function testCallMethod() { $this->wrapped->incrementPropertyValue(); $this->assertSame( 2, $this->wrapped->property ); $this->assertSame( 2, $this->raw->getProperty() ); + + $this->wrapped->incrementPrivatePropertyValue(); + $this->assertSame( 43, $this->wrapped->privateProperty ); + $this->assertSame( 43, $this->raw->getPrivateProperty() ); + + $this->wrapped->incrementPrivateParentPropertyValue(); + $this->assertSame( 9001, $this->wrapped->privateParentProperty ); + $this->assertSame( 9001, $this->raw->getPrivateParentProperty() ); } function testCallMethodTwoArgs() { diff --git a/tests/phpunit/includes/TitleArrayFromResultTest.php b/tests/phpunit/includes/TitleArrayFromResultTest.php index 0f7069ae..6654a5b6 100644 --- a/tests/phpunit/includes/TitleArrayFromResultTest.php +++ b/tests/phpunit/includes/TitleArrayFromResultTest.php @@ -4,7 +4,7 @@ * @author Adam Shorland * @covers TitleArrayFromResult */ -class TitleArrayFromResultTest extends MediaWikiTestCase { +class TitleArrayFromResultTest extends PHPUnit_Framework_TestCase { private function getMockResultWrapper( $row = null, $numRows = 1 ) { $resultWrapper = $this->getMockBuilder( 'ResultWrapper' ) diff --git a/tests/phpunit/includes/TitlePermissionTest.php b/tests/phpunit/includes/TitlePermissionTest.php index 022c7d53..f588ed63 100644 --- a/tests/phpunit/includes/TitlePermissionTest.php +++ b/tests/phpunit/includes/TitlePermissionTest.php @@ -664,7 +664,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->setTitle( NS_MAIN, "test page" ); $this->title->mTitleProtection['permission'] = ''; $this->title->mTitleProtection['user'] = $this->user->getID(); - $this->title->mTitleProtection['expiry'] = wfGetDB( DB_SLAVE )->getInfinity(); + $this->title->mTitleProtection['expiry'] = 'infinity'; $this->title->mTitleProtection['reason'] = 'test'; $this->title->mCascadeRestriction = false; @@ -753,8 +753,14 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $prev = time(); $now = time() + 120; $this->user->mBlockedby = $this->user->getId(); - $this->user->mBlock = new Block( '127.0.8.1', 0, $this->user->getId(), - 'no reason given', $prev + 3600, 1, 0 ); + $this->user->mBlock = new Block( array( + 'address' => '127.0.8.1', + 'by' => $this->user->getId(), + 'reason' => 'no reason given', + 'timestamp' => $prev + 3600, + 'auto' => true, + 'expiry' => 0 + ) ); $this->user->mBlock->mTimestamp = 0; $this->assertEquals( array( array( 'autoblockedtext', '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1', @@ -770,8 +776,14 @@ class TitlePermissionTest extends MediaWikiLangTestCase { global $wgLocalTZoffset; $wgLocalTZoffset = -60; $this->user->mBlockedby = $this->user->getName(); - $this->user->mBlock = new Block( '127.0.8.1', 0, $this->user->getId(), - 'no reason given', $now, 0, 10 ); + $this->user->mBlock = new Block( array( + 'address' => '127.0.8.1', + 'by' => $this->user->getId(), + 'reason' => 'no reason given', + 'timestamp' => $now, + 'auto' => false, + 'expiry' => 10, + ) ); $this->assertEquals( array( array( 'blockedtext', '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1', 'Useruser', null, '23:00, 31 December 1969', '127.0.8.1', diff --git a/tests/phpunit/includes/TitleTest.php b/tests/phpunit/includes/TitleTest.php index d55f958b..a2c6f23d 100644 --- a/tests/phpunit/includes/TitleTest.php +++ b/tests/phpunit/includes/TitleTest.php @@ -1,6 +1,7 @@ <?php /** + * @group Database * @group Title */ class TitleTest extends MediaWikiTestCase { @@ -79,22 +80,22 @@ class TitleTest extends MediaWikiTestCase { public static function provideInvalidSecureAndSplit() { return array( - array( '' ), - array( ':' ), - array( '__ __' ), - array( ' __ ' ), + array( '', 'title-invalid-empty' ), + array( ':', 'title-invalid-empty' ), + array( '__ __', 'title-invalid-empty' ), + array( ' __ ', 'title-invalid-empty' ), // Bad characters forbidden regardless of wgLegalTitleChars - array( 'A [ B' ), - array( 'A ] B' ), - array( 'A { B' ), - array( 'A } B' ), - array( 'A < B' ), - array( 'A > B' ), - array( 'A | B' ), + array( 'A [ B', 'title-invalid-characters' ), + array( 'A ] B', 'title-invalid-characters' ), + array( 'A { B', 'title-invalid-characters' ), + array( 'A } B', 'title-invalid-characters' ), + array( 'A < B', 'title-invalid-characters' ), + array( 'A > B', 'title-invalid-characters' ), + array( 'A | B', 'title-invalid-characters' ), // URL encoding - array( 'A%20B' ), - array( 'A%23B' ), - array( 'A%2523B' ), + array( 'A%20B', 'title-invalid-characters' ), + array( 'A%23B', 'title-invalid-characters' ), + array( 'A%2523B', 'title-invalid-characters' ), // XML/HTML character entity references // Note: Commented out because they are not marked invalid by the PHP test as // Title::newFromText runs Sanitizer::decodeCharReferencesAndNormalize first. @@ -102,29 +103,30 @@ class TitleTest extends MediaWikiTestCase { //'A é B', //'A é B', // Subject of NS_TALK does not roundtrip to NS_MAIN - array( 'Talk:File:Example.svg' ), + array( 'Talk:File:Example.svg', 'title-invalid-talk-namespace' ), // Directory navigation - array( '.' ), - array( '..' ), - array( './Sandbox' ), - array( '../Sandbox' ), - array( 'Foo/./Sandbox' ), - array( 'Foo/../Sandbox' ), - array( 'Sandbox/.' ), - array( 'Sandbox/..' ), + array( '.', 'title-invalid-relative' ), + array( '..', 'title-invalid-relative' ), + array( './Sandbox', 'title-invalid-relative' ), + array( '../Sandbox', 'title-invalid-relative' ), + array( 'Foo/./Sandbox', 'title-invalid-relative' ), + array( 'Foo/../Sandbox', 'title-invalid-relative' ), + array( 'Sandbox/.', 'title-invalid-relative' ), + array( 'Sandbox/..', 'title-invalid-relative' ), // Tilde - array( 'A ~~~ Name' ), - array( 'A ~~~~ Signature' ), - array( 'A ~~~~~ Timestamp' ), - array( str_repeat( 'x', 256 ) ), + array( 'A ~~~ Name', 'title-invalid-magic-tilde' ), + array( 'A ~~~~ Signature', 'title-invalid-magic-tilde' ), + array( 'A ~~~~~ Timestamp', 'title-invalid-magic-tilde' ), + // Length + array( str_repeat( 'x', 256 ), 'title-invalid-too-long' ), // Namespace prefix without actual title - array( 'Talk:' ), - array( 'Talk:#' ), - array( 'Category: ' ), - array( 'Category: #bar' ), + array( 'Talk:', 'title-invalid-empty' ), + array( 'Talk:#', 'title-invalid-empty' ), + array( 'Category: ', 'title-invalid-empty' ), + array( 'Category: #bar', 'title-invalid-empty' ), // interwiki prefix - array( 'localtestiw: Talk: # anchor' ), - array( 'localtestiw: Talk:' ) + array( 'localtestiw: Talk: # anchor', 'title-invalid-empty' ), + array( 'localtestiw: Talk:', 'title-invalid-empty' ) ); } @@ -143,7 +145,7 @@ class TitleTest extends MediaWikiTestCase { } ) ) - )); + ) ); } /** @@ -163,9 +165,14 @@ class TitleTest extends MediaWikiTestCase { * @dataProvider provideInvalidSecureAndSplit * @note This mainly tests MediaWikiTitleCodec::parseTitle(). */ - public function testSecureAndSplitInvalid( $text ) { + public function testSecureAndSplitInvalid( $text, $expectedErrorMessage ) { $this->secureAndSplitGlobals(); - $this->assertNull( Title::newFromText( $text ), "Invalid: $text" ); + try { + Title::newFromTextThrow( $text ); // should throw + $this->assertTrue( false, "Invalid: $text" ); + } catch ( MalformedTitleException $ex ) { + $this->assertEquals( $expectedErrorMessage, $ex->getErrorMessage(), "Invalid: $text" ); + } } public static function provideConvertByteClassToUnicodeClass() { @@ -631,4 +638,26 @@ class TitleTest extends MediaWikiTestCase { $title = Title::makeTitle( NS_MAIN, 'Interwiki link', '', 'externalwiki' ); $this->assertTrue( $title->isAlwaysKnown() ); } + + /** + * @covers Title::exists + */ + public function testExists() { + $title = Title::makeTitle( NS_PROJECT, 'New page' ); + $linkCache = LinkCache::singleton(); + + $article = new Article( $title ); + $page = $article->getPage(); + $page->doEditContent( new WikitextContent( 'Some [[link]]' ), 'summary' ); + + // Tell Title it doesn't know whether it exists + $title->mArticleID = -1; + + // Tell the link cache it doesn't exists when it really does + $linkCache->clearLink( $title ); + $linkCache->addBadLinkObj( $title ); + + $this->assertEquals( false, $title->exists(), 'exists() should rely on link cache unless GAID_FOR_UPDATE is used' ); + $this->assertEquals( true, $title->exists( Title::GAID_FOR_UPDATE ), 'exists() should re-query database when GAID_FOR_UPDATE is used' ); + } } diff --git a/tests/phpunit/includes/UserTest.php b/tests/phpunit/includes/UserTest.php index b74a7ead..77132bbb 100644 --- a/tests/phpunit/includes/UserTest.php +++ b/tests/phpunit/includes/UserTest.php @@ -307,9 +307,30 @@ class UserTest extends MediaWikiTestCase { */ public function testCheckPasswordValidity() { $this->setMwGlobals( array( - 'wgMinimalPasswordLength' => 6, - 'wgMaximalPasswordLength' => 30, + 'wgPasswordPolicy' => array( + 'policies' => array( + 'sysop' => array( + 'MinimalPasswordLength' => 8, + 'MinimumPasswordLengthToLogin' => 1, + 'PasswordCannotMatchUsername' => 1, + ), + 'default' => array( + 'MinimalPasswordLength' => 6, + 'PasswordCannotMatchUsername' => true, + 'PasswordCannotMatchBlacklist' => true, + 'MaximalPasswordLength' => 30, + ), + ), + 'checks' => array( + 'MinimalPasswordLength' => 'PasswordPolicyChecks::checkMinimalPasswordLength', + 'MinimumPasswordLengthToLogin' => 'PasswordPolicyChecks::checkMinimumPasswordLengthToLogin', + 'PasswordCannotMatchUsername' => 'PasswordPolicyChecks::checkPasswordCannotMatchUsername', + 'PasswordCannotMatchBlacklist' => 'PasswordPolicyChecks::checkPasswordCannotMatchBlacklist', + 'MaximalPasswordLength' => 'PasswordPolicyChecks::checkMaximalPasswordLength', + ), + ), ) ); + $user = User::newFromName( 'Useruser' ); // Sanity $this->assertTrue( $user->isValidPassword( 'Password1234' ) ); @@ -425,4 +446,109 @@ class UserTest extends MediaWikiTestCase { $this->assertFalse( $user->isLoggedIn() ); $this->assertTrue( $user->isAnon() ); } + + /** + * @covers User::checkAndSetTouched + */ + public function testCheckAndSetTouched() { + $user = TestingAccessWrapper::newFromObject( User::newFromName( 'UTSysop' ) ); + $this->assertTrue( $user->isLoggedIn() ); + + $touched = $user->getDBTouched(); + $this->assertTrue( + $user->checkAndSetTouched(), "checkAndSetTouched() succeded" ); + $this->assertGreaterThan( + $touched, $user->getDBTouched(), "user_touched increased with casOnTouched()" ); + + $touched = $user->getDBTouched(); + $this->assertTrue( + $user->checkAndSetTouched(), "checkAndSetTouched() succeded #2" ); + $this->assertGreaterThan( + $touched, $user->getDBTouched(), "user_touched increased with casOnTouched() #2" ); + } + + public static function setExtendedLoginCookieDataProvider() { + $data = array(); + $now = time(); + + $secondsInDay = 86400; + + // Arbitrary durations, in units of days, to ensure it chooses the + // right one. There is a 5-minute grace period (see testSetExtendedLoginCookie) + // to work around slow tests, since we're not currently mocking time() for PHP. + + $durationOne = $secondsInDay * 5; + $durationTwo = $secondsInDay * 29; + $durationThree = $secondsInDay * 17; + + // If $wgExtendedLoginCookieExpiration is null, then the expiry passed to + // set cookie is time() + $wgCookieExpiration + $data[] = array( + null, + $durationOne, + $now + $durationOne, + ); + + // If $wgExtendedLoginCookieExpiration isn't null, then the expiry passed to + // set cookie is $now + $wgExtendedLoginCookieExpiration + $data[] = array( + $durationTwo, + $durationThree, + $now + $durationTwo, + ); + + return $data; + } + + /** + * @dataProvider setExtendedLoginCookieDataProvider + * @covers User::getRequest + * @covers User::setCookie + * @backupGlobals enabled + */ + public function testSetExtendedLoginCookie( + $extendedLoginCookieExpiration, + $cookieExpiration, + $expectedExpiry + ) { + $this->setMwGlobals( array( + 'wgExtendedLoginCookieExpiration' => $extendedLoginCookieExpiration, + 'wgCookieExpiration' => $cookieExpiration, + ) ); + + $response = $this->getMock( 'WebResponse' ); + $setcookieSpy = $this->any(); + $response->expects( $setcookieSpy ) + ->method( 'setcookie' ); + + $request = new MockWebRequest( $response ); + $user = new UserProxy( User::newFromSession( $request ) ); + $user->setExtendedLoginCookie( 'name', 'value', true ); + + $setcookieInvocations = $setcookieSpy->getInvocations(); + $setcookieInvocation = end( $setcookieInvocations ); + $actualExpiry = $setcookieInvocation->parameters[ 2 ]; + + // TODO: ± 300 seconds compensates for + // slow-running tests. However, the dependency on the time + // function should be removed. This requires some way + // to mock/isolate User->setExtendedLoginCookie's call to time() + $this->assertEquals( $expectedExpiry, $actualExpiry, '', 300 ); + } +} + +class UserProxy extends User { + + /** + * @var User + */ + protected $user; + + public function __construct( User $user ) { + $this->user = $user; + } + + public function setExtendedLoginCookie( $name, $value, $secure ) { + $this->user->setExtendedLoginCookie( $name, $value, $secure ); + } } diff --git a/tests/phpunit/includes/WikiMapTest.php b/tests/phpunit/includes/WikiMapTest.php new file mode 100644 index 00000000..9233416c --- /dev/null +++ b/tests/phpunit/includes/WikiMapTest.php @@ -0,0 +1,108 @@ +<?php + +/** + * @covers WikiMap + */ + +class WikiMapTest extends MediaWikiLangTestCase { + + public function setUp() { + parent::setUp(); + + $conf = new SiteConfiguration(); + $conf->settings = array( + 'wgServer' => array( + 'enwiki' => 'http://en.example.org', + 'ruwiki' => '//ru.example.org', + ), + 'wgArticlePath' => array( + 'enwiki' => '/w/$1', + 'ruwiki' => '/wiki/$1', + ), + ); + $conf->suffixes = array( 'wiki' ); + $this->setMwGlobals( array( + 'wgConf' => $conf, + ) ); + } + + public function provideGetWiki() { + $enwiki = new WikiReference( 'wiki', 'en', 'http://en.example.org', '/w/$1' ); + $ruwiki = new WikiReference( 'wiki', 'ru', '//ru.example.org', '/wiki/$1' ); + + return array( + 'unknown' => array( false, 'xyzzy' ), + 'enwiki' => array( $enwiki, 'enwiki' ), + 'ruwiki' => array( $ruwiki, 'ruwiki' ), + ); + } + + /** + * @dataProvider provideGetWiki + */ + public function testGetWiki( $expected, $wikiId ) { + $this->assertEquals( $expected, WikiMap::getWiki( $wikiId ) ); + } + + public function provideGetWikiName() { + return array( + 'unknown' => array( 'xyzzy', 'xyzzy' ), + 'enwiki' => array( 'en.example.org', 'enwiki' ), + 'ruwiki' => array( 'ru.example.org', 'ruwiki' ), + ); + } + + /** + * @dataProvider provideGetWikiName + */ + public function testGetWikiName( $expected, $wikiId ) { + $this->assertEquals( $expected, WikiMap::getWikiName( $wikiId ) ); + } + + public function provideMakeForeignLink() { + return array( + 'unknown' => array( false, 'xyzzy', 'Foo' ), + 'enwiki' => array( '<a class="external" rel="nofollow" href="http://en.example.org/w/Foo">Foo</a>', 'enwiki', 'Foo', ), + 'ruwiki' => array( '<a class="external" rel="nofollow" href="//ru.example.org/wiki/%D0%A4%D1%83">вар</a>', 'ruwiki', 'Фу', 'вар' ), + ); + } + + /** + * @dataProvider provideMakeForeignLink + */ + public function testMakeForeignLink( $expected, $wikiId, $page, $text = null ) { + $this->assertEquals( $expected, WikiMap::makeForeignLink( $wikiId, $page, $text ) ); + } + + public function provideForeignUserLink() { + return array( + 'unknown' => array( false, 'xyzzy', 'Foo' ), + 'enwiki' => array( '<a class="external" rel="nofollow" href="http://en.example.org/w/User:Foo">User:Foo</a>', 'enwiki', 'Foo', ), + 'ruwiki' => array( '<a class="external" rel="nofollow" href="//ru.example.org/wiki/User:%D0%A4%D1%83">вар</a>', 'ruwiki', 'Фу', 'вар' ), + ); + } + + /** + * @dataProvider provideForeignUserLink + */ + public function testForeignUserLink( $expected, $wikiId, $user, $text = null ) { + $this->assertEquals( $expected, WikiMap::foreignUserLink( $wikiId, $user, $text ) ); + } + + public function provideGetForeignURL() { + return array( + 'unknown' => array( false, 'xyzzy', 'Foo' ), + 'enwiki' => array( 'http://en.example.org/w/Foo', 'enwiki', 'Foo', ), + 'ruwiki with fragement' => array( '//ru.example.org/wiki/%D0%A4%D1%83#%D0%B2%D0%B0%D1%80', 'ruwiki', 'Фу', 'вар' ), + ); + } + + /** + * @dataProvider provideGetForeignURL + */ + public function testGetForeignURL( $expected, $wikiId, $page, $fragment = null ) { + $this->assertEquals( $expected, WikiMap::getForeignURL( $wikiId, $page, $fragment ) ); + } + +} + diff --git a/tests/phpunit/includes/WikiReferenceTest.php b/tests/phpunit/includes/WikiReferenceTest.php new file mode 100644 index 00000000..4fe2e855 --- /dev/null +++ b/tests/phpunit/includes/WikiReferenceTest.php @@ -0,0 +1,80 @@ +<?php + +/** + * @covers WikiReference + */ + +class WikiReferenceTest extends PHPUnit_Framework_TestCase { + + public function provideGetDisplayName() { + return array( + 'http' => array( 'foo.bar', 'http://foo.bar' ), + 'https' => array( 'foo.bar', 'http://foo.bar' ), + + // apparently, this is the expected behavior + 'invalid' => array( 'purple kittens', 'purple kittens' ), + ); + } + + /** + * @dataProvider provideGetDisplayName + */ + public function testGetDisplayName( $expected, $canonicalServer ) { + $reference = new WikiReference( 'wiki', 'xx', $canonicalServer, '/wiki/$1' ); + $this->assertEquals( $expected, $reference->getDisplayName() ); + } + + public function testGetCanonicalServer() { + $reference = new WikiReference( 'wiki', 'xx', 'https://acme.com', '/wiki/$1', '//acme.com' ); + $this->assertEquals( 'https://acme.com', $reference->getCanonicalServer() ); + } + + public function provideGetCanonicalUrl() { + return array( + 'no fragement' => array( 'https://acme.com/wiki/Foo', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', null ), + 'empty fragement' => array( 'https://acme.com/wiki/Foo', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', '' ), + 'fragment' => array( 'https://acme.com/wiki/Foo#Bar', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', 'Bar' ), + 'double fragment' => array( 'https://acme.com/wiki/Foo#Bar%23Xus', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', 'Bar#Xus' ), + 'escaped fragement' => array( 'https://acme.com/wiki/Foo%23Bar', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo#Bar', null ), + 'empty path' => array( 'https://acme.com/Foo', 'https://acme.com', '//acme.com', '/$1', 'Foo', null ), + ); + } + + /** + * @dataProvider provideGetCanonicalUrl + */ + public function testGetCanonicalUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) { + $reference = new WikiReference( 'wiki', 'xx', $canonicalServer, $path, $server ); + $this->assertEquals( $expected, $reference->getCanonicalUrl( $page, $fragmentId ) ); + } + + /** + * @dataProvider provideGetCanonicalUrl + * @note getUrl is an alias for getCanonicalUrl + */ + public function testGetUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) { + $reference = new WikiReference( 'wiki', 'xx', $canonicalServer, $path, $server ); + $this->assertEquals( $expected, $reference->getUrl( $page, $fragmentId ) ); + } + + public function provideGetFullUrl() { + return array( + 'no fragement' => array( '//acme.com/wiki/Foo', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', null ), + 'empty fragement' => array( '//acme.com/wiki/Foo', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', '' ), + 'fragment' => array( '//acme.com/wiki/Foo#Bar', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', 'Bar' ), + 'double fragment' => array( '//acme.com/wiki/Foo#Bar%23Xus', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', 'Bar#Xus' ), + 'escaped fragement' => array( '//acme.com/wiki/Foo%23Bar', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo#Bar', null ), + 'empty path' => array( '//acme.com/Foo', 'https://acme.com', '//acme.com', '/$1', 'Foo', null ), + ); + } + + /** + * @dataProvider provideGetFullUrl + */ + public function testGetFullUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) { + $reference = new WikiReference( 'wiki', 'xx', $canonicalServer, $path, $server ); + $this->assertEquals( $expected, $reference->getFullUrl( $page, $fragmentId ) ); + } + +} + diff --git a/tests/phpunit/includes/XmlJsTest.php b/tests/phpunit/includes/XmlJsTest.php index 0dbb0109..21819b7e 100644 --- a/tests/phpunit/includes/XmlJsTest.php +++ b/tests/phpunit/includes/XmlJsTest.php @@ -3,7 +3,7 @@ /** * @group Xml */ -class XmlJs extends MediaWikiTestCase { +class XmlJs extends PHPUnit_Framework_TestCase { /** * @covers XmlJsCode::__construct diff --git a/tests/phpunit/includes/XmlTest.php b/tests/phpunit/includes/XmlTest.php index 382e3d89..bea338de 100644 --- a/tests/phpunit/includes/XmlTest.php +++ b/tests/phpunit/includes/XmlTest.php @@ -154,7 +154,7 @@ class XmlTest extends MediaWikiTestCase { '<label for="year">From year (and earlier):</label> ' . '<input id="year" maxlength="4" size="7" type="number" value="2011" name="year" /> ' . '<label for="month">From month (and earlier):</label> ' . - '<select id="month" name="month" class="mw-month-selector">' . + '<select name="month" id="month" class="mw-month-selector">' . '<option value="-1">all</option>' . "\n" . '<option value="1">January</option>' . "\n" . '<option value="2" selected="">February</option>' . "\n" . @@ -175,7 +175,7 @@ class XmlTest extends MediaWikiTestCase { '<label for="year">From year (and earlier):</label> ' . '<input id="year" maxlength="4" size="7" type="number" value="2011" name="year" /> ' . '<label for="month">From month (and earlier):</label> ' . - '<select id="month" name="month" class="mw-month-selector">' . + '<select name="month" id="month" class="mw-month-selector">' . '<option value="-1">all</option>' . "\n" . '<option value="1">January</option>' . "\n" . '<option value="2">February</option>' . "\n" . @@ -209,7 +209,7 @@ class XmlTest extends MediaWikiTestCase { '<label for="year">From year (and earlier):</label> ' . '<input id="year" maxlength="4" size="7" type="number" name="year" /> ' . '<label for="month">From month (and earlier):</label> ' . - '<select id="month" name="month" class="mw-month-selector">' . + '<select name="month" id="month" class="mw-month-selector">' . '<option value="-1">all</option>' . "\n" . '<option value="1">January</option>' . "\n" . '<option value="2">February</option>' . "\n" . diff --git a/tests/phpunit/includes/api/ApiBlockTest.php b/tests/phpunit/includes/api/ApiBlockTest.php index d98eec6a..575efd6d 100644 --- a/tests/phpunit/includes/api/ApiBlockTest.php +++ b/tests/phpunit/includes/api/ApiBlockTest.php @@ -53,7 +53,7 @@ class ApiBlockTest extends ApiTestCase { 'action' => 'block', 'user' => 'UTApiBlockee', 'reason' => 'Some reason', - 'token' => $tokens['blocktoken'] ), null, false, self::$users['sysop']->user ); + 'token' => $tokens['blocktoken'] ), null, false, self::$users['sysop']->getUser() ); $block = Block::newFromTarget( 'UTApiBlockee' ); @@ -68,7 +68,7 @@ class ApiBlockTest extends ApiTestCase { * @expectedException UsageException * @expectedExceptionMessage The token parameter must be set */ - public function testBlockingActionWithNoToken( ) { + public function testBlockingActionWithNoToken() { $this->doApiRequest( array( 'action' => 'block', @@ -77,7 +77,7 @@ class ApiBlockTest extends ApiTestCase { ), null, false, - self::$users['sysop']->user + self::$users['sysop']->getUser() ); } } diff --git a/tests/phpunit/includes/api/ApiEditPageTest.php b/tests/phpunit/includes/api/ApiEditPageTest.php index 3179a452..61a8ad11 100644 --- a/tests/phpunit/includes/api/ApiEditPageTest.php +++ b/tests/phpunit/includes/api/ApiEditPageTest.php @@ -27,9 +27,14 @@ class ApiEditPageTest extends ApiTestCase { $wgExtraNamespaces[12312] = 'Dummy'; $wgExtraNamespaces[12313] = 'Dummy_talk'; + $wgExtraNamespaces[12314] = 'DummyNonText'; + $wgExtraNamespaces[12315] = 'DummyNonText_talk'; $wgNamespaceContentModels[12312] = "testing"; + $wgNamespaceContentModels[12314] = "testing-nontext"; + $wgContentHandlers["testing"] = 'DummyContentHandlerForTesting'; + $wgContentHandlers["testing-nontext"] = 'DummyNonTextContentHandler'; MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache $wgContLang->resetNamespaces(); # reset namespace cache @@ -96,33 +101,6 @@ class ApiEditPageTest extends ApiTestCase { ); } - public function testNonTextEdit() { - $name = 'Dummy:ApiEditPageTest_testNonTextEdit'; - $data = serialize( 'some bla bla text' ); - - // -- test new page -------------------------------------------- - $apiResult = $this->doApiRequestWithToken( array( - 'action' => 'edit', - 'title' => $name, - 'text' => $data, ) ); - $apiResult = $apiResult[0]; - - // Validate API result data - $this->assertArrayHasKey( 'edit', $apiResult ); - $this->assertArrayHasKey( 'result', $apiResult['edit'] ); - $this->assertEquals( 'Success', $apiResult['edit']['result'] ); - - $this->assertArrayHasKey( 'new', $apiResult['edit'] ); - $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] ); - - $this->assertArrayHasKey( 'pageid', $apiResult['edit'] ); - - // validate resulting revision - $page = WikiPage::factory( Title::newFromText( $name ) ); - $this->assertEquals( "testing", $page->getContentModel() ); - $this->assertEquals( $data, $page->getContent()->serialize() ); - } - /** * @return array */ @@ -240,7 +218,7 @@ class ApiEditPageTest extends ApiTestCase { 'section' => 'new', 'text' => 'test', 'summary' => 'header', - )); + ) ); $this->assertEquals( 'Success', $re['edit']['result'] ); // Check the page text is correct @@ -257,7 +235,7 @@ class ApiEditPageTest extends ApiTestCase { 'section' => 'new', 'text' => 'test', 'summary' => 'header', - )); + ) ); $this->assertEquals( 'Success', $re2['edit']['result'] ); $text = WikiPage::factory( Title::newFromText( $name ) ) @@ -284,18 +262,18 @@ class ApiEditPageTest extends ApiTestCase { // base edit for content $page->doEditContent( new WikitextContent( "Foo" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $page, '20120101000000' ); $baseTime = $page->getRevision()->getTimestamp(); // base edit for redirect $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $rpage, '20120101000000' ); // conflicting edit to redirect $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]\n\n[[Category:Test]]" ), - "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() ); $this->forceRevisionDate( $rpage, '20120101020202' ); // try to save edit, following the redirect @@ -306,7 +284,7 @@ class ApiEditPageTest extends ApiTestCase { 'basetimestamp' => $baseTime, 'section' => 'new', 'redirect' => true, - ), null, self::$users['sysop']->user ); + ), null, self::$users['sysop']->getUser() ); $this->assertEquals( 'Success', $re['edit']['result'], "no problems expected when following redirect" ); @@ -330,18 +308,18 @@ class ApiEditPageTest extends ApiTestCase { // base edit for content $page->doEditContent( new WikitextContent( "Foo" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $page, '20120101000000' ); $baseTime = $page->getRevision()->getTimestamp(); // base edit for redirect $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $rpage, '20120101000000' ); // conflicting edit to redirect $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]\n\n[[Category:Test]]" ), - "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() ); $this->forceRevisionDate( $rpage, '20120101020202' ); // try to save edit, following the redirect but without creating a section @@ -352,7 +330,7 @@ class ApiEditPageTest extends ApiTestCase { 'text' => 'nix bar!', 'basetimestamp' => $baseTime, 'redirect' => true, - ), null, self::$users['sysop']->user ); + ), null, self::$users['sysop']->getUser() ); $this->fail( 'redirect-appendonly error expected' ); } catch ( UsageException $ex ) { @@ -372,13 +350,13 @@ class ApiEditPageTest extends ApiTestCase { // base edit $page->doEditContent( new WikitextContent( "Foo" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $page, '20120101000000' ); $baseTime = $page->getRevision()->getTimestamp(); // conflicting edit $page->doEditContent( new WikitextContent( "Foo bar" ), - "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() ); $this->forceRevisionDate( $page, '20120101020202' ); // try to save edit, expect conflict @@ -388,7 +366,7 @@ class ApiEditPageTest extends ApiTestCase { 'title' => $name, 'text' => 'nix bar!', 'basetimestamp' => $baseTime, - ), null, self::$users['sysop']->user ); + ), null, self::$users['sysop']->getUser() ); $this->fail( 'edit conflict expected' ); } catch ( UsageException $ex ) { @@ -411,13 +389,13 @@ class ApiEditPageTest extends ApiTestCase { // base edit $page->doEditContent( new WikitextContent( "Foo" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $page, '20120101000000' ); $baseTime = $page->getRevision()->getTimestamp(); // conflicting edit $page->doEditContent( new WikitextContent( "Foo bar" ), - "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() ); $this->forceRevisionDate( $page, '20120101020202' ); // try to save edit, expect no conflict @@ -427,7 +405,7 @@ class ApiEditPageTest extends ApiTestCase { 'text' => 'nix bar!', 'basetimestamp' => $baseTime, 'section' => 'new', - ), null, self::$users['sysop']->user ); + ), null, self::$users['sysop']->getUser() ); $this->assertEquals( 'Success', $re['edit']['result'], "no edit conflict expected here" ); @@ -454,17 +432,17 @@ class ApiEditPageTest extends ApiTestCase { // base edit for content $page->doEditContent( new WikitextContent( "Foo" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $page, '20120101000000' ); // base edit for redirect $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $rpage, '20120101000000' ); // new edit to content $page->doEditContent( new WikitextContent( "Foo bar" ), - "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() ); $this->forceRevisionDate( $rpage, '20120101020202' ); // try to save edit; should work, following the redirect. @@ -474,7 +452,7 @@ class ApiEditPageTest extends ApiTestCase { 'text' => 'nix bar!', 'section' => 'new', 'redirect' => true, - ), null, self::$users['sysop']->user ); + ), null, self::$users['sysop']->getUser() ); $this->assertEquals( 'Success', $re['edit']['result'], "no edit conflict expected here" ); @@ -493,4 +471,45 @@ class ApiEditPageTest extends ApiTestCase { $page->clear(); } + + public function testCheckDirectApiEditingDisallowed_forNonTextContent() { + $this->setExpectedException( + 'UsageException', + 'Direct editing via API is not supported for content model testing used by Dummy:ApiEditPageTest_nonTextPageEdit' + ); + + $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => 'Dummy:ApiEditPageTest_nonTextPageEdit', + 'text' => '{"animals":["kittens!"]}' + ) ); + } + + public function testSupportsDirectApiEditing_withContentHandlerOverride() { + $name = 'DummyNonText:ApiEditPageTest_testNonTextEdit'; + $data = serialize( 'some bla bla text' ); + + $result = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'text' => $data, + ) ); + + $apiResult = $result[0]; + + // Validate API result data + $this->assertArrayHasKey( 'edit', $apiResult ); + $this->assertArrayHasKey( 'result', $apiResult['edit'] ); + $this->assertEquals( 'Success', $apiResult['edit']['result'] ); + + $this->assertArrayHasKey( 'new', $apiResult['edit'] ); + $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] ); + + $this->assertArrayHasKey( 'pageid', $apiResult['edit'] ); + + // validate resulting revision + $page = WikiPage::factory( Title::newFromText( $name ) ); + $this->assertEquals( "testing-nontext", $page->getContentModel() ); + $this->assertEquals( $data, $page->getContent()->serialize() ); + } } diff --git a/tests/phpunit/includes/api/ApiLoginTest.php b/tests/phpunit/includes/api/ApiLoginTest.php index 88a99e9b..7dfd14f3 100644 --- a/tests/phpunit/includes/api/ApiLoginTest.php +++ b/tests/phpunit/includes/api/ApiLoginTest.php @@ -23,7 +23,7 @@ class ApiLoginTest extends ApiTestCase { global $wgServer; $user = self::$users['sysop']; - $user->user->logOut(); + $user->getUser()->logOut(); if ( !isset( $wgServer ) ) { $this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' ); @@ -68,7 +68,7 @@ class ApiLoginTest extends ApiTestCase { } $user = self::$users['sysop']; - $user->user->logOut(); + $user->getUser()->logOut(); $ret = $this->doApiRequest( array( "action" => "login", diff --git a/tests/phpunit/includes/api/ApiMainTest.php b/tests/phpunit/includes/api/ApiMainTest.php index e8ef1804..94b741dc 100644 --- a/tests/phpunit/includes/api/ApiMainTest.php +++ b/tests/phpunit/includes/api/ApiMainTest.php @@ -71,7 +71,7 @@ class ApiMainTest extends ApiTestCase { new FauxRequest( array( 'action' => 'query', 'meta' => 'siteinfo' ) ) ); $modules = $api->getModuleManager()->getNamesWithClasses(); - foreach( $modules as $name => $class ) { + foreach ( $modules as $name => $class ) { $this->assertArrayHasKey( $class, $classes, @@ -79,4 +79,173 @@ class ApiMainTest extends ApiTestCase { ); } } + + /** + * Test HTTP precondition headers + * + * @covers ApiMain::checkConditionalRequestHeaders + * @dataProvider provideCheckConditionalRequestHeaders + * @param array $headers HTTP headers + * @param array $conditions Return data for ApiBase::getConditionalRequestData + * @param int $status Expected response status + * @param bool $post Request is a POST + */ + public function testCheckConditionalRequestHeaders( $headers, $conditions, $status, $post = false ) { + $request = new FauxRequest( array( 'action' => 'query', 'meta' => 'siteinfo' ), $post ); + $request->setHeaders( $headers ); + $request->response()->statusHeader( 200 ); // Why doesn't it default? + + $api = new ApiMain( $request ); + $priv = TestingAccessWrapper::newFromObject( $api ); + $priv->mInternalMode = false; + + $module = $this->getMockBuilder( 'ApiBase' ) + ->setConstructorArgs( array( $api, 'mock' ) ) + ->setMethods( array( 'getConditionalRequestData' ) ) + ->getMockForAbstractClass(); + $module->expects( $this->any() ) + ->method( 'getConditionalRequestData' ) + ->will( $this->returnCallback( function ( $condition ) use ( $conditions ) { + return isset( $conditions[$condition] ) ? $conditions[$condition] : null; + } ) ); + + $ret = $priv->checkConditionalRequestHeaders( $module ); + + $this->assertSame( $status, $request->response()->getStatusCode() ); + $this->assertSame( $status === 200, $ret ); + } + + public static function provideCheckConditionalRequestHeaders() { + $now = time(); + + return array( + // Non-existing from module is ignored + array( array( 'If-None-Match' => '"foo", "bar"' ), array(), 200 ), + array( array( 'If-Modified-Since' => 'Tue, 18 Aug 2015 00:00:00 GMT' ), array(), 200 ), + + // No headers + array( + array(), + array( + 'etag' => '""', + 'last-modified' => '20150815000000', + ), + 200 + ), + + // Basic If-None-Match + array( array( 'If-None-Match' => '"foo", "bar"' ), array( 'etag' => '"bar"' ), 304 ), + array( array( 'If-None-Match' => '"foo", "bar"' ), array( 'etag' => '"baz"' ), 200 ), + array( array( 'If-None-Match' => '"foo"' ), array( 'etag' => 'W/"foo"' ), 304 ), + array( array( 'If-None-Match' => 'W/"foo"' ), array( 'etag' => '"foo"' ), 304 ), + array( array( 'If-None-Match' => 'W/"foo"' ), array( 'etag' => 'W/"foo"' ), 304 ), + + // Pointless, but supported + array( array( 'If-None-Match' => '*' ), array(), 304 ), + + // Basic If-Modified-Since + array( array( 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 304 ), + array( array( 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now ) ), 304 ), + array( array( 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now + 1 ) ), 200 ), + + // If-Modified-Since ignored when If-None-Match is given too + array( array( 'If-None-Match' => '""', 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'etag' => '"x"', 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 200 ), + array( array( 'If-None-Match' => '""', 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 304 ), + + // Ignored for POST + array( array( 'If-None-Match' => '"foo", "bar"' ), array( 'etag' => '"bar"' ), 200, true ), + array( array( 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 200, true ), + + // Other date formats allowed by the RFC + array( array( 'If-Modified-Since' => gmdate( 'l, d-M-y H:i:s', $now ) . ' GMT' ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 304 ), + array( array( 'If-Modified-Since' => gmdate( 'D M j H:i:s Y', $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 304 ), + + // Old browser extension to HTTP/1.0 + array( array( 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) . '; length=123' ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 304 ), + + // Invalid date formats should be ignored + array( array( 'If-Modified-Since' => gmdate( 'Y-m-d H:i:s', $now ) . ' GMT' ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 200 ), + ); + } + + /** + * Test conditional headers output + * @dataProvider provideConditionalRequestHeadersOutput + * @param array $conditions Return data for ApiBase::getConditionalRequestData + * @param array $headers Expected output headers + * @param bool $isError $isError flag + * @param bool $post Request is a POST + */ + public function testConditionalRequestHeadersOutput( $conditions, $headers, $isError = false, $post = false ) { + $request = new FauxRequest( array( 'action' => 'query', 'meta' => 'siteinfo' ), $post ); + $response = $request->response(); + + $api = new ApiMain( $request ); + $priv = TestingAccessWrapper::newFromObject( $api ); + $priv->mInternalMode = false; + + $module = $this->getMockBuilder( 'ApiBase' ) + ->setConstructorArgs( array( $api, 'mock' ) ) + ->setMethods( array( 'getConditionalRequestData' ) ) + ->getMockForAbstractClass(); + $module->expects( $this->any() ) + ->method( 'getConditionalRequestData' ) + ->will( $this->returnCallback( function ( $condition ) use ( $conditions ) { + return isset( $conditions[$condition] ) ? $conditions[$condition] : null; + } ) ); + $priv->mModule = $module; + + $priv->sendCacheHeaders( $isError ); + + foreach ( array( 'Last-Modified', 'ETag' ) as $header ) { + $this->assertEquals( + isset( $headers[$header] ) ? $headers[$header] : null, + $response->getHeader( $header ), + $header + ); + } + } + + public static function provideConditionalRequestHeadersOutput() { + return array( + array( + array(), + array() + ), + array( + array( 'etag' => '"foo"' ), + array( 'ETag' => '"foo"' ) + ), + array( + array( 'last-modified' => '20150818000102' ), + array( 'Last-Modified' => 'Tue, 18 Aug 2015 00:01:02 GMT' ) + ), + array( + array( 'etag' => '"foo"', 'last-modified' => '20150818000102' ), + array( 'ETag' => '"foo"', 'Last-Modified' => 'Tue, 18 Aug 2015 00:01:02 GMT' ) + ), + array( + array( 'etag' => '"foo"', 'last-modified' => '20150818000102' ), + array(), + true, + ), + array( + array( 'etag' => '"foo"', 'last-modified' => '20150818000102' ), + array(), + false, + true, + ), + ); + } + } diff --git a/tests/phpunit/includes/api/ApiMessageTest.php b/tests/phpunit/includes/api/ApiMessageTest.php index 6c3ce60d..08a984eb 100644 --- a/tests/phpunit/includes/api/ApiMessageTest.php +++ b/tests/phpunit/includes/api/ApiMessageTest.php @@ -14,9 +14,13 @@ class ApiMessageTest extends MediaWikiTestCase { $msg = TestingAccessWrapper::newFromObject( $msg ); $msg2 = TestingAccessWrapper::newFromObject( $msg2 ); - foreach ( array( 'interface', 'useDatabase', 'title' ) as $key ) { - $this->assertSame( $msg->$key, $msg2->$key, $key ); - } + $this->assertSame( $msg->interface, $msg2->interface, 'interface' ); + $this->assertSame( $msg->useDatabase, $msg2->useDatabase, 'useDatabase' ); + $this->assertSame( + $msg->title ? $msg->title->getFullText() : null, + $msg2->title ? $msg2->title->getFullText() : null, + 'title' + ); } /** @@ -30,6 +34,11 @@ class ApiMessageTest extends MediaWikiTestCase { $this->assertEquals( 'code', $msg2->getApiCode() ); $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg2 = unserialize( serialize( $msg2 ) ); + $this->compareMessages( $msg, $msg2 ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg = new Message( array( 'foo', 'bar' ), array( 'baz' ) ); $msg2 = new ApiMessage( array( array( 'foo', 'bar' ), 'baz' ), 'code', array( 'data' ) ); $this->compareMessages( $msg, $msg2 ); @@ -63,6 +72,11 @@ class ApiMessageTest extends MediaWikiTestCase { $this->assertEquals( 'code', $msg2->getApiCode() ); $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg2 = unserialize( serialize( $msg2 ) ); + $this->compareMessages( $msg, $msg2 ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg = new RawMessage( 'foo', array( 'baz' ) ); $msg2 = new ApiRawMessage( array( 'foo', 'baz' ), 'code', array( 'data' ) ); $this->compareMessages( $msg, $msg2 ); diff --git a/tests/phpunit/includes/api/ApiQueryAllPagesTest.php b/tests/phpunit/includes/api/ApiQueryAllPagesTest.php index 124988f3..0ac00eea 100644 --- a/tests/phpunit/includes/api/ApiQueryAllPagesTest.php +++ b/tests/phpunit/includes/api/ApiQueryAllPagesTest.php @@ -13,9 +13,11 @@ class ApiQueryAllPagesTest extends ApiTestCase { } /** - * @todo give this test a real name explaining what is being tested here + *Test bug 25702 + *Prefixes of API search requests are not handled with case sensitivity and may result + *in wrong search results */ - public function testBug25702() { + public function testPrefixNormalizationSearchBug() { $title = Title::newFromText( 'Category:Template:xyz' ); $page = WikiPage::factory( $title ); $page->doEdit( 'Some text', 'inserting content' ); diff --git a/tests/phpunit/includes/api/ApiResultTest.php b/tests/phpunit/includes/api/ApiResultTest.php index f0d84552..2f31677e 100644 --- a/tests/phpunit/includes/api/ApiResultTest.php +++ b/tests/phpunit/includes/api/ApiResultTest.php @@ -181,6 +181,19 @@ class ApiResultTest extends MediaWikiTestCase { ); } + ApiResult::setValue( $arr, null, NAN, ApiResult::NO_VALIDATE ); + + try { + ApiResult::setValue( $arr, null, NAN, ApiResult::NO_SIZE_CHECK ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add non-finite floats to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + $arr = array(); $result2 = new ApiResult( 8388608 ); $result2->addValue( null, 'foo', 'bar' ); @@ -408,6 +421,19 @@ class ApiResultTest extends MediaWikiTestCase { ); } + $result->addValue( null, null, NAN, ApiResult::NO_VALIDATE ); + + try { + $result->addValue( null, null, NAN, ApiResult::NO_SIZE_CHECK ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add non-finite floats to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + $result->reset(); $result->addParsedLimit( 'foo', 12 ); $this->assertSame( array( @@ -444,6 +470,12 @@ class ApiResultTest extends MediaWikiTestCase { $result->removeValue( null, 'foo' ); $this->assertTrue( $result->addValue( null, 'foo', '1' ) ); + $result = new ApiResult( 10 ); + $obj = new ApiResultTestSerializableObject( 'ok' ); + $obj->foobar = 'foobaz'; + $this->assertTrue( $result->addValue( null, 'foo', $obj ) ); + $this->assertSame( 2, $result->getSize() ); + $result = new ApiResult( 8388608 ); $result2 = new ApiResult( 8388608 ); $result2->addValue( null, 'foo', 'bar' ); @@ -674,6 +706,10 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => array( 'x' => 'a', 'y' => array( 'b' ), 'z' => array( 'c' => 'd' ), + ApiResult::META_TYPE => 'kvp', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1 ), 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -858,6 +894,13 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'assoc', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => array( + 'x' => 'a', + 'y' => array( 'b', ApiResult::META_TYPE => 'array' ), + 'z' => array( 'c' => 'd', ApiResult::META_TYPE => 'assoc' ), + ApiResult::META_TYPE => 'assoc', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -871,8 +914,12 @@ class ApiResultTest extends MediaWikiTestCase { array( 'Types' => array( 'AssocAsObject' => true ) ), (object)array( 'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ), - 'defaultAssoc' => (object)array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), - 'defaultAssoc2' => (object)array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'defaultAssoc' => (object)array( 'x' => 'a', + 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' + ), + 'defaultAssoc2' => (object)array( 2 => 'a', 3 => 'b', + 0 => 'c', ApiResult::META_TYPE => 'assoc' + ), 'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), 'BCarray' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), 'BCassoc' => (object)array( 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ), @@ -885,6 +932,13 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'assoc', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => (object)array( + 'x' => 'a', + 'y' => array( 'b', ApiResult::META_TYPE => 'array' ), + 'z' => (object)array( 'c' => 'd', ApiResult::META_TYPE => 'assoc' ), + ApiResult::META_TYPE => 'assoc', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), 'emptyAssoc' => (object)array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -916,6 +970,13 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'array', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => array( + $kvp( 'name', 'x', 'value', 'a' ), + $kvp( 'name', 'y', 'value', array( 'b', ApiResult::META_TYPE => 'array' ) ), + array( 'name' => 'z', 'c' => 'd', ApiResult::META_TYPE => 'assoc', ApiResult::META_PRESERVE_KEYS => array( 'name' ) ), + ApiResult::META_TYPE => 'array', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -947,6 +1008,13 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'array', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => array( + $kvp( 'name', 'x', '*', 'a' ), + $kvp( 'name', 'y', '*', array( 'b', ApiResult::META_TYPE => 'array' ) ), + array( 'name' => 'z', 'c' => 'd', ApiResult::META_TYPE => 'assoc', ApiResult::META_PRESERVE_KEYS => array( 'name' ) ), + ApiResult::META_TYPE => 'array', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -960,8 +1028,12 @@ class ApiResultTest extends MediaWikiTestCase { array( 'Types' => array( 'ArmorKVP' => 'name', 'AssocAsObject' => true ) ), (object)array( 'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ), - 'defaultAssoc' => (object)array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), - 'defaultAssoc2' => (object)array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'defaultAssoc' => (object)array( 'x' => 'a', 1 => 'b', + 0 => 'c', ApiResult::META_TYPE => 'assoc' + ), + 'defaultAssoc2' => (object)array( 2 => 'a', 3 => 'b', + 0 => 'c', ApiResult::META_TYPE => 'assoc' + ), 'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), 'BCarray' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), 'BCassoc' => (object)array( 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ), @@ -978,6 +1050,13 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'array', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => array( + (object)$kvp( 'name', 'x', 'value', 'a' ), + (object)$kvp( 'name', 'y', 'value', array( 'b', ApiResult::META_TYPE => 'array' ) ), + (object)array( 'name' => 'z', 'c' => 'd', ApiResult::META_TYPE => 'assoc', ApiResult::META_PRESERVE_KEYS => array( 'name' ) ), + ApiResult::META_TYPE => 'array', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), 'emptyAssoc' => (object)array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -1017,6 +1096,11 @@ class ApiResultTest extends MediaWikiTestCase { (object)array( 'key' => 'x', 'value' => 'a' ), (object)array( 'key' => 'y', 'value' => 'b' ), ), + 'kvpmerge' => array( + (object)array( 'name' => 'x', 'value' => 'a' ), + (object)array( 'name' => 'y', 'value' => array( 'b' ) ), + (object)array( 'name' => 'z', 'c' => 'd' ), + ), 'emptyDefault' => array(), 'emptyAssoc' => (object)array(), '_dummy' => 1, @@ -1127,13 +1211,84 @@ class ApiResultTest extends MediaWikiTestCase { /** * @covers ApiResult */ + public function testAddMetadataToResultVars() { + $arr = array( + 'a' => "foo", + 'b' => false, + 'c' => 10, + 'sequential_numeric_keys' => array( 'a', 'b', 'c' ), + 'non_sequential_numeric_keys' => array( 'a', 'b', 4 => 'c' ), + 'string_keys' => array( + 'one' => 1, + 'two' => 2 + ), + 'object_sequential_keys' => (object)array( 'a', 'b', 'c' ), + '_type' => "should be overwritten in result", + ); + $this->assertSame( array( + ApiResult::META_TYPE => 'kvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( + 'a', 'b', 'c', + 'sequential_numeric_keys', 'non_sequential_numeric_keys', + 'string_keys', 'object_sequential_keys' + ), + ApiResult::META_BC_BOOLS => array( 'b' ), + ApiResult::META_INDEXED_TAG_NAME => 'var', + 'a' => "foo", + 'b' => false, + 'c' => 10, + 'sequential_numeric_keys' => array( + ApiResult::META_TYPE => 'array', + ApiResult::META_BC_BOOLS => array(), + ApiResult::META_INDEXED_TAG_NAME => 'value', + 0 => 'a', + 1 => 'b', + 2 => 'c', + ), + 'non_sequential_numeric_keys' => array( + ApiResult::META_TYPE => 'kvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( 0, 1, 4 ), + ApiResult::META_BC_BOOLS => array(), + ApiResult::META_INDEXED_TAG_NAME => 'var', + 0 => 'a', + 1 => 'b', + 4 => 'c', + ), + 'string_keys' => array( + ApiResult::META_TYPE => 'kvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( 'one', 'two' ), + ApiResult::META_BC_BOOLS => array(), + ApiResult::META_INDEXED_TAG_NAME => 'var', + 'one' => 1, + 'two' => 2, + ), + 'object_sequential_keys' => array( + ApiResult::META_TYPE => 'kvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( 0, 1, 2 ), + ApiResult::META_BC_BOOLS => array(), + ApiResult::META_INDEXED_TAG_NAME => 'var', + 0 => 'a', + 1 => 'b', + 2 => 'c', + ), + ), ApiResult::addMetadataToResultVars( $arr ) ); + } + + /** + * @covers ApiResult + */ public function testDeprecatedFunctions() { // Ignore ApiResult deprecation warnings during this test set_error_handler( function ( $errno, $errstr ) use ( &$warnings ) { if ( preg_match( '/Use of ApiResult::\S+ was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) { return true; } - if ( preg_match( '/Use of ApiMain to ApiResult::__construct was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) { + if ( preg_match( '/Use of ApiMain to ApiResult::__construct ' . + 'was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) { return true; } return false; @@ -1166,17 +1321,6 @@ class ApiResultTest extends MediaWikiTestCase { ), '*' => 'content', ), $result->getData() ); - $result->setRawMode(); - $this->assertSame( array( - 'foo' => array( - 'bar' => array( - '*' => 'content', - ), - ), - '*' => 'content', - '_element' => 'itn', - '_subelements' => array( 'sub' ), - ), $result->getData() ); $arr = array(); ApiResult::setContent( $arr, 'value' ); @@ -1451,7 +1595,8 @@ class ApiResultTest extends MediaWikiTestCase { $result = new ApiResult( 8388608 ); $result->setMainForContinuation( $main ); - $result->beginContinuation( '||mock2', array_slice( $allModules, 0, 2 ), array( 'mock1', 'mock2' ) ); + $result->beginContinuation( '||mock2', array_slice( $allModules, 0, 2 ), + array( 'mock1', 'mock2' ) ); try { $result->setContinueParam( $allModules[1], 'm2continue', 1 ); $this->fail( 'Expected exception not thrown' ); @@ -1467,7 +1612,8 @@ class ApiResultTest extends MediaWikiTestCase { $this->fail( 'Expected exception not thrown' ); } catch ( UnexpectedValueException $ex ) { $this->assertSame( - 'Module \'mocklist\' called ApiContinuationManager::addContinueParam but was not passed to ApiContinuationManager::__construct', + 'Module \'mocklist\' called ApiContinuationManager::addContinueParam ' . + 'but was not passed to ApiContinuationManager::__construct', $ex->getMessage(), 'Expected exception' ); @@ -1495,13 +1641,14 @@ class ApiResultTest extends MediaWikiTestCase { try { $arr = array(); - ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( + ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( new ApiResultTestStringifiableObject() ) ); $this->fail( 'Expected exception not thrown' ); } catch ( UnexpectedValueException $ex ) { $this->assertSame( - 'ApiResultTestSerializableObject::serializeForApiResult() returned an object of class ApiResultTestStringifiableObject', + 'ApiResultTestSerializableObject::serializeForApiResult() ' . + 'returned an object of class ApiResultTestStringifiableObject', $ex->getMessage(), 'Expected exception' ); @@ -1509,18 +1656,19 @@ class ApiResultTest extends MediaWikiTestCase { try { $arr = array(); - ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( NAN ) ); + ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( NAN ) ); $this->fail( 'Expected exception not thrown' ); } catch ( UnexpectedValueException $ex ) { $this->assertSame( - 'ApiResultTestSerializableObject::serializeForApiResult() returned an invalid value: Cannot add non-finite floats to ApiResult', + 'ApiResultTestSerializableObject::serializeForApiResult() ' . + 'returned an invalid value: Cannot add non-finite floats to ApiResult', $ex->getMessage(), 'Expected exception' ); } $arr = array(); - ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( + ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( array( 'one' => new ApiResultTestStringifiableObject( '1' ), 'two' => new ApiResultTestSerializableObject( 2 ), diff --git a/tests/phpunit/includes/api/ApiRevisionDeleteTest.php b/tests/phpunit/includes/api/ApiRevisionDeleteTest.php index b03836eb..362d647f 100644 --- a/tests/phpunit/includes/api/ApiRevisionDeleteTest.php +++ b/tests/phpunit/includes/api/ApiRevisionDeleteTest.php @@ -25,7 +25,7 @@ class ApiRevisionDeleteTest extends ApiTestCase { } public function testHidingRevisions() { - $user = self::$users['sysop']->user; + $user = self::$users['sysop']->getUser(); $revid = array_shift( $this->revs ); $out = $this->doApiRequest( array( 'action' => 'revisiondelete', @@ -80,7 +80,7 @@ class ApiRevisionDeleteTest extends ApiTestCase { } public function testUnhidingOutput() { - $user = self::$users['sysop']->user; + $user = self::$users['sysop']->getUser(); $revid = array_shift( $this->revs ); // Hide revisions $this->doApiRequest( array( diff --git a/tests/phpunit/includes/api/ApiTestCase.php b/tests/phpunit/includes/api/ApiTestCase.php index da62bb0a..21345ac1 100644 --- a/tests/phpunit/includes/api/ApiTestCase.php +++ b/tests/phpunit/includes/api/ApiTestCase.php @@ -105,6 +105,7 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { $wgRequest = new FauxRequest( $params, true, $session ); RequestContext::getMain()->setRequest( $wgRequest ); + RequestContext::getMain()->setUser( $wgUser ); // set up local environment $context = $this->apiContext->newTestContext( $wgRequest, $wgUser ); diff --git a/tests/phpunit/includes/api/ApiUnblockTest.php b/tests/phpunit/includes/api/ApiUnblockTest.php index 2c2370a8..a374f094 100644 --- a/tests/phpunit/includes/api/ApiUnblockTest.php +++ b/tests/phpunit/includes/api/ApiUnblockTest.php @@ -16,7 +16,7 @@ class ApiUnblockTest extends ApiTestCase { /** * @expectedException UsageException */ - public function testWithNoToken( ) { + public function testWithNoToken() { $this->doApiRequest( array( 'action' => 'unblock', @@ -25,7 +25,7 @@ class ApiUnblockTest extends ApiTestCase { ), null, false, - self::$users['sysop']->user + self::$users['sysop']->getUser() ); } } diff --git a/tests/phpunit/includes/api/ApiUploadTest.php b/tests/phpunit/includes/api/ApiUploadTest.php index f74fc354..c852d72b 100644 --- a/tests/phpunit/includes/api/ApiUploadTest.php +++ b/tests/phpunit/includes/api/ApiUploadTest.php @@ -80,7 +80,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $this->doApiRequestWithToken( array( 'action' => 'upload', - ), $session, self::$users['uploader']->user ); + ), $session, self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; $this->assertEquals( "One of the parameters filekey, file, url, statuskey is required", @@ -126,7 +126,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result, , ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; } @@ -165,7 +165,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { - $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); + $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $this->assertContains( 'The file you submitted was empty', $e->getMessage() ); $exception = true; @@ -215,7 +215,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; } @@ -232,7 +232,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result, , ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); // FIXME: leaks a temporary file + self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file } catch ( UsageException $e ) { $exception = true; } @@ -286,7 +286,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; } @@ -311,7 +311,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); // FIXME: leaks a temporary file + self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file } catch ( UsageException $e ) { $exception = true; } @@ -331,7 +331,7 @@ class ApiUploadTest extends ApiTestCaseUpload { */ public function testUploadStash( $session ) { $this->setMwGlobals( array( - 'wgUser' => self::$users['uploader']->user, // @todo FIXME: still used somewhere + 'wgUser' => self::$users['uploader']->getUser(), // @todo FIXME: still used somewhere ) ); $extension = 'png'; @@ -368,7 +368,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); // FIXME: leaks a temporary file + self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file } catch ( UsageException $e ) { $exception = true; } @@ -397,7 +397,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; } @@ -415,7 +415,7 @@ class ApiUploadTest extends ApiTestCaseUpload { public function testUploadChunks( $session ) { $this->setMwGlobals( array( // @todo FIXME: still used somewhere - 'wgUser' => self::$users['uploader']->user, + 'wgUser' => self::$users['uploader']->getUser(), ) ); $chunkSize = 1048576; @@ -451,9 +451,9 @@ class ApiUploadTest extends ApiTestCaseUpload { $chunkSessionKey = false; $resultOffset = 0; // Open the file: - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $handle = fopen( $filePath, "r" ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $handle === false ) { $this->markTestIncomplete( "could not open file: $filePath" ); @@ -461,9 +461,9 @@ class ApiUploadTest extends ApiTestCaseUpload { while ( !feof( $handle ) ) { // Get the current chunk - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $chunkData = fread( $handle, $chunkSize ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); // Upload the current chunk into the $_FILE object: $this->fakeUploadChunk( 'chunk', 'blob', $mimeType, $chunkData ); @@ -473,7 +473,7 @@ class ApiUploadTest extends ApiTestCaseUpload { // Upload fist chunk ( and get the session key ) try { list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -501,7 +501,7 @@ class ApiUploadTest extends ApiTestCaseUpload { // Upload current chunk try { list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -541,7 +541,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; } diff --git a/tests/phpunit/includes/api/format/ApiFormatDumpTest.php b/tests/phpunit/includes/api/format/ApiFormatDumpTest.php deleted file mode 100644 index c0f67f8d..00000000 --- a/tests/phpunit/includes/api/format/ApiFormatDumpTest.php +++ /dev/null @@ -1,63 +0,0 @@ -<?php - -/** - * @group API - * @covers ApiFormatDump - */ -class ApiFormatDumpTest extends ApiFormatTestBase { - - protected $printerName = 'dump'; - - public static function provideGeneralEncoding() { - // Sigh. Docs claim it's a boolean, but can have values 0, 1, or 2. - // Fortunately wfIniGetBool does the right thing. - if ( wfIniGetBool( 'xdebug.overload_var_dump' ) ) { - return array( - array( array(), 'Cannot test ApiFormatDump when xDebug overloads var_dump', array( 'SKIP' => true ) ), - ); - } - - $warning = "\n [\"warnings\"]=>\n array(1) {\n [\"dump\"]=>\n array(1) {\n [\"*\"]=>\n" . - " string(64) \"format=dump has been deprecated. Please use format=json instead.\"\n" . - " }\n }"; - - return array( - // Basic types - array( array( null ), "array(2) {{$warning}\n [0]=>\n NULL\n}\n" ), - array( array( true ), "array(2) {{$warning}\n [0]=>\n string(0) \"\"\n}\n" ), - array( array( false ), "array(1) {{$warning}\n}\n" ), - array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), - "array(2) {{$warning}\n [0]=>\n bool(true)\n}\n" ), - array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), - "array(2) {{$warning}\n [0]=>\n bool(false)\n}\n" ), - array( array( 42 ), "array(2) {{$warning}\n [0]=>\n int(42)\n}\n" ), - array( array( 42.5 ), "array(2) {{$warning}\n [0]=>\n float(42.5)\n}\n" ), - array( array( 1e42 ), "array(2) {{$warning}\n [0]=>\n float(1.0E+42)\n}\n" ), - array( array( 'foo' ), "array(2) {{$warning}\n [0]=>\n string(3) \"foo\"\n}\n" ), - array( array( 'fóo' ), "array(2) {{$warning}\n [0]=>\n string(4) \"fóo\"\n}\n" ), - - // Arrays - array( array( array() ), "array(2) {{$warning}\n [0]=>\n array(0) {\n }\n}\n" ), - array( array( array( 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n int(1)\n }\n}\n" ), - array( array( array( 'x' => 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [\"x\"]=>\n int(1)\n }\n}\n" ), - array( array( array( 2 => 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [2]=>\n int(1)\n }\n}\n" ), - array( array( (object)array() ), "array(2) {{$warning}\n [0]=>\n array(0) {\n }\n}\n" ), - array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n int(1)\n }\n}\n" ), - array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n int(1)\n }\n}\n" ), - array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [\"x\"]=>\n int(1)\n }\n}\n" ), - array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), - "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n array(2) {\n [\"key\"]=>\n string(1) \"x\"\n [\"*\"]=>\n int(1)\n }\n }\n}\n" ), - array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [\"x\"]=>\n int(1)\n }\n}\n" ), - array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), "array(2) {{$warning}\n [0]=>\n array(2) {\n [0]=>\n string(1) \"a\"\n [1]=>\n string(1) \"b\"\n }\n}\n" ), - - // Content - array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), - "array(2) {{$warning}\n [\"*\"]=>\n string(3) \"foo\"\n}\n" ), - - // BC Subelements - array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), - "array(2) {{$warning}\n [\"foo\"]=>\n array(1) {\n [\"*\"]=>\n string(3) \"foo\"\n }\n}\n" ), - ); - } - -} diff --git a/tests/phpunit/includes/api/format/ApiFormatWddxTest.php b/tests/phpunit/includes/api/format/ApiFormatWddxTest.php deleted file mode 100644 index 07111300..00000000 --- a/tests/phpunit/includes/api/format/ApiFormatWddxTest.php +++ /dev/null @@ -1,80 +0,0 @@ -<?php - -/** - * @group API - * @covers ApiFormatWddx - */ -class ApiFormatWddxTest extends ApiFormatTestBase { - - protected $printerName = 'wddx'; - - public static function provideGeneralEncoding() { - if ( ApiFormatWddx::useSlowPrinter() ) { - return array( - array( array(), 'Fast Wddx printer is unavailable', array( 'SKIP' => true ) ) - ); - } - return self::provideEncoding(); - } - - public static function provideEncoding() { - $p = '<wddxPacket version=\'1.0\'><header/><data><struct><var name=\'warnings\'><struct><var name=\'wddx\'><struct><var name=\'*\'><string>format=wddx has been deprecated. Please use format=json instead.</string></var></struct></var></struct></var>'; - $s = '</struct></data></wddxPacket>'; - - return array( - // Basic types - array( array( null ), "{$p}<var name='0'><null/></var>{$s}" ), - array( array( true ), "{$p}<var name='0'><string></string></var>{$s}" ), - array( array( false ), "{$p}{$s}" ), - array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), - "{$p}<var name='0'><boolean value='true'/></var>{$s}" ), - array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), - "{$p}<var name='0'><boolean value='false'/></var>{$s}" ), - array( array( 42 ), "{$p}<var name='0'><number>42</number></var>{$s}" ), - array( array( 42.5 ), "{$p}<var name='0'><number>42.5</number></var>{$s}" ), - array( array( 1e42 ), "{$p}<var name='0'><number>1.0E+42</number></var>{$s}" ), - array( array( 'foo' ), "{$p}<var name='0'><string>foo</string></var>{$s}" ), - array( array( 'fóo' ), "{$p}<var name='0'><string>fóo</string></var>{$s}" ), - - // Arrays and objects - array( array( array() ), "{$p}<var name='0'><array length='0'></array></var>{$s}" ), - array( array( array( 1 ) ), "{$p}<var name='0'><array length='1'><number>1</number></array></var>{$s}" ), - array( array( array( 'x' => 1 ) ), "{$p}<var name='0'><struct><var name='x'><number>1</number></var></struct></var>{$s}" ), - array( array( array( 2 => 1 ) ), "{$p}<var name='0'><struct><var name='2'><number>1</number></var></struct></var>{$s}" ), - array( array( (object)array() ), "{$p}<var name='0'><struct></struct></var>{$s}" ), - array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), "{$p}<var name='0'><struct><var name='0'><number>1</number></var></struct></var>{$s}" ), - array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), "{$p}<var name='0'><array length='1'><number>1</number></array></var>{$s}" ), - array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), "{$p}<var name='0'><struct><var name='x'><number>1</number></var></struct></var>{$s}" ), - array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), - "{$p}<var name='0'><array length='1'><struct><var name='key'><string>x</string></var><var name='*'><number>1</number></var></struct></array></var>{$s}" ), - array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), "{$p}<var name='0'><struct><var name='x'><number>1</number></var></struct></var>{$s}" ), - array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), "{$p}<var name='0'><array length='2'><string>a</string><string>b</string></array></var>{$s}" ), - - // Content - array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), - "{$p}<var name='*'><string>foo</string></var>{$s}" ), - - // BC Subelements - array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), - "{$p}<var name='foo'><struct><var name='*'><string>foo</string></var></struct></var>{$s}" ), - ); - } - - /** - * @dataProvider provideEncoding - */ - public function testSlowEncoding( array $data, $expect, array $params = array() ) { - // Adjust expectation for differences between fast and slow printers. - $expect = str_replace( '\'', '"', $expect ); - $expect = str_replace( '/>', ' />', $expect ); - $expect = '<?xml version="1.0"?>' . $expect; - - $this->assertSame( $expect, $this->encodeData( $params, $data, 'ApiFormatWddxTest_SlowWddx' ) ); - } -} - -class ApiFormatWddxTest_SlowWddx extends ApiFormatWddx { - public static function useSlowPrinter() { - return true; - } -} diff --git a/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php b/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php index ce2f70de..db61bc80 100644 --- a/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php +++ b/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php @@ -57,10 +57,9 @@ abstract class ApiQueryContinueTestBase extends ApiQueryTestBase { } else { $params['action'] = 'query'; } - if ( $useContinue && !isset( $params['continue'] ) ) { + // Silence warning + if ( !isset( $params['continue'] ) ) { $params['continue'] = ''; - } else { - $params['rawcontinue'] = '1'; } $count = 0; $result = array(); diff --git a/tests/phpunit/includes/api/query/ApiQueryTest.php b/tests/phpunit/includes/api/query/ApiQueryTest.php index 5f061b50..61b992ba 100644 --- a/tests/phpunit/includes/api/query/ApiQueryTest.php +++ b/tests/phpunit/includes/api/query/ApiQueryTest.php @@ -131,7 +131,7 @@ class ApiQueryTest extends ApiTestCase { ); $queryApi = new ApiQuery( $api, 'query' ); $modules = $queryApi->getModuleManager()->getNamesWithClasses(); - foreach( $modules as $name => $class ) { + foreach ( $modules as $name => $class ) { $this->assertArrayHasKey( $class, $classes, diff --git a/tests/phpunit/includes/api/query/ApiQueryTestBase.php b/tests/phpunit/includes/api/query/ApiQueryTestBase.php index dabf72e0..d5fa4542 100644 --- a/tests/phpunit/includes/api/query/ApiQueryTestBase.php +++ b/tests/phpunit/includes/api/query/ApiQueryTestBase.php @@ -56,12 +56,12 @@ STR; * @return array */ private function validateRequestExpectedPair( $v ) { - $this->assertType( 'array', $v, self::PARAM_ASSERT ); + $this->assertInternalType( 'array', $v, self::PARAM_ASSERT ); $this->assertEquals( 2, count( $v ), self::PARAM_ASSERT ); $this->assertArrayHasKey( 0, $v, self::PARAM_ASSERT ); $this->assertArrayHasKey( 1, $v, self::PARAM_ASSERT ); - $this->assertType( 'array', $v[0], self::PARAM_ASSERT ); - $this->assertType( 'array', $v[1], self::PARAM_ASSERT ); + $this->assertInternalType( 'array', $v[0], self::PARAM_ASSERT ); + $this->assertInternalType( 'array', $v[1], self::PARAM_ASSERT ); return $v; } @@ -87,6 +87,7 @@ STR; /** * Checks that the request's result matches the expected results. + * Assumes no rawcontinue and a complete batch. * @param array $values Array is a two element array( request, expected_results ) * @param array $session * @param bool $appendModule @@ -99,8 +100,9 @@ STR; if ( !array_key_exists( 'action', $req ) ) { $req['action'] = 'query'; } - if ( !array_key_exists( 'continue', $req ) ) { - $req['rawcontinue'] = '1'; + // Silence warning + if ( !isset( $params['continue'] ) ) { + $params['continue'] = ''; } foreach ( $req as &$val ) { if ( is_array( $val ) ) { @@ -108,7 +110,7 @@ STR; } } $result = $this->doApiRequest( $req, $session, $appendModule, $user ); - $this->assertResult( array( 'query' => $exp ), $result[0], $req ); + $this->assertResult( array( 'batchcomplete' => true, 'query' => $exp ), $result[0], $req ); } protected function assertResult( $exp, $result, $message = '' ) { diff --git a/tests/phpunit/includes/cache/MessageCacheTest.php b/tests/phpunit/includes/cache/MessageCacheTest.php index 442e9f9f..5302b363 100644 --- a/tests/phpunit/includes/cache/MessageCacheTest.php +++ b/tests/phpunit/includes/cache/MessageCacheTest.php @@ -52,7 +52,7 @@ class MessageCacheTest extends MediaWikiLangTestCase { $this->makePage( 'MessageCacheTest-FullKeyTest', 'ru' ); // In content language -- get base if no derivative - $this->makePage( 'FallbackLanguageTest-NoDervContLang', 'de', 'de/none', false ); + $this->makePage( 'FallbackLanguageTest-NoDervContLang', 'de', 'de/none' ); } /** @@ -61,15 +61,14 @@ class MessageCacheTest extends MediaWikiLangTestCase { * @param string $title Title of page to be created * @param string $lang Language and content of the created page * @param string|null $content Content of the created page, or null for a generic string - * @param bool $createSubPage Set to false if a root page should be created */ - protected function makePage( $title, $lang, $content = null, $createSubPage = true ) { + protected function makePage( $title, $lang, $content = null ) { global $wgContLang; if ( $content === null ) { $content = $lang; } - if ( $lang !== $wgContLang->getCode() || $createSubPage ) { + if ( $lang !== $wgContLang->getCode() ) { $title = "$title/$lang"; } @@ -125,4 +124,26 @@ class MessageCacheTest extends MediaWikiLangTestCase { ); } + /** + * @dataProvider provideNormalizeKey + */ + public function testNormalizeKey( $key, $expected ) { + $actual = MessageCache::normalizeKey( $key ); + $this->assertEquals( $expected, $actual ); + } + + public function provideNormalizeKey() { + return array( + array( 'Foo', 'foo' ), + array( 'foo', 'foo' ), + array( 'fOo', 'fOo' ), + array( 'FOO', 'fOO' ), + array( 'Foo bar', 'foo_bar' ), + array( 'Ćab', 'ćab' ), + array( 'Ćab_e 3', 'ćab_e_3' ), + array( 'ĆAB', 'ćAB' ), + array( 'ćab', 'ćab' ), + array( 'ćaB', 'ćaB' ), + ); + } } diff --git a/tests/phpunit/includes/changes/RecentChangeTest.php b/tests/phpunit/includes/changes/RecentChangeTest.php index b3cb7b52..4d1a936e 100644 --- a/tests/phpunit/includes/changes/RecentChangeTest.php +++ b/tests/phpunit/includes/changes/RecentChangeTest.php @@ -10,8 +10,8 @@ class RecentChangeTest extends MediaWikiTestCase { protected $user_comment; protected $context; - public function __construct() { - parent::__construct(); + public function setUp() { + parent::setUp(); $this->title = Title::newFromText( 'SomeTitle' ); $this->target = Title::newFromText( 'TestTarget' ); @@ -22,6 +22,26 @@ class RecentChangeTest extends MediaWikiTestCase { } /** + * @covers RecentChange::newFromRow + * @covers RecentChange::loadFromRow + */ + public function testNewFromRow() { + $row = new stdClass(); + $row->rc_foo = 'AAA'; + $row->rc_timestamp = '20150921134808'; + $row->rc_deleted = 'bar'; + + $rc = RecentChange::newFromRow( $row ); + + $expected = array( + 'rc_foo' => 'AAA', + 'rc_timestamp' => '20150921134808', + 'rc_deleted' => 'bar', + ); + $this->assertEquals( $expected, $rc->getAttributes() ); + } + + /** * The testIrcMsgForAction* tests are supposed to cover the hacky * LogFormatter::getIRCActionText / bug 34508 * @@ -46,6 +66,7 @@ class RecentChangeTest extends MediaWikiTestCase { * - protect/protect * - protect/modifyprotect * - protect/unprotect + * - protect/move_prot * - upload/upload * - merge/merge * - import/upload @@ -59,289 +80,95 @@ class RecentChangeTest extends MediaWikiTestCase { */ /** - * @covers LogFormatter::getIRCActionText - */ - public function testIrcMsgForLogTypeBlock() { - $sep = $this->context->msg( 'colon-separator' )->text(); - - # block/block - $this->assertIRCComment( - $this->context->msg( 'blocklogentry', 'SomeTitle', 'duration', '(flags)' )->plain() - . $sep . $this->user_comment, - 'block', 'block', - array( - '5::duration' => 'duration', - '6::flags' => 'flags', - ), - $this->user_comment - ); - # block/unblock - $this->assertIRCComment( - $this->context->msg( 'unblocklogentry', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'block', 'unblock', - array(), - $this->user_comment - ); - # block/reblock - $this->assertIRCComment( - $this->context->msg( 'reblock-logentry', 'SomeTitle', 'duration', '(flags)' )->plain() - . $sep . $this->user_comment, - 'block', 'reblock', - array( - '5::duration' => 'duration', - '6::flags' => 'flags', - ), - $this->user_comment - ); - } - - /** - * @covers LogFormatter::getIRCActionText - */ - public function testIrcMsgForLogTypeDelete() { - $sep = $this->context->msg( 'colon-separator' )->text(); - - # delete/delete - $this->assertIRCComment( - $this->context->msg( 'deletedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'delete', 'delete', - array(), - $this->user_comment - ); - - # delete/restore - $this->assertIRCComment( - $this->context->msg( 'undeletedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'delete', 'restore', - array(), - $this->user_comment - ); - } - - /** - * @covers LogFormatter::getIRCActionText - */ - public function testIrcMsgForLogTypeNewusers() { - $this->assertIRCComment( - 'New user account', - 'newusers', 'newusers', - array() - ); - $this->assertIRCComment( - 'New user account', - 'newusers', 'create', - array() - ); - $this->assertIRCComment( - 'created new account SomeTitle', - 'newusers', 'create2', - array() - ); - $this->assertIRCComment( - 'Account created automatically', - 'newusers', 'autocreate', - array() - ); - } - - /** - * @covers LogFormatter::getIRCActionText - */ - public function testIrcMsgForLogTypeMove() { - $move_params = array( - '4::target' => $this->target->getPrefixedText(), - '5::noredir' => 0, - ); - $sep = $this->context->msg( 'colon-separator' )->text(); - - # move/move - $this->assertIRCComment( - $this->context->msg( '1movedto2', 'SomeTitle', 'TestTarget' ) - ->plain() . $sep . $this->user_comment, - 'move', 'move', - $move_params, - $this->user_comment - ); - - # move/move_redir - $this->assertIRCComment( - $this->context->msg( '1movedto2_redir', 'SomeTitle', 'TestTarget' ) - ->plain() . $sep . $this->user_comment, - 'move', 'move_redir', - $move_params, - $this->user_comment - ); - } - - /** - * @covers LogFormatter::getIRCActionText + * @covers RecentChange::parseParams */ - public function testIrcMsgForLogTypePatrol() { - # patrol/patrol - $this->assertIRCComment( - $this->context->msg( 'patrol-log-line', 'revision 777', '[[SomeTitle]]', '' )->plain(), - 'patrol', 'patrol', - array( - '4::curid' => '777', - '5::previd' => '666', - '6::auto' => 0, + public function testParseParams() { + $params = array( + 'root' => array( + 'A' => 1, + 'B' => 'two' ) ); - } - /** - * @covers LogFormatter::getIRCActionText - */ - public function testIrcMsgForLogTypeProtect() { - $protectParams = array( - '[edit=sysop] (indefinite) [move=sysop] (indefinite)' + $this->assertParseParams( + $params, + 'a:1:{s:4:"root";a:2:{s:1:"A";i:1;s:1:"B";s:3:"two";}}' ); - $sep = $this->context->msg( 'colon-separator' )->text(); - # protect/protect - $this->assertIRCComment( - $this->context->msg( 'protectedarticle', 'SomeTitle ' . $protectParams[0] ) - ->plain() . $sep . $this->user_comment, - 'protect', 'protect', - $protectParams, - $this->user_comment + $this->assertParseParams( + null, + null ); - # protect/unprotect - $this->assertIRCComment( - $this->context->msg( 'unprotectedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'protect', 'unprotect', - array(), - $this->user_comment + $this->assertParseParams( + null, + serialize( false ) ); - # protect/modify - $this->assertIRCComment( - $this->context->msg( 'modifiedarticleprotection', 'SomeTitle ' . $protectParams[0] ) - ->plain() . $sep . $this->user_comment, - 'protect', 'modify', - $protectParams, - $this->user_comment + $this->assertParseParams( + null, + 'not-an-array' ); } /** - * @covers LogFormatter::getIRCActionText + * @param array $expectedParseParams + * @param string|null $rawRcParams */ - public function testIrcMsgForLogTypeUpload() { - $sep = $this->context->msg( 'colon-separator' )->text(); + protected function assertParseParams( $expectedParseParams, $rawRcParams ) { + $rc = new RecentChange; + $rc->setAttribs( array( 'rc_params' => $rawRcParams ) ); - # upload/upload - $this->assertIRCComment( - $this->context->msg( 'uploadedimage', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'upload', 'upload', - array(), - $this->user_comment - ); + $actualParseParams = $rc->parseParams(); - # upload/overwrite - $this->assertIRCComment( - $this->context->msg( 'overwroteimage', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'upload', 'overwrite', - array(), - $this->user_comment - ); + $this->assertEquals( $expectedParseParams, $actualParseParams ); } /** - * @covers LogFormatter::getIRCActionText + * 50 mins and 100 mins are used here as the tests never take that long! + * @return array */ - public function testIrcMsgForLogTypeMerge() { - $sep = $this->context->msg( 'colon-separator' )->text(); - - # merge/merge - $this->assertIRCComment( - $this->context->msg( 'pagemerge-logentry', 'SomeTitle', 'Dest', 'timestamp' )->plain() - . $sep . $this->user_comment, - 'merge', 'merge', - array( - '4::dest' => 'Dest', - '5::mergepoint' => 'timestamp', - ), - $this->user_comment + public function provideIsInRCLifespan() { + return array( + array( 6000, time() - 3000, 0, true ), + array( 3000, time() - 6000, 0, false ), + array( 6000, time() - 3000, 6000, true ), + array( 3000, time() - 6000, 6000, true ), ); } /** - * @covers LogFormatter::getIRCActionText + * @covers RecentChange::isInRCLifespan + * @dataProvider provideIsInRCLifespan */ - public function testIrcMsgForLogTypeImport() { - $sep = $this->context->msg( 'colon-separator' )->text(); - - # import/upload - $this->assertIRCComment( - $this->context->msg( 'import-logentry-upload', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'import', 'upload', - array(), - $this->user_comment - ); + public function testIsInRCLifespan( $maxAge, $timestamp, $tolerance, $expected ) { + $this->setMwGlobals( 'wgRCMaxAge', $maxAge ); + $this->assertEquals( $expected, RecentChange::isInRCLifespan( $timestamp, $tolerance ) ); + } - # import/interwiki - $this->assertIRCComment( - $this->context->msg( 'import-logentry-interwiki', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'import', 'interwiki', - array(), - $this->user_comment + public function provideRCTypes() { + return array( + array( RC_EDIT, 'edit' ), + array( RC_NEW, 'new' ), + array( RC_LOG, 'log' ), + array( RC_EXTERNAL, 'external' ), ); } /** - * @todo Emulate these edits somehow and extract - * raw edit summary from RecentChange object - * -- + * @dataProvider provideRCTypes + * @covers RecentChange::parseFromRCType */ - /* - public function testIrcMsgForBlankingAES() { - // $this->context->msg( 'autosumm-blank', .. ); - } - - public function testIrcMsgForReplaceAES() { - // $this->context->msg( 'autosumm-replace', .. ); - } - - public function testIrcMsgForRollbackAES() { - // $this->context->msg( 'revertpage', .. ); + public function testParseFromRCType( $rcType, $type ) { + $this->assertEquals( $type, RecentChange::parseFromRCType( $rcType ) ); } - public function testIrcMsgForUndoAES() { - // $this->context->msg( 'undo-summary', .. ); - } - */ - /** - * @param string $expected Expected IRC text without colors codes - * @param string $type Log type (move, delete, suppress, patrol ...) - * @param string $action A log type action - * @param array $params - * @param string $comment (optional) A comment for the log action - * @param string $msg (optional) A message for PHPUnit :-) + * @dataProvider provideRCTypes + * @covers RecentChange::parseToRCType */ - protected function assertIRCComment( $expected, $type, $action, $params, - $comment = null, $msg = '' - ) { - $logEntry = new ManualLogEntry( $type, $action ); - $logEntry->setPerformer( $this->user ); - $logEntry->setTarget( $this->title ); - if ( $comment !== null ) { - $logEntry->setComment( $comment ); - } - $logEntry->setParameters( $params ); - - $formatter = LogFormatter::newFromEntry( $logEntry ); - $formatter->setContext( $this->context ); - - // Apply the same transformation as done in IRCColourfulRCFeedFormatter::getLine for rc_comment - $ircRcComment = IRCColourfulRCFeedFormatter::cleanupForIRC( $formatter->getIRCActionComment() ); - - $this->assertEquals( - $expected, - $ircRcComment, - $msg - ); + public function testParseToRCType( $rcType, $type ) { + $this->assertEquals( $rcType, RecentChange::parseToRCType( $type ) ); } + } diff --git a/tests/phpunit/includes/config/HashConfigTest.php b/tests/phpunit/includes/config/HashConfigTest.php index 06973b09..4aa3e30c 100644 --- a/tests/phpunit/includes/config/HashConfigTest.php +++ b/tests/phpunit/includes/config/HashConfigTest.php @@ -30,7 +30,7 @@ class HashConfigTest extends MediaWikiTestCase { public function testGet() { $conf = new HashConfig( array( 'one' => '1', - )); + ) ); $this->assertEquals( '1', $conf->get( 'one' ) ); $this->setExpectedException( 'ConfigException', 'HashConfig::get: undefined option' ); $conf->get( 'two' ); diff --git a/tests/phpunit/includes/content/ContentHandlerTest.php b/tests/phpunit/includes/content/ContentHandlerTest.php index 988a59ee..8178c12e 100644 --- a/tests/phpunit/includes/content/ContentHandlerTest.php +++ b/tests/phpunit/includes/content/ContentHandlerTest.php @@ -22,6 +22,7 @@ class ContentHandlerTest extends MediaWikiTestCase { 'wgContentHandlers' => array( CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler', CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler', + CONTENT_MODEL_JSON => 'JsonContentHandler', CONTENT_MODEL_CSS => 'CssContentHandler', CONTENT_MODEL_TEXT => 'TextContentHandler', 'testing' => 'DummyContentHandlerForTesting', @@ -51,19 +52,27 @@ class ContentHandlerTest extends MediaWikiTestCase { return array( array( 'Help:Foo', CONTENT_MODEL_WIKITEXT ), array( 'Help:Foo.js', CONTENT_MODEL_WIKITEXT ), + array( 'Help:Foo.css', CONTENT_MODEL_WIKITEXT ), + array( 'Help:Foo.json', CONTENT_MODEL_WIKITEXT ), array( 'Help:Foo/bar.js', CONTENT_MODEL_WIKITEXT ), array( 'User:Foo', CONTENT_MODEL_WIKITEXT ), array( 'User:Foo.js', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo.css', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo.json', CONTENT_MODEL_WIKITEXT ), array( 'User:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ), array( 'User:Foo/bar.css', CONTENT_MODEL_CSS ), + array( 'User:Foo/bar.json', CONTENT_MODEL_JSON ), + array( 'User:Foo/bar.json.nope', CONTENT_MODEL_WIKITEXT ), array( 'User talk:Foo/bar.css', CONTENT_MODEL_WIKITEXT ), array( 'User:Foo/bar.js.xxx', CONTENT_MODEL_WIKITEXT ), array( 'User:Foo/bar.xxx', CONTENT_MODEL_WIKITEXT ), array( 'MediaWiki:Foo.js', CONTENT_MODEL_JAVASCRIPT ), - array( 'MediaWiki:Foo.css', CONTENT_MODEL_CSS ), array( 'MediaWiki:Foo.JS', CONTENT_MODEL_WIKITEXT ), - array( 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.css', CONTENT_MODEL_CSS ), array( 'MediaWiki:Foo.css.xxx', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.json', CONTENT_MODEL_JSON ), + array( 'MediaWiki:Foo.JSON', CONTENT_MODEL_WIKITEXT ), ); } @@ -338,6 +347,11 @@ class ContentHandlerTest extends MediaWikiTestCase { } */ + public function testSupportsDirectEditing() { + $handler = new DummyContentHandlerForTesting( CONTENT_MODEL_JSON ); + $this->assertFalse( $handler->supportsDirectEditing(), 'direct editing is not supported' ); + } + /** * @covers ContentHandler::runLegacyHooks */ @@ -365,164 +379,3 @@ class ContentHandlerTest extends MediaWikiTestCase { return true; } } - -class DummyContentHandlerForTesting extends ContentHandler { - - public function __construct( $dataModel ) { - parent::__construct( $dataModel, array( "testing" ) ); - } - - /** - * @see ContentHandler::serializeContent - * - * @param Content $content - * @param string $format - * - * @return string - */ - public function serializeContent( Content $content, $format = null ) { - return $content->serialize(); - } - - /** - * @see ContentHandler::unserializeContent - * - * @param string $blob - * @param string $format Unused. - * - * @return Content - */ - public function unserializeContent( $blob, $format = null ) { - $d = unserialize( $blob ); - - return new DummyContentForTesting( $d ); - } - - /** - * Creates an empty Content object of the type supported by this ContentHandler. - * - */ - public function makeEmptyContent() { - return new DummyContentForTesting( '' ); - } -} - -class DummyContentForTesting extends AbstractContent { - - public function __construct( $data ) { - parent::__construct( "testing" ); - - $this->data = $data; - } - - public function serialize( $format = null ) { - return serialize( $this->data ); - } - - /** - * @return string A string representing the content in a way useful for - * building a full text search index. If no useful representation exists, - * this method returns an empty string. - */ - public function getTextForSearchIndex() { - return ''; - } - - /** - * @return string|bool The wikitext to include when another page includes this content, - * or false if the content is not includable in a wikitext page. - */ - public function getWikitextForTransclusion() { - return false; - } - - /** - * Returns a textual representation of the content suitable for use in edit - * summaries and log messages. - * - * @param int $maxlength Maximum length of the summary text. - * @return string The summary text. - */ - public function getTextForSummary( $maxlength = 250 ) { - return ''; - } - - /** - * Returns native represenation of the data. Interpretation depends on the data model used, - * as given by getDataModel(). - * - * @return mixed The native representation of the content. Could be a string, a nested array - * structure, an object, a binary blob... anything, really. - */ - public function getNativeData() { - return $this->data; - } - - /** - * returns the content's nominal size in bogo-bytes. - * - * @return int - */ - public function getSize() { - return strlen( $this->data ); - } - - /** - * Return a copy of this Content object. The following must be true for the object returned - * if $copy = $original->copy() - * - * * get_class($original) === get_class($copy) - * * $original->getModel() === $copy->getModel() - * * $original->equals( $copy ) - * - * If and only if the Content object is imutable, the copy() method can and should - * return $this. That is, $copy === $original may be true, but only for imutable content - * objects. - * - * @return Content A copy of this object - */ - public function copy() { - return $this; - } - - /** - * Returns true if this content is countable as a "real" wiki page, provided - * that it's also in a countable location (e.g. a current revision in the main namespace). - * - * @param bool $hasLinks If it is known whether this content contains links, - * provide this information here, to avoid redundant parsing to find out. - * @return bool - */ - public function isCountable( $hasLinks = null ) { - return false; - } - - /** - * @param Title $title - * @param int $revId Unused. - * @param null|ParserOptions $options - * @param bool $generateHtml Whether to generate Html (default: true). If false, the result - * of calling getText() on the ParserOutput object returned by this method is undefined. - * - * @return ParserOutput - */ - public function getParserOutput( Title $title, $revId = null, - ParserOptions $options = null, $generateHtml = true - ) { - return new ParserOutput( $this->getNativeData() ); - } - - /** - * @see AbstractContent::fillParserOutput() - * - * @param Title $title Context title for parsing - * @param int|null $revId Revision ID (for {{REVISIONID}}) - * @param ParserOptions $options Parser options - * @param bool $generateHtml Whether or not to generate HTML - * @param ParserOutput &$output The output object to fill (reference). - */ - protected function fillParserOutput( Title $title, $revId, - ParserOptions $options, $generateHtml, ParserOutput &$output ) { - $output = new ParserOutput( $this->getNativeData() ); - } -} diff --git a/tests/phpunit/includes/content/CssContentHandlerTest.php b/tests/phpunit/includes/content/CssContentHandlerTest.php new file mode 100644 index 00000000..e1785a96 --- /dev/null +++ b/tests/phpunit/includes/content/CssContentHandlerTest.php @@ -0,0 +1,30 @@ +<?php + +class CssContentHandlerTest extends MediaWikiTestCase { + + /** + * @dataProvider provideMakeRedirectContent + * @covers CssContentHandler::makeRedirectContent + */ + public function testMakeRedirectContent( $title, $expected ) { + $this->setMwGlobals( array( + 'wgServer' => '//example.org', + 'wgScript' => '/w/index.php', + ) ); + $ch = new CssContentHandler(); + $content = $ch->makeRedirectContent( Title::newFromText( $title ) ); + $this->assertInstanceOf( 'CssContent', $content ); + $this->assertEquals( $expected, $content->serialize( CONTENT_FORMAT_CSS ) ); + } + + /** + * Keep this in sync with CssContentTest::provideGetRedirectTarget() + */ + public static function provideMakeRedirectContent() { + return array( + array( 'MediaWiki:MonoBook.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=MediaWiki:MonoBook.css&action=raw&ctype=text/css);" ), + array( 'User:FooBar/common.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=User:FooBar/common.css&action=raw&ctype=text/css);" ), + array( 'Gadget:FooBaz.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);" ), + ); + } +} diff --git a/tests/phpunit/includes/content/CssContentTest.php b/tests/phpunit/includes/content/CssContentTest.php index 40484d3a..c4d87c24 100644 --- a/tests/phpunit/includes/content/CssContentTest.php +++ b/tests/phpunit/includes/content/CssContentTest.php @@ -4,6 +4,8 @@ * @group ContentHandler * @group Database * ^--- needed, because we do need the database to test link updates + * + * @FIXME this should not extend JavaScriptContentTest. */ class CssContentTest extends JavaScriptContentTest { @@ -68,7 +70,48 @@ class CssContentTest extends JavaScriptContentTest { $this->assertEquals( CONTENT_MODEL_CSS, $content->getContentHandler()->getModelID() ); } - public static function dataEquals() { + /** + * Redirects aren't supported + */ + public static function provideUpdateRedirect() { + return array( + array( + '#REDIRECT [[Someplace]]', + '#REDIRECT [[Someplace]]', + ), + ); + } + + /** + * @dataProvider provideGetRedirectTarget + */ + public function testGetRedirectTarget( $title, $text ) { + $this->setMwGlobals( array( + 'wgServer' => '//example.org', + 'wgScriptPath' => '/w', + 'wgScript' => '/w/index.php', + ) ); + $content = new CssContent( $text ); + $target = $content->getRedirectTarget(); + $this->assertEquals( $title, $target ? $target->getPrefixedText() : null ); + } + + /** + * Keep this in sync with CssContentHandlerTest::provideMakeRedirectContent() + */ + public static function provideGetRedirectTarget() { + return array( + array( 'MediaWiki:MonoBook.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=MediaWiki:MonoBook.css&action=raw&ctype=text/css);" ), + array( 'User:FooBar/common.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=User:FooBar/common.css&action=raw&ctype=text/css);" ), + array( 'Gadget:FooBaz.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);" ), + # No #REDIRECT comment + array( null, "@import url(//example.org/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);" ), + # Wrong domain + array( null, "/* #REDIRECT */@import url(//example.com/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);" ), + ); + } + + public static function dataEquals() { return array( array( new CssContent( 'hallo' ), null, false ), array( new CssContent( 'hallo' ), new CssContent( 'hallo' ), true ), diff --git a/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php b/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php new file mode 100644 index 00000000..0f41020f --- /dev/null +++ b/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php @@ -0,0 +1,30 @@ +<?php + +class JavaScriptContentHandlerTest extends MediaWikiTestCase { + + /** + * @dataProvider provideMakeRedirectContent + * @covers JavaScriptContentHandler::makeRedirectContent + */ + public function testMakeRedirectContent( $title, $expected ) { + $this->setMwGlobals( array( + 'wgServer' => '//example.org', + 'wgScript' => '/w/index.php', + ) ); + $ch = new JavaScriptContentHandler(); + $content = $ch->makeRedirectContent( Title::newFromText( $title ) ); + $this->assertInstanceOf( 'JavaScriptContent', $content ); + $this->assertEquals( $expected, $content->serialize( CONTENT_FORMAT_JAVASCRIPT ) ); + } + + /** + * Keep this in sync with JavaScriptContentTest::provideGetRedirectTarget() + */ + public static function provideMakeRedirectContent() { + return array( + array( 'MediaWiki:MonoBook.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=MediaWiki:MonoBook.js\u0026action=raw\u0026ctype=text/javascript");' ), + array( 'User:FooBar/common.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=User:FooBar/common.js\u0026action=raw\u0026ctype=text/javascript");' ), + array( 'Gadget:FooBaz.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=Gadget:FooBaz.js\u0026action=raw\u0026ctype=text/javascript");' ), + ); + } +} diff --git a/tests/phpunit/includes/content/JavaScriptContentTest.php b/tests/phpunit/includes/content/JavaScriptContentTest.php index 7193ec9f..0ee27129 100644 --- a/tests/phpunit/includes/content/JavaScriptContentTest.php +++ b/tests/phpunit/includes/content/JavaScriptContentTest.php @@ -251,16 +251,31 @@ class JavaScriptContentTest extends TextContentTest { /** * @covers JavaScriptContent::updateRedirect + * @dataProvider provideUpdateRedirect */ - public function testUpdateRedirect() { + public function testUpdateRedirect( $oldText, $expectedText) { + $this->setMwGlobals( array( + 'wgServer' => '//example.org', + 'wgScriptPath' => '/w/index.php', + ) ); $target = Title::newFromText( "testUpdateRedirect_target" ); - $content = $this->newContent( "#REDIRECT [[Someplace]]" ); + $content = new JavaScriptContent( $oldText ); $newContent = $content->updateRedirect( $target ); - $this->assertTrue( - $content->equals( $newContent ), - "content should be unchanged since it's not wikitext" + $this->assertEquals( $expectedText, $newContent->getNativeData() ); + } + + public static function provideUpdateRedirect() { + return array( + array( + '#REDIRECT [[Someplace]]', + '#REDIRECT [[Someplace]]', + ), + array( + '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=MediaWiki:MonoBook.js\u0026action=raw\u0026ctype=text/javascript");', + '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=TestUpdateRedirect_target\u0026action=raw\u0026ctype=text/javascript");' + ) ); } @@ -290,4 +305,32 @@ class JavaScriptContentTest extends TextContentTest { array( new JavaScriptContent( "hallo" ), new JavaScriptContent( "HALLO" ), false ), ); } + + /** + * @dataProvider provideGetRedirectTarget + */ + public function testGetRedirectTarget( $title, $text ) { + $this->setMwGlobals( array( + 'wgServer' => '//example.org', + 'wgScriptPath' => '/w/index.php', + ) ); + $content = new JavaScriptContent( $text ); + $target = $content->getRedirectTarget(); + $this->assertEquals( $title, $target ? $target->getPrefixedText() : null ); + } + + /** + * Keep this in sync with JavaScriptContentHandlerTest::provideMakeRedirectContent() + */ + public static function provideGetRedirectTarget() { + return array( + array( 'MediaWiki:MonoBook.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=MediaWiki:MonoBook.js\u0026action=raw\u0026ctype=text/javascript");' ), + array( 'User:FooBar/common.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=User:FooBar/common.js\u0026action=raw\u0026ctype=text/javascript");' ), + array( 'Gadget:FooBaz.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=Gadget:FooBaz.js\u0026action=raw\u0026ctype=text/javascript");' ), + // No #REDIRECT comment + array( null, 'mw.loader.load("//example.org/w/index.php?title=MediaWiki:NoRedirect.js\u0026action=raw\u0026ctype=text/javascript");' ), + // Different domain + array( null, '/* #REDIRECT */mw.loader.load("//example.com/w/index.php?title=MediaWiki:OtherWiki.js\u0026action=raw\u0026ctype=text/javascript");' ), + ); + } } diff --git a/tests/phpunit/includes/content/JsonContentTest.php b/tests/phpunit/includes/content/JsonContentTest.php index cccfe7b1..8a9d2ab0 100644 --- a/tests/phpunit/includes/content/JsonContentTest.php +++ b/tests/phpunit/includes/content/JsonContentTest.php @@ -138,7 +138,7 @@ class JsonContentTest extends MediaWikiLangTestCase { '<tr><th>0</th><td class="value">"bar"</td></tr></tbody></table>' ), array( - (object)array( '<script>alert("evil!")</script>'), + (object)array( '<script>alert("evil!")</script>' ), '<table class="mw-json"><tbody><tr><th>0</th><td class="value">"' . '<script>alert("evil!")</script>"' . '</td></tr></tbody></table>', diff --git a/tests/phpunit/includes/content/TextContentHandlerTest.php b/tests/phpunit/includes/content/TextContentHandlerTest.php new file mode 100644 index 00000000..492fec6b --- /dev/null +++ b/tests/phpunit/includes/content/TextContentHandlerTest.php @@ -0,0 +1,12 @@ +<?php + +/** + * @group ContentHandler + */ +class TextContentHandlerTest extends MediaWikiLangTestCase { + public function testSupportsDirectEditing() { + $handler = new TextContentHandler(); + $this->assertTrue( $handler->supportsDirectEditing(), 'direct editing is supported' ); + } + +} diff --git a/tests/phpunit/includes/content/TextContentTest.php b/tests/phpunit/includes/content/TextContentTest.php index dd61f85b..fe263756 100644 --- a/tests/phpunit/includes/content/TextContentTest.php +++ b/tests/phpunit/includes/content/TextContentTest.php @@ -27,10 +27,16 @@ class TextContentTest extends MediaWikiLangTestCase { CONTENT_MODEL_JAVASCRIPT, ), 'wgUseTidy' => false, - 'wgAlwaysUseTidy' => false, 'wgCapitalLinks' => true, 'wgHooks' => array(), // bypass hook ContentGetParserOutput that force custom rendering ) ); + + MWTidy::destroySingleton(); + } + + protected function tearDown() { + MWTidy::destroySingleton(); + parent::tearDown(); } public function newContent( $text ) { diff --git a/tests/phpunit/includes/content/WikitextContentHandlerTest.php b/tests/phpunit/includes/content/WikitextContentHandlerTest.php index 38fb5733..361238b7 100644 --- a/tests/phpunit/includes/content/WikitextContentHandlerTest.php +++ b/tests/phpunit/includes/content/WikitextContentHandlerTest.php @@ -115,6 +115,11 @@ class WikitextContentHandlerTest extends MediaWikiLangTestCase { $this->assertEquals( $supported, $this->handler->isSupportedFormat( $format ) ); } + public function testSupportsDirectEditing() { + $handler = new WikiTextContentHandler(); + $this->assertTrue( $handler->supportsDirectEditing(), 'direct editing is supported' ); + } + public static function dataMerge3() { return array( array( diff --git a/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php index b4292a60..42ea58e5 100644 --- a/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php +++ b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php @@ -181,7 +181,7 @@ class DatabaseMysqlBaseTest extends MediaWikiTestCase { array( 'Tables_in_' => 'view2' ), array( 'Tables_in_' => 'myview' ), false # no more rows - )); + ) ); return $db; } /** diff --git a/tests/phpunit/includes/db/DatabaseSqliteTest.php b/tests/phpunit/includes/db/DatabaseSqliteTest.php index 645baf1f..3db9172a 100644 --- a/tests/phpunit/includes/db/DatabaseSqliteTest.php +++ b/tests/phpunit/includes/db/DatabaseSqliteTest.php @@ -1,12 +1,13 @@ <?php -class MockDatabaseSqlite extends DatabaseSqlite { +class DatabaseSqliteMock extends DatabaseSqlite { private $lastQuery; public static function newInstance( array $p = array() ) { $p['dbFilePath'] = ':memory:'; + $p['schema'] = false; - return new self( $p ); + return DatabaseBase::factory( 'SqliteMock', $p ); } function query( $sql, $fname = '', $tempIgnore = false ) { @@ -29,7 +30,7 @@ class MockDatabaseSqlite extends DatabaseSqlite { * @group medium */ class DatabaseSqliteTest extends MediaWikiTestCase { - /** @var MockDatabaseSqlite */ + /** @var DatabaseSqliteMock */ protected $db; protected function setUp() { @@ -38,7 +39,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { if ( !Sqlite::isPresent() ) { $this->markTestSkipped( 'No SQLite support detected' ); } - $this->db = MockDatabaseSqlite::newInstance(); + $this->db = DatabaseSqliteMock::newInstance(); if ( version_compare( $this->db->getServerVersion(), '3.6.0', '<' ) ) { $this->markTestSkipped( "SQLite at least 3.6 required, {$this->db->getServerVersion()} found" ); } @@ -188,18 +189,34 @@ class DatabaseSqliteTest extends MediaWikiTestCase { public function testDuplicateTableStructure() { $db = DatabaseSqlite::newStandaloneInstance( ':memory:' ); $db->query( 'CREATE TABLE foo(foo, barfoo)' ); + $db->query( 'CREATE INDEX index1 ON foo(foo)' ); + $db->query( 'CREATE UNIQUE INDEX index2 ON foo(barfoo)' ); $db->duplicateTableStructure( 'foo', 'bar' ); $this->assertEquals( 'CREATE TABLE "bar"(foo, barfoo)', $db->selectField( 'sqlite_master', 'sql', array( 'name' => 'bar' ) ), 'Normal table duplication' ); + $indexList = $db->query( 'PRAGMA INDEX_LIST("bar")' ); + $index = $indexList->next(); + $this->assertEquals( 'bar_index1', $index->name ); + $this->assertEquals( '0', $index->unique ); + $index = $indexList->next(); + $this->assertEquals( 'bar_index2', $index->name ); + $this->assertEquals( '1', $index->unique ); $db->duplicateTableStructure( 'foo', 'baz', true ); $this->assertEquals( 'CREATE TABLE "baz"(foo, barfoo)', $db->selectField( 'sqlite_temp_master', 'sql', array( 'name' => 'baz' ) ), 'Creation of temporary duplicate' ); + $indexList = $db->query( 'PRAGMA INDEX_LIST("baz")' ); + $index = $indexList->next(); + $this->assertEquals( 'baz_index1', $index->name ); + $this->assertEquals( '0', $index->unique ); + $index = $indexList->next(); + $this->assertEquals( 'baz_index2', $index->name ); + $this->assertEquals( '1', $index->unique ); $this->assertEquals( 0, $db->selectField( 'sqlite_master', 'COUNT(*)', array( 'name' => 'baz' ) ), 'Create a temporary duplicate only' diff --git a/tests/phpunit/includes/db/ORMTableTest.php b/tests/phpunit/includes/db/ORMTableTest.php index 338d931f..764560d5 100644 --- a/tests/phpunit/includes/db/ORMTableTest.php +++ b/tests/phpunit/includes/db/ORMTableTest.php @@ -68,25 +68,6 @@ class ORMTableTest extends MediaWikiTestCase { $this->assertInstanceOf( $class, $class::singleton() ); $this->assertTrue( $class::singleton() === $class::singleton() ); } - - /** - * @since 1.21 - */ - public function testIgnoreErrorsOverride() { - $table = $this->getTable(); - - $db = $table->getReadDbConnection(); - $db->ignoreErrors( true ); - - try { - $table->rawSelect( "this is invalid" ); - $this->fail( "An invalid query should trigger a DBQueryError even if ignoreErrors is enabled." ); - } catch ( DBQueryError $ex ) { - $this->assertTrue( true, "just making phpunit happy" ); - } - - $db->ignoreErrors( false ); - } } /** diff --git a/tests/phpunit/includes/debug/MWDebugTest.php b/tests/phpunit/includes/debug/MWDebugTest.php index 1abb47e7..7280a97b 100644 --- a/tests/phpunit/includes/debug/MWDebugTest.php +++ b/tests/phpunit/includes/debug/MWDebugTest.php @@ -12,11 +12,11 @@ class MWDebugTest extends MediaWikiTestCase { } /** Clear log before each test */ MWDebug::clearLog(); - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); } protected function tearDown() { - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); parent::tearDown(); } diff --git a/tests/phpunit/includes/debug/logging/LegacyLoggerTest.php b/tests/phpunit/includes/debug/logger/LegacyLoggerTest.php index 415fa045..1b3ce2ca 100644 --- a/tests/phpunit/includes/debug/logging/LegacyLoggerTest.php +++ b/tests/phpunit/includes/debug/logger/LegacyLoggerTest.php @@ -35,6 +35,8 @@ class LegacyLoggerTest extends MediaWikiTestCase { } public function provideInterpolate() { + $e = new \Exception( 'boom!' ); + $d = new \DateTime(); return array( array( 'no-op', @@ -68,6 +70,57 @@ class LegacyLoggerTest extends MediaWikiTestCase { ), '{ not interpolated }', ), + array( + '{null}', + array( + 'null' => null, + ), + '[Null]', + ), + array( + '{bool}', + array( + 'bool' => true, + ), + 'true', + ), + array( + '{float}', + array( + 'float' => 1.23, + ), + '1.23', + ), + array( + '{array}', + array( + 'array' => array( 1, 2, 3 ), + ), + '[Array(3)]', + ), + array( + '{exception}', + array( + 'exception' => $e, + ), + '[Exception ' . get_class( $e ) . '( ' . + $e->getFile() . ':' . $e->getLine() . ') ' . + $e->getMessage() . ']', + ), + array( + '{datetime}', + array( + 'datetime' => $d, + ), + $d->format( 'c' ), + ), + array( + '{object}', + array( + 'object' => new \stdClass, + ), + '[Object stdClass]', + ), ); } diff --git a/tests/phpunit/includes/debug/logger/MonologSpiTest.php b/tests/phpunit/includes/debug/logger/MonologSpiTest.php new file mode 100644 index 00000000..aa0a54ff --- /dev/null +++ b/tests/phpunit/includes/debug/logger/MonologSpiTest.php @@ -0,0 +1,136 @@ +<?php +/** + * 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 + */ + +namespace MediaWiki\Logger; + +use MediaWikiTestCase; +use TestingAccessWrapper; + +class MonologSpiTest extends MediaWikiTestCase { + + /** + * @covers MonologSpi::mergeConfig + */ + public function testMergeConfig() { + $base = array( + 'loggers' => array( + '@default' => array( + 'processors' => array( 'constructor' ), + 'handlers' => array( 'constructor' ), + ), + ), + 'processors' => array( + 'constructor' => array( + 'class' => 'constructor', + ), + ), + 'handlers' => array( + 'constructor' => array( + 'class' => 'constructor', + 'formatter' => 'constructor', + ), + ), + 'formatters' => array( + 'constructor' => array( + 'class' => 'constructor', + ), + ), + ); + + $fixture = new MonologSpi( $base ); + $this->assertSame( + $base, + TestingAccessWrapper::newFromObject( $fixture )->config + ); + + $fixture->mergeConfig( array( + 'loggers' => array( + 'merged' => array( + 'processors' => array( 'merged' ), + 'handlers' => array( 'merged' ), + ), + ), + 'processors' => array( + 'merged' => array( + 'class' => 'merged', + ), + ), + 'magic' => array( + 'idkfa' => array( 'xyzzy' ), + ), + 'handlers' => array( + 'merged' => array( + 'class' => 'merged', + 'formatter' => 'merged', + ), + ), + 'formatters' => array( + 'merged' => array( + 'class' => 'merged', + ), + ), + ) ); + $this->assertSame( + array( + 'loggers' => array( + '@default' => array( + 'processors' => array( 'constructor' ), + 'handlers' => array( 'constructor' ), + ), + 'merged' => array( + 'processors' => array( 'merged' ), + 'handlers' => array( 'merged' ), + ), + ), + 'processors' => array( + 'constructor' => array( + 'class' => 'constructor', + ), + 'merged' => array( + 'class' => 'merged', + ), + ), + 'handlers' => array( + 'constructor' => array( + 'class' => 'constructor', + 'formatter' => 'constructor', + ), + 'merged' => array( + 'class' => 'merged', + 'formatter' => 'merged', + ), + ), + 'formatters' => array( + 'constructor' => array( + 'class' => 'constructor', + ), + 'merged' => array( + 'class' => 'merged', + ), + ), + 'magic' => array( + 'idkfa' => array( 'xyzzy' ), + ), + ), + TestingAccessWrapper::newFromObject( $fixture )->config + ); + } + +} diff --git a/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php b/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php new file mode 100644 index 00000000..44242ed2 --- /dev/null +++ b/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php @@ -0,0 +1,64 @@ +<?php +/** + * 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 + */ + +namespace MediaWiki\Logger\Monolog; + +use MediaWikiTestCase; +use PHPUnit_Framework_Error_Notice; + +class AvroFormatterTest extends MediaWikiTestCase { + + protected function setUp() { + if ( !class_exists( 'AvroStringIO' ) ) { + $this->markTestSkipped( 'Avro is required for the AvroFormatterTest' ); + } + parent::setUp(); + } + + public function testSchemaNotAvailable() { + $formatter = new AvroFormatter( array() ); + $this->setExpectedException( 'PHPUnit_Framework_Error_Notice', "The schema for channel 'marty' is not available" ); + $formatter->format( array( 'channel' => 'marty' ) ); + } + + public function testSchemaNotAvailableReturnValue() { + $formatter = new AvroFormatter( array() ); + $noticeEnabled = PHPUnit_Framework_Error_Notice::$enabled; + // disable conversion of notices + PHPUnit_Framework_Error_Notice::$enabled = false; + // have to keep the user notice from being output + wfSuppressWarnings(); + $res = $formatter->format( array( 'channel' => 'marty' ) ); + wfRestoreWarnings(); + PHPUnit_Framework_Error_Notice::$enabled = $noticeEnabled; + $this->assertNull( $res ); + } + + public function testDoesSomethingWhenSchemaAvailable() { + $formatter = new AvroFormatter( array( 'string' => array( 'type' => 'string' ) ) ); + $res = $formatter->format( array( + 'channel' => 'string', + 'context' => 'better to be', + ) ); + $this->assertNotNull( $res ); + // basically just tell us if avro changes its string encoding + $this->assertEquals( base64_decode( 'GGJldHRlciB0byBiZQ==' ), $res ); + } +} diff --git a/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php b/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php new file mode 100644 index 00000000..090f439e --- /dev/null +++ b/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php @@ -0,0 +1,207 @@ +<?php +/** + * 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 + */ + +namespace MediaWiki\Logger\Monolog; + +use MediaWikiTestCase; +use Monolog\Logger; + +// not available in the version of phpunit mw uses, so copied into repo +require_once __DIR__ . '/../../../ConsecutiveParametersMatcher.php'; + +class KafkaHandlerTest extends MediaWikiTestCase { + + protected function setUp() { + if ( !class_exists( 'Monolog\Handler\AbstractProcessingHandler' ) + || !class_exists( 'Kafka\Produce' ) + ) { + $this->markTestSkipped( 'Monolog and Kafka are required for the KafkaHandlerTest' ); + } + + parent::setUp(); + } + + public function topicNamingProvider() { + return array( + array( array(), 'monolog_foo' ), + array( array( 'alias' => array( 'foo' => 'bar' ) ), 'bar' ) + ); + } + + /** + * @dataProvider topicNamingProvider + */ + public function testTopicNaming( $options, $expect ) { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects($this->any()) + ->method('getAvailablePartitions') + ->will($this->returnValue( array( 'A' ) ) ); + $produce->expects($this->once()) + ->method( 'setMessages' ) + ->with( $expect, $this->anything(), $this->anything() ); + + $handler = new KafkaHandler( $produce, $options ); + $handler->handle( array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ) ); + } + + public function swallowsExceptionsWhenRequested() { + return array( + // defaults to false + array( array(), true ), + // also try false explicitly + array( array( 'swallowExceptions' => false ), true ), + // turn it on + array( array( 'swallowExceptions' => true ), false ), + ); + } + + /** + * @dataProvider swallowsExceptionsWhenRequested + */ + public function testGetAvailablePartitionsException( $options, $expectException ) { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->throwException( new \Kafka\Exception ) ); + + if ( $expectException ) { + $this->setExpectedException( 'Kafka\Exception' ); + } + + $handler = new KafkaHandler( $produce, $options ); + $handler->handle( array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ) ); + + if ( !$expectException ) { + $this->assertTrue( true, 'no exception was thrown' ); + } + } + + /** + * @dataProvider swallowsExceptionsWhenRequested + */ + public function testSendException( $options, $expectException ) { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->returnValue( array( 'A' ) ) ); + $produce->expects( $this->any() ) + ->method( 'send' ) + ->will( $this->throwException( new \Kafka\Exception ) ); + + if ( $expectException ) { + $this->setExpectedException( 'Kafka\Exception' ); + } + + $handler = new KafkaHandler( $produce, $options ); + $handler->handle( array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ) ); + + if ( !$expectException ) { + $this->assertTrue( true, 'no exception was thrown' ); + } + } + + public function testHandlesNullFormatterResult() { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->returnValue( array( 'A' ) ) ); + $mockMethod = $produce->expects( $this->exactly( 2 ) ) + ->method( 'setMessages' ); + // evil hax + \TestingAccessWrapper::newFromObject( $mockMethod )->matcher->parametersMatcher = + new \PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters( array( + array( $this->anything(), $this->anything(), array( 'words' ) ), + array( $this->anything(), $this->anything(), array( 'lines' ) ) + ) ); + + $formatter = $this->getMock( 'Monolog\Formatter\FormatterInterface' ); + $formatter->expects( $this->any() ) + ->method( 'format' ) + ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) ); + + $handler = new KafkaHandler( $produce, array() ); + $handler->setFormatter( $formatter ); + for ( $i = 0; $i < 3; ++$i ) { + $handler->handle( array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ) ); + } + } + + + public function testBatchHandlesNullFormatterResult() { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->returnValue( array( 'A' ) ) ); + $produce->expects( $this->once() ) + ->method( 'setMessages' ) + ->with( $this->anything(), $this->anything(), array( 'words', 'lines' ) ); + + $formatter = $this->getMock( 'Monolog\Formatter\FormatterInterface' ); + $formatter->expects( $this->any() ) + ->method( 'format' ) + ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) ); + + $handler = new KafkaHandler( $produce, array() ); + $handler->setFormatter( $formatter ); + $handler->handleBatch( array( + array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ), + array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ), + array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ), + ) ); + } +} diff --git a/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php b/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php new file mode 100644 index 00000000..be23c4a2 --- /dev/null +++ b/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php @@ -0,0 +1,75 @@ +<?php +/** + * 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 + */ + +namespace MediaWiki\Logger\Monolog; + +use InvalidArgumentException; +use LengthException; +use LogicException; +use MediaWikiTestCase; +use TestingAccessWrapper; + +class LineFormatterTest extends MediaWikiTestCase { + + protected function setUp() { + if ( !class_exists( 'Monolog\Formatter\LineFormatter' ) ) { + $this->markTestSkipped( 'This test requires monolog to be installed' ); + } + parent::setUp(); + } + + /** + * @covers LineFormatter::normalizeException + */ + public function testNormalizeExceptionNoTrace() { + $fixture = new LineFormatter(); + $fixture->includeStacktraces( false ); + $fixture = TestingAccessWrapper::newFromObject( $fixture ); + $boom = new InvalidArgumentException( 'boom', 0, + new LengthException( 'too long', 0, + new LogicException( 'Spock wuz here' ) + ) + ); + $out = $fixture->normalizeException( $boom ); + $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); + $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); + $this->assertContains( "\nCaused by: [Exception LogicException]", $out ); + $this->assertNotContains( "\n #0", $out ); + } + + /** + * @covers LineFormatter::normalizeException + */ + public function testNormalizeExceptionTrace() { + $fixture = new LineFormatter(); + $fixture->includeStacktraces( true ); + $fixture = TestingAccessWrapper::newFromObject( $fixture ); + $boom = new InvalidArgumentException( 'boom', 0, + new LengthException( 'too long', 0, + new LogicException( 'Spock wuz here' ) + ) + ); + $out = $fixture->normalizeException( $boom ); + $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); + $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); + $this->assertContains( "\nCaused by: [Exception LogicException]", $out ); + $this->assertContains( "\n #0", $out ); + } +} diff --git a/tests/phpunit/includes/deferred/DeferredUpdatesTest.php b/tests/phpunit/includes/deferred/DeferredUpdatesTest.php index 5348c854..df4213ab 100644 --- a/tests/phpunit/includes/deferred/DeferredUpdatesTest.php +++ b/tests/phpunit/includes/deferred/DeferredUpdatesTest.php @@ -1,8 +1,9 @@ <?php class DeferredUpdatesTest extends MediaWikiTestCase { + public function testDoUpdatesWeb() { + $this->setMwGlobals( 'wgCommandLineMode', false ); - public function testDoUpdates() { $updates = array( '1' => 'deferred update 1', '2' => 'deferred update 2', @@ -35,4 +36,38 @@ class DeferredUpdatesTest extends MediaWikiTestCase { DeferredUpdates::doUpdates(); } + public function testDoUpdatesCLI() { + $this->setMwGlobals( 'wgCommandLineMode', true ); + + $updates = array( + '1' => 'deferred update 1', + '2' => 'deferred update 2', + '2-1' => 'deferred update 1 within deferred update 2', + '3' => 'deferred update 3', + ); + DeferredUpdates::addCallableUpdate( + function () use ( $updates ) { + echo $updates['1']; + } + ); + DeferredUpdates::addCallableUpdate( + function () use ( $updates ) { + echo $updates['2']; + DeferredUpdates::addCallableUpdate( + function () use ( $updates ) { + echo $updates['2-1']; + } + ); + } + ); + DeferredUpdates::addCallableUpdate( + function () use ( $updates ) { + echo $updates[3]; + } + ); + + $this->expectOutputString( implode( '', $updates ) ); + + DeferredUpdates::doUpdates(); + } } diff --git a/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php b/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php index 3bea9b31..a546bec1 100644 --- a/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php +++ b/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php @@ -69,11 +69,11 @@ class ArrayDiffFormatterTest extends MediaWikiTestCase { $otherTestCases = array(); $otherTestCases[] = array( - $this->getMockDiff( array( $this->getMockDiffOp( 'add', array( ), array( 'a1' ) ) ) ), + $this->getMockDiff( array( $this->getMockDiffOp( 'add', array(), array( 'a1' ) ) ) ), array( array( 'action' => 'add', 'new' => 'a1', 'newline' => 1 ) ), ); $otherTestCases[] = array( - $this->getMockDiff( array( $this->getMockDiffOp( 'add', array( ), array( 'a1', 'a2' ) ) ) ), + $this->getMockDiff( array( $this->getMockDiffOp( 'add', array(), array( 'a1', 'a2' ) ) ) ), array( array( 'action' => 'add', 'new' => 'a1', 'newline' => 1 ), array( 'action' => 'add', 'new' => 'a2', 'newline' => 2 ), diff --git a/tests/phpunit/includes/exception/HttpErrorTest.php b/tests/phpunit/includes/exception/HttpErrorTest.php new file mode 100644 index 00000000..66fe90c9 --- /dev/null +++ b/tests/phpunit/includes/exception/HttpErrorTest.php @@ -0,0 +1,65 @@ +<?php + +/** + * @todo tests for HttpError::report + * + * @covers HttpError + */ +class HttpErrorTest extends MediaWikiTestCase { + + public function testIsLoggable() { + $httpError = new HttpError( 500, 'server error!' ); + $this->assertFalse( $httpError->isLoggable(), 'http error is not loggable' ); + } + + public function testGetStatusCode() { + $httpError = new HttpError( 500, 'server error!' ); + $this->assertEquals( 500, $httpError->getStatusCode() ); + } + + /** + * @dataProvider getHtmlProvider + */ + public function testGetHtml( array $expected, $content, $header ) { + $httpError = new HttpError( 500, $content, $header ); + $errorHtml = $httpError->getHtml(); + + foreach ( $expected as $key => $html ) { + $this->assertContains( $html, $errorHtml, $key ); + } + } + + public function getHtmlProvider() { + return array( + array( + array( + 'head html' => '<head><title>Server Error 123</title></head>', + 'body html' => '<body><h1>Server Error 123</h1>' + . '<p>a server error!</p></body>' + ), + 'a server error!', + 'Server Error 123' + ), + array( + array( + 'head html' => '<head><title>loginerror</title></head>', + 'body html' => '<body><h1>loginerror</h1>' + . '<p>suspicious-userlogout</p></body>' + ), + new RawMessage( 'suspicious-userlogout' ), + new RawMessage( 'loginerror' ) + ), + array( + array( + 'head html' => '<html><head><title>Internal Server Error</title></head>', + 'body html' => '<body><h1>Internal Server Error</h1>' + . '<p>a server error!</p></body></html>' + ), + 'a server error!', + null + ) + ); + } + + +} diff --git a/tests/phpunit/includes/exception/MWExceptionTest.php b/tests/phpunit/includes/exception/MWExceptionTest.php index ef0f2a9e..f11fda32 100644 --- a/tests/phpunit/includes/exception/MWExceptionTest.php +++ b/tests/phpunit/includes/exception/MWExceptionTest.php @@ -178,7 +178,7 @@ class MWExceptionTest extends MediaWikiTestCase { $this->setMwGlobals( array( 'wgLogExceptionBacktrace' => true ) ); $json = json_decode( - MWExceptionHandler::jsonSerializeException( new $exClass()) + MWExceptionHandler::jsonSerializeException( new $exClass() ) ); $this->assertObjectHasAttribute( $key, $json, "JSON serialized exception is missing key '$key'" diff --git a/tests/phpunit/includes/filebackend/FileBackendTest.php b/tests/phpunit/includes/filebackend/FileBackendTest.php index bfca75ad..2e4942f0 100644 --- a/tests/phpunit/includes/filebackend/FileBackendTest.php +++ b/tests/phpunit/includes/filebackend/FileBackendTest.php @@ -2376,7 +2376,7 @@ class FileBackendTest extends MediaWikiTestCase { $status = Status::newGood(); $sl = $this->backend->getScopedFileLocks( $paths, LockManager::LOCK_EX, $status ); - $this->assertType( 'ScopedLock', $sl, + $this->assertInstanceOf( 'ScopedLock', $sl, "Scoped locking of files succeeded ($backendName)." ); $this->assertEquals( array(), $status->errors, "Scoped locking of files succeeded ($backendName)." ); @@ -2392,6 +2392,56 @@ class FileBackendTest extends MediaWikiTestCase { "Scoped unlocking of files succeeded with OK status ($backendName)." ); } + public function testReadAffinity() { + $be = TestingAccessWrapper::newFromObject( + new FileBackendMultiWrite( array( + 'name' => 'localtesting', + 'wikiId' => wfWikiId() . mt_rand(), + 'backends' => array( + array( // backend 0 + 'name' => 'multitesting0', + 'class' => 'MemoryFileBackend', + 'isMultiMaster' => false, + 'readAffinity' => true + ), + array( // backend 1 + 'name' => 'multitesting1', + 'class' => 'MemoryFileBackend', + 'isMultiMaster' => true + ) + ) + ) ) + ); + + $this->assertEquals( + 1, + $be->getReadIndexFromParams( array( 'latest' => 1 ) ), + 'Reads with "latest" flag use backend 1' + ); + $this->assertEquals( + 0, + $be->getReadIndexFromParams( array( 'latest' => 0 ) ), + 'Reads without "latest" flag use backend 0' + ); + + $p = 'container/test-cont/file.txt'; + $be->backends[0]->quickCreate( array( + 'dst' => "mwstore://multitesting0/$p", 'content' => 'cattitude' ) ); + $be->backends[1]->quickCreate( array( + 'dst' => "mwstore://multitesting1/$p", 'content' => 'princess of power' ) ); + + $this->assertEquals( + 'cattitude', + $be->getFileContents( array( 'src' => "mwstore://localtesting/$p" ) ), + "Non-latest read came from backend 0" + ); + $this->assertEquals( + 'princess of power', + $be->getFileContents( array( 'src' => "mwstore://localtesting/$p", 'latest' => 1 ) ), + "Latest read came from backend1" + ); + } + // helper function private function listToArray( $iter ) { return is_array( $iter ) ? $iter : iterator_to_array( $iter ); diff --git a/tests/phpunit/includes/filebackend/SwiftFileBackendTest.php b/tests/phpunit/includes/filebackend/SwiftFileBackendTest.php new file mode 100644 index 00000000..a618889c --- /dev/null +++ b/tests/phpunit/includes/filebackend/SwiftFileBackendTest.php @@ -0,0 +1,148 @@ +<?php + +/** + * @group FileRepo + * @group FileBackend + * @group medium + */ +class SwiftFileBackendTest extends MediaWikiTestCase { + /** @var TestingAccessWrapper Proxy to SwiftFileBackend */ + private $backend; + + protected function setUp() { + parent::setUp(); + + $this->backend = TestingAccessWrapper::newFromObject( + new SwiftFileBackend( array( + 'name' => 'local-swift-testing', + 'class' => 'SwiftFileBackend', + 'wikiId' => 'unit-testing', + 'lockManager' => LockManagerGroup::singleton()->get( 'fsLockManager' ), + 'swiftAuthUrl' => 'http://127.0.0.1:8080/auth', // unused + 'swiftUser' => 'test:tester', + 'swiftKey' => 'testing', + 'swiftTempUrlKey' => 'b3968d0207b54ece87cccc06515a89d4' // unused + ) ) + ); + } + + /** + * @dataProvider provider_testSanitzeHdrs + * @covers SwiftFileBackend::sanitzeHdrs + * @covers SwiftFileBackend::getCustomHeaders + */ + public function testSanitzeHdrs( $raw, $sanitized ) { + $hdrs = $this->backend->sanitizeHdrs( array( 'headers' => $raw ) ); + + $this->assertEquals( $hdrs, $sanitized, 'sanitizeHdrs() has expected result' ); + } + + public static function provider_testSanitzeHdrs() { + return array( + array( + array( + 'content-length' => 345, + 'content-type' => 'image+bitmap/jpeg', + 'content-disposition' => 'inline', + 'content-duration' => 35.6363, + 'content-Custom' => 'hello', + 'x-content-custom' => 'hello' + ), + array( + 'content-disposition' => 'inline', + 'content-duration' => 35.6363, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello' + ) + ), + array( + array( + 'content-length' => 345, + 'content-type' => 'image+bitmap/jpeg', + 'content-Disposition' => 'inline; filename=xxx; ' . str_repeat( 'o', 1024 ), + 'content-duration' => 35.6363, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello' + ), + array( + 'content-disposition' => 'inline;filename=xxx', + 'content-duration' => 35.6363, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello' + ) + ), + array( + array( + 'content-length' => 345, + 'content-type' => 'image+bitmap/jpeg', + 'content-disposition' => 'filename='. str_repeat( 'o', 1024 ) . ';inline', + 'content-duration' => 35.6363, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello' + ), + array( + 'content-disposition' => '', + 'content-duration' => 35.6363, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello' + ) + ) + ); + } + + /** + * @dataProvider provider_testGetMetadataHeaders + * @covers SwiftFileBackend::getMetadataHeaders + */ + public function testGetMetadataHeaders( $raw, $sanitized ) { + $hdrs = $this->backend->getMetadataHeaders( $raw ); + + $this->assertEquals( $hdrs, $sanitized, 'getMetadataHeaders() has expected result' ); + } + + public static function provider_testGetMetadataHeaders() { + return array( + array( + array( + 'content-length' => 345, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello', + 'x-object-meta-custom' => 5, + 'x-object-meta-sha1Base36' => 'a3deadfg...', + ), + array( + 'x-object-meta-custom' => 5, + 'x-object-meta-sha1base36' => 'a3deadfg...', + ) + ) + ); + } + + /** + * @dataProvider provider_testGetMetadata + * @covers SwiftFileBackend::getMetadata + */ + public function testGetMetadata( $raw, $sanitized ) { + $hdrs = $this->backend->getMetadata( $raw ); + + $this->assertEquals( $hdrs, $sanitized, 'getMetadata() has expected result' ); + } + + public static function provider_testGetMetadata() { + return array( + array( + array( + 'content-length' => 345, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello', + 'x-object-meta-custom' => 5, + 'x-object-meta-sha1Base36' => 'a3deadfg...', + ), + array( + 'custom' => 5, + 'sha1base36' => 'a3deadfg...', + ) + ) + ); + } +}
\ No newline at end of file diff --git a/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php b/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php new file mode 100644 index 00000000..681e3681 --- /dev/null +++ b/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php @@ -0,0 +1,138 @@ +<?php + +class FileBackendDBRepoWrapperTest extends MediaWikiTestCase { + protected $backendName = 'foo-backend'; + protected $repoName = 'pureTestRepo'; + + /** + * @dataProvider getBackendPathsProvider + * @covers FileBackendDBRepoWrapper::getBackendPaths + */ + public function testGetBackendPaths( + $mocks, + $latest, + $dbReadsExpected, + $dbReturnValue, + $originalPath, + $expectedBackendPath, + $message ) { + list( $dbMock, $backendMock, $wrapperMock ) = $mocks; + + $dbMock->expects( $dbReadsExpected ) + ->method( 'selectField' ) + ->will( $this->returnValue( $dbReturnValue ) ); + + $newPaths = $wrapperMock->getBackendPaths( array( $originalPath ), $latest ); + + $this->assertEquals( + $expectedBackendPath, + $newPaths[0], + $message ); + } + + public function getBackendPathsProvider() { + $prefix = 'mwstore://' . $this->backendName . '/' . $this->repoName; + $mocksForCaching = $this->getMocks(); + + return array( + array( + $mocksForCaching, + false, + $this->once(), + '96246614d75ba1703bdfd5d7660bb57407aaf5d9', + $prefix . '-public/f/o/foobar.jpg', + $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9', + 'Public path translated correctly', + ), + array( + $mocksForCaching, + false, + $this->never(), + '96246614d75ba1703bdfd5d7660bb57407aaf5d9', + $prefix . '-public/f/o/foobar.jpg', + $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9', + 'LRU cache leveraged', + ), + array( + $this->getMocks(), + true, + $this->once(), + '96246614d75ba1703bdfd5d7660bb57407aaf5d9', + $prefix . '-public/f/o/foobar.jpg', + $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9', + 'Latest obtained', + ), + array( + $this->getMocks(), + true, + $this->never(), + '96246614d75ba1703bdfd5d7660bb57407aaf5d9', + $prefix . '-deleted/f/o/foobar.jpg', + $prefix . '-original/f/o/o/foobar', + 'Deleted path translated correctly', + ), + array( + $this->getMocks(), + true, + $this->once(), + null, + $prefix . '-public/b/a/baz.jpg', + $prefix . '-public/b/a/baz.jpg', + 'Path left untouched if no sha1 can be found', + ), + ); + } + + /** + * @covers FileBackendDBRepoWrapper::getFileContentsMulti + */ + public function testGetFileContentsMulti() { + list( $dbMock, $backendMock, $wrapperMock ) = $this->getMocks(); + + $sha1Path = 'mwstore://' . $this->backendName . '/' . $this->repoName + . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9'; + $filenamePath = 'mwstore://' . $this->backendName . '/' . $this->repoName + . '-public/f/o/foobar.jpg'; + + $dbMock->expects( $this->once() ) + ->method( 'selectField' ) + ->will( $this->returnValue( '96246614d75ba1703bdfd5d7660bb57407aaf5d9' ) ); + + $backendMock->expects( $this->once() ) + ->method( 'getFileContentsMulti') + ->will( $this->returnValue( array( $sha1Path => 'foo' ) ) ); + + $result = $wrapperMock->getFileContentsMulti( array( 'srcs' => array( $filenamePath ) ) ); + + $this->assertEquals( + array( $filenamePath => 'foo' ), + $result, + 'File contents paths translated properly' + ); + } + + protected function getMocks() { + $dbMock = $this->getMockBuilder( 'DatabaseMysql' ) + ->disableOriginalConstructor() + ->getMock(); + + $backendMock = $this->getMock( 'FSFileBackend', + array(), + array( array( + 'name' => $this->backendName, + 'wikiId' => wfWikiId() + ) ) ); + + $wrapperMock = $this->getMock( 'FileBackendDBRepoWrapper', + array( 'getDB' ), + array( array( + 'backend' => $backendMock, + 'repoName' => $this->repoName, + 'dbHandleFactory' => null + ) ) ); + + $wrapperMock->expects( $this->any() )->method( 'getDB' )->will( $this->returnValue( $dbMock ) ); + + return array( $dbMock, $backendMock, $wrapperMock ); + } +} diff --git a/tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php b/tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php new file mode 100644 index 00000000..551d3a76 --- /dev/null +++ b/tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php @@ -0,0 +1,114 @@ +<?php + +class MigrateFileRepoLayoutTest extends MediaWikiTestCase { + protected $tmpPrefix; + protected $migratorMock; + protected $tmpFilepath; + protected $text = 'testing'; + + protected function setUp() { + parent::setUp(); + + $filename = 'Foo.png'; + + $this->tmpPrefix = wfTempDir() . '/migratefilelayout-test-' . time() . '-' . mt_rand(); + + $backend = new FSFileBackend( array( + 'name' => 'local-migratefilerepolayouttest', + 'wikiId' => wfWikiID(), + 'containerPaths' => array( + 'migratefilerepolayouttest-original' => "{$this->tmpPrefix}-original", + 'migratefilerepolayouttest-public' => "{$this->tmpPrefix}-public", + 'migratefilerepolayouttest-thumb' => "{$this->tmpPrefix}-thumb", + 'migratefilerepolayouttest-temp' => "{$this->tmpPrefix}-temp", + 'migratefilerepolayouttest-deleted' => "{$this->tmpPrefix}-deleted", + ) + ) ); + + $dbMock = $this->getMockBuilder( 'DatabaseMysql' ) + ->disableOriginalConstructor() + ->getMock(); + + $imageRow = new stdClass; + $imageRow->img_name = $filename; + $imageRow->img_sha1 = sha1( $this->text ); + + $dbMock->expects( $this->any() ) + ->method( 'select' ) + ->will( $this->onConsecutiveCalls( + new FakeResultWrapper( array( $imageRow ) ), // image + new FakeResultWrapper( array() ), // image + new FakeResultWrapper( array() ) // filearchive + ) ); + + $repoMock = $this->getMock( 'LocalRepo', + array( 'getMasterDB' ), + array( array( + 'name' => 'migratefilerepolayouttest', + 'backend' => $backend + ) ) ); + + $repoMock->expects( $this->any() )->method( 'getMasterDB' )->will( $this->returnValue( $dbMock ) ); + + $this->migratorMock = $this->getMock( 'MigrateFileRepoLayout', array( 'getRepo' ) ); + $this->migratorMock->expects( $this->any() )->method( 'getRepo' )->will( $this->returnValue( $repoMock ) ); + + $this->tmpFilepath = TempFSFile::factory( 'migratefilelayout-test-', 'png' )->getPath(); + + file_put_contents( $this->tmpFilepath, $this->text ); + + $hashPath = $repoMock->getHashPath( $filename ); + + $status = $repoMock->store( $this->tmpFilepath, 'public', $hashPath . $filename, FileRepo::OVERWRITE ); + } + + protected function deleteFilesRecursively( $directory ) { + foreach ( glob( $directory . '/*' ) as $file ) { + if ( is_dir( $file ) ) { + $this->deleteFilesRecursively( $file ); + } else { + unlink( $file ); + } + } + + rmdir( $directory ); + } + + protected function tearDown() { + foreach ( glob( $this->tmpPrefix . '*' ) as $directory ) { + $this->deleteFilesRecursively( $directory ); + } + + unlink( $this->tmpFilepath ); + + parent::tearDown(); + } + + public function testMigration() { + $this->migratorMock->loadParamsAndArgs( null, array( 'oldlayout' => 'name', 'newlayout' => 'sha1' ) ); + + ob_start(); + + $this->migratorMock->execute(); + + ob_end_clean(); + + $sha1 = sha1( $this->text ); + + $expectedOriginalFilepath = $this->tmpPrefix + . '-original/' + . substr( $sha1, 0, 1 ) + . '/' + . substr( $sha1, 1, 1 ) + . '/' + . substr( $sha1, 2, 1 ) + . '/' + . $sha1 ; + + $this->assertEquals( file_get_contents( $expectedOriginalFilepath ), $this->text, 'New sha1 file should be exist and have the right contents' ); + + $expectedPublicFilepath = $this->tmpPrefix . '-public/f/f8/Foo.png'; + + $this->assertEquals( file_get_contents( $expectedPublicFilepath ), $this->text, 'Existing name file should still and have the right contents' ); + } +} diff --git a/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php b/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php index 2c7f50c9..3b5347cd 100644 --- a/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php +++ b/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php @@ -6,7 +6,7 @@ */ class HtmlAutoCompleteSelectFieldTest extends MediaWikiTestCase { - var $options = array( + public $options = array( 'Bulgaria' => 'BGR', 'Burkina Faso' => 'BFA', 'Burundi' => 'BDI', diff --git a/tests/phpunit/includes/json/FormatJsonTest.php b/tests/phpunit/includes/json/FormatJsonTest.php index f0ac6acc..8bca3331 100644 --- a/tests/phpunit/includes/json/FormatJsonTest.php +++ b/tests/phpunit/includes/json/FormatJsonTest.php @@ -159,12 +159,12 @@ class FormatJsonTest extends MediaWikiTestCase { $this->assertJson( $json ); $st = FormatJson::parse( $json ); - $this->assertType( 'Status', $st ); + $this->assertInstanceOf( 'Status', $st ); $this->assertTrue( $st->isGood() ); $this->assertEquals( $expected, $st->getValue() ); $st = FormatJson::parse( $json, FormatJson::FORCE_ASSOC ); - $this->assertType( 'Status', $st ); + $this->assertInstanceOf( 'Status', $st ); $this->assertTrue( $st->isGood() ); $this->assertEquals( $value, $st->getValue() ); } @@ -230,7 +230,7 @@ class FormatJsonTest extends MediaWikiTestCase { } $st = FormatJson::parse( $value, FormatJson::TRY_FIXING ); - $this->assertType( 'Status', $st ); + $this->assertInstanceOf( 'Status', $st ); if ( $expected === false ) { $this->assertFalse( $st->isOK(), 'Expected isOK() == false' ); } else { @@ -256,7 +256,7 @@ class FormatJsonTest extends MediaWikiTestCase { */ public function testParseErrors( $value ) { $st = FormatJson::parse( $value ); - $this->assertType( 'Status', $st ); + $this->assertInstanceOf( 'Status', $st ); $this->assertFalse( $st->isOK() ); } @@ -313,7 +313,7 @@ class FormatJsonTest extends MediaWikiTestCase { */ public function testParseStripComments( $json, $expect ) { $st = FormatJson::parse( $json, FormatJson::STRIP_COMMENTS ); - $this->assertType( 'Status', $st ); + $this->assertInstanceOf( 'Status', $st ); $this->assertTrue( $st->isGood() ); $this->assertEquals( $expect, $st->getValue() ); } diff --git a/tests/phpunit/includes/libs/ArrayUtilsTest.php b/tests/phpunit/includes/libs/ArrayUtilsTest.php index b5ea7b72..32b150c7 100644 --- a/tests/phpunit/includes/libs/ArrayUtilsTest.php +++ b/tests/phpunit/includes/libs/ArrayUtilsTest.php @@ -23,11 +23,11 @@ class ArrayUtilsTest extends PHPUnit_Framework_TestCase { } function provideFindLowerBound() { - $self = $this; - $indexValueCallback = function ( $size ) use ( $self ) { - return function ( $val ) use ( $self, $size ) { - $self->assertTrue( $val >= 0 ); - $self->assertTrue( $val < $size ); + $that = $this; + $indexValueCallback = function ( $size ) use ( $that ) { + return function ( $val ) use ( $that, $size ) { + $that->assertTrue( $val >= 0 ); + $that->assertTrue( $val < $size ); return $val; }; }; @@ -212,7 +212,7 @@ class ArrayUtilsTest extends PHPUnit_Framework_TestCase { array(), array( 1 => 1 ), array( 1 ), - array( 1 => 1), + array( 1 => 1 ), ), array( array(), diff --git a/tests/phpunit/includes/libs/CSSMinTest.php b/tests/phpunit/includes/libs/CSSMinTest.php index 6142f967..7841f30f 100644 --- a/tests/phpunit/includes/libs/CSSMinTest.php +++ b/tests/phpunit/includes/libs/CSSMinTest.php @@ -102,12 +102,12 @@ class CSSMinTest extends MediaWikiTestCase { array( 'Without trailing slash', array( 'foo { prop: url(../bar.png); }', false, 'http://example.org/quux', false ), - 'foo { prop: url(http://example.org/quux/../bar.png); }', + 'foo { prop: url(http://example.org/bar.png); }', ), array( 'With trailing slash on remote (bug 27052)', array( 'foo { prop: url(../bar.png); }', false, 'http://example.org/quux/', false ), - 'foo { prop: url(http://example.org/quux/../bar.png); }', + 'foo { prop: url(http://example.org/bar.png); }', ), array( 'Guard against stripping double slashes from query', @@ -133,12 +133,7 @@ class CSSMinTest extends MediaWikiTestCase { $remotePath = 'http://localhost/w/'; $realOutput = CSSMin::remap( $input, $localPath, $remotePath ); - - $this->assertEquals( - $expectedOutput, - preg_replace( '/\d+-\d+-\d+T\d+:\d+:\d+Z/', 'timestamp', $realOutput ), - "CSSMin::remap: $message" - ); + $this->assertEquals( $expectedOutput, $realOutput, "CSSMin::remap: $message" ); } public static function provideIsRemoteUrl() { @@ -197,7 +192,7 @@ class CSSMinTest extends MediaWikiTestCase { array( 'Regular file', 'foo { background: url(red.gif); }', - 'foo { background: url(http://localhost/w/red.gif?timestamp); }', + 'foo { background: url(http://localhost/w/red.gif?34ac6); }', ), array( 'Regular file (missing)', @@ -242,12 +237,12 @@ class CSSMinTest extends MediaWikiTestCase { array( 'Embedded file', 'foo { /* @embed */ background: url(red.gif); }', - "foo { background: url($red); background: url(http://localhost/w/red.gif?timestamp)!ie; }", + "foo { background: url($red); background: url(http://localhost/w/red.gif?34ac6)!ie; }", ), array( 'Embedded file, other comments before the rule', "foo { /* Bar. */ /* @embed */ background: url(red.gif); }", - "foo { /* Bar. */ background: url($red); /* Bar. */ background: url(http://localhost/w/red.gif?timestamp)!ie; }", + "foo { /* Bar. */ background: url($red); /* Bar. */ background: url(http://localhost/w/red.gif?34ac6)!ie; }", ), array( 'Can not re-embed data: URIs', @@ -268,12 +263,12 @@ class CSSMinTest extends MediaWikiTestCase { 'Embedded file (inline @embed)', 'foo { background: /* @embed */ url(red.gif); }', "foo { background: url($red); " - . "background: url(http://localhost/w/red.gif?timestamp)!ie; }", + . "background: url(http://localhost/w/red.gif?34ac6)!ie; }", ), array( 'Can not embed large files', 'foo { /* @embed */ background: url(large.png); }', - "foo { background: url(http://localhost/w/large.png?timestamp); }", + "foo { background: url(http://localhost/w/large.png?e3d1f); }", ), array( 'SVG files are embedded without base64 encoding and unnecessary IE 6 and 7 fallback', @@ -283,55 +278,55 @@ class CSSMinTest extends MediaWikiTestCase { array( 'Two regular files in one rule', 'foo { background: url(red.gif), url(green.gif); }', - 'foo { background: url(http://localhost/w/red.gif?timestamp), ' - . 'url(http://localhost/w/green.gif?timestamp); }', + 'foo { background: url(http://localhost/w/red.gif?34ac6), ' + . 'url(http://localhost/w/green.gif?13651); }', ), array( 'Two embedded files in one rule', 'foo { /* @embed */ background: url(red.gif), url(green.gif); }', "foo { background: url($red), url($green); " - . "background: url(http://localhost/w/red.gif?timestamp), " - . "url(http://localhost/w/green.gif?timestamp)!ie; }", + . "background: url(http://localhost/w/red.gif?34ac6), " + . "url(http://localhost/w/green.gif?13651)!ie; }", ), array( 'Two embedded files in one rule (inline @embed)', 'foo { background: /* @embed */ url(red.gif), /* @embed */ url(green.gif); }', "foo { background: url($red), url($green); " - . "background: url(http://localhost/w/red.gif?timestamp), " - . "url(http://localhost/w/green.gif?timestamp)!ie; }", + . "background: url(http://localhost/w/red.gif?34ac6), " + . "url(http://localhost/w/green.gif?13651)!ie; }", ), array( 'Two embedded files in one rule (inline @embed), one too large', 'foo { background: /* @embed */ url(red.gif), /* @embed */ url(large.png); }', - "foo { background: url($red), url(http://localhost/w/large.png?timestamp); " - . "background: url(http://localhost/w/red.gif?timestamp), " - . "url(http://localhost/w/large.png?timestamp)!ie; }", + "foo { background: url($red), url(http://localhost/w/large.png?e3d1f); " + . "background: url(http://localhost/w/red.gif?34ac6), " + . "url(http://localhost/w/large.png?e3d1f)!ie; }", ), array( 'Practical example with some noise', 'foo { /* @embed */ background: #f9f9f9 url(red.gif) 0 0 no-repeat; }', "foo { background: #f9f9f9 url($red) 0 0 no-repeat; " - . "background: #f9f9f9 url(http://localhost/w/red.gif?timestamp) 0 0 no-repeat!ie; }", + . "background: #f9f9f9 url(http://localhost/w/red.gif?34ac6) 0 0 no-repeat!ie; }", ), array( 'Does not mess with other properties', 'foo { color: red; background: url(red.gif); font-size: small; }', - 'foo { color: red; background: url(http://localhost/w/red.gif?timestamp); font-size: small; }', + 'foo { color: red; background: url(http://localhost/w/red.gif?34ac6); font-size: small; }', ), array( 'Spacing and miscellanea not changed (1)', 'foo { background: url(red.gif); }', - 'foo { background: url(http://localhost/w/red.gif?timestamp); }', + 'foo { background: url(http://localhost/w/red.gif?34ac6); }', ), array( 'Spacing and miscellanea not changed (2)', 'foo {background:url(red.gif)}', - 'foo {background:url(http://localhost/w/red.gif?timestamp)}', + 'foo {background:url(http://localhost/w/red.gif?34ac6)}', ), array( 'Spaces within url() parentheses are ignored', 'foo { background: url( red.gif ); }', - 'foo { background: url(http://localhost/w/red.gif?timestamp); }', + 'foo { background: url(http://localhost/w/red.gif?34ac6); }', ), array( '@import rule to local file (should we remap this?)', @@ -351,22 +346,22 @@ class CSSMinTest extends MediaWikiTestCase { array( 'Simple case with comments after url', 'foo { prop: url(red.gif)/* some {funny;} comment */ ; }', - 'foo { prop: url(http://localhost/w/red.gif?timestamp)/* some {funny;} comment */ ; }', + 'foo { prop: url(http://localhost/w/red.gif?34ac6)/* some {funny;} comment */ ; }', ), array( 'Embedded file with comment before url', 'foo { /* @embed */ background: /* some {funny;} comment */ url(red.gif); }', - "foo { background: /* some {funny;} comment */ url($red); background: /* some {funny;} comment */ url(http://localhost/w/red.gif?timestamp)!ie; }", + "foo { background: /* some {funny;} comment */ url($red); background: /* some {funny;} comment */ url(http://localhost/w/red.gif?34ac6)!ie; }", ), array( 'Embedded file with comments inside and outside the rule', 'foo { /* @embed */ background: url(red.gif) /* some {foo;} comment */; /* some {bar;} comment */ }', - "foo { background: url($red) /* some {foo;} comment */; background: url(http://localhost/w/red.gif?timestamp) /* some {foo;} comment */!ie; /* some {bar;} comment */ }", + "foo { background: url($red) /* some {foo;} comment */; background: url(http://localhost/w/red.gif?34ac6) /* some {foo;} comment */!ie; /* some {bar;} comment */ }", ), array( 'Embedded file with comment outside the rule', 'foo { /* @embed */ background: url(red.gif); /* some {funny;} comment */ }', - "foo { background: url($red); background: url(http://localhost/w/red.gif?timestamp)!ie; /* some {funny;} comment */ }", + "foo { background: url($red); background: url(http://localhost/w/red.gif?34ac6)!ie; /* some {funny;} comment */ }", ), array( 'Rule with two urls, each with comments', diff --git a/tests/phpunit/includes/libs/IEUrlExtensionTest.php b/tests/phpunit/includes/libs/IEUrlExtensionTest.php index e96953ee..57668e50 100644 --- a/tests/phpunit/includes/libs/IEUrlExtensionTest.php +++ b/tests/phpunit/includes/libs/IEUrlExtensionTest.php @@ -170,4 +170,37 @@ class IEUrlExtensionTest extends PHPUnit_Framework_TestCase { 'Two dots' ); } + + /** + * @covers IEUrlExtension::findIE6Extension + */ + public function testScriptQuery() { + $this->assertEquals( + 'php', + IEUrlExtension::findIE6Extension( 'example.php?foo=a&bar=b' ), + 'Script with query' + ); + } + + /** + * @covers IEUrlExtension::findIE6Extension + */ + public function testEscapedScriptQuery() { + $this->assertEquals( + '', + IEUrlExtension::findIE6Extension( 'example%2Ephp?foo=a&bar=b' ), + 'Script with urlencoded dot and query' + ); + } + + /** + * @covers IEUrlExtension::findIE6Extension + */ + public function testEscapedScriptQueryDot() { + $this->assertEquals( + 'y', + IEUrlExtension::findIE6Extension( 'example%2Ephp?foo=a.x&bar=b.y' ), + 'Script with urlencoded dot and query with dot' + ); + } } diff --git a/tests/phpunit/includes/libs/IPSetTest.php b/tests/phpunit/includes/libs/IPSetTest.php deleted file mode 100644 index 5bbacef4..00000000 --- a/tests/phpunit/includes/libs/IPSetTest.php +++ /dev/null @@ -1,252 +0,0 @@ -<?php - -/** - * @group IPSet - */ -class IPSetTest extends PHPUnit_Framework_TestCase { - /** - * Provides test cases for IPSetTest::testIPSet - * - * Returns an array of test cases. Each case is an array of (description, - * config, tests). Description is just text output for failure messages, - * config is an array constructor argument for IPSet, and the tests are - * an array of IP => expected (boolean) result against the config dataset. - */ - public static function provideIPSets() { - return array( - array( - 'old_list_subset', - array( - '208.80.152.162', - '10.64.0.123', - '10.64.0.124', - '10.64.0.125', - '10.64.0.126', - '10.64.0.127', - '10.64.0.128', - '10.64.0.129', - '10.64.32.104', - '10.64.32.105', - '10.64.32.106', - '10.64.32.107', - '91.198.174.45', - '91.198.174.46', - '91.198.174.47', - '91.198.174.57', - '2620:0:862:1:A6BA:DBFF:FE30:CFB3', - '91.198.174.58', - '2620:0:862:1:A6BA:DBFF:FE38:FFDA', - '208.80.152.16', - '208.80.152.17', - '208.80.152.18', - '208.80.152.19', - '91.198.174.102', - '91.198.174.103', - '91.198.174.104', - '91.198.174.105', - '91.198.174.106', - '91.198.174.107', - '91.198.174.81', - '2620:0:862:1:26B6:FDFF:FEF5:B2D4', - '91.198.174.82', - '2620:0:862:1:26B6:FDFF:FEF5:ABB4', - '10.20.0.113', - '2620:0:862:102:26B6:FDFF:FEF5:AD9C', - '10.20.0.114', - '2620:0:862:102:26B6:FDFF:FEF5:7C38', - ), - array( - '0.0.0.0' => false, - '255.255.255.255' => false, - '10.64.0.122' => false, - '10.64.0.123' => true, - '10.64.0.124' => true, - '10.64.0.129' => true, - '10.64.0.130' => false, - '91.198.174.81' => true, - '91.198.174.80' => false, - '0::0' => false, - 'ffff:ffff:ffff:ffff:FFFF:FFFF:FFFF:FFFF' => false, - '2001:db8::1234' => false, - '2620:0:862:1:26b6:fdff:fef5:abb3' => false, - '2620:0:862:1:26b6:fdff:fef5:abb4' => true, - '2620:0:862:1:26b6:fdff:fef5:abb5' => false, - ), - ), - array( - 'new_cidr_set', - array( - '208.80.154.0/26', - '2620:0:861:1::/64', - '208.80.154.128/26', - '2620:0:861:2::/64', - '208.80.154.64/26', - '2620:0:861:3::/64', - '208.80.155.96/27', - '2620:0:861:4::/64', - '10.64.0.0/22', - '2620:0:861:101::/64', - '10.64.16.0/22', - '2620:0:861:102::/64', - '10.64.32.0/22', - '2620:0:861:103::/64', - '10.64.48.0/22', - '2620:0:861:107::/64', - '91.198.174.0/25', - '2620:0:862:1::/64', - '10.20.0.0/24', - '2620:0:862:102::/64', - '10.128.0.0/24', - '2620:0:863:101::/64', - '10.2.4.26', - ), - array( - '0.0.0.0' => false, - '255.255.255.255' => false, - '10.2.4.25' => false, - '10.2.4.26' => true, - '10.2.4.27' => false, - '10.20.0.255' => true, - '10.128.0.0' => true, - '10.64.17.55' => true, - '10.64.20.0' => false, - '10.64.27.207' => false, - '10.64.31.255' => false, - '0::0' => false, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' => false, - '2001:DB8::1' => false, - '2620:0:861:106::45' => false, - '2620:0:862:103::' => false, - '2620:0:862:102:10:20:0:113' => true, - ), - ), - array( - 'empty_set', - array(), - array( - '0.0.0.0' => false, - '255.255.255.255' => false, - '10.2.4.25' => false, - '10.2.4.26' => false, - '10.2.4.27' => false, - '10.20.0.255' => false, - '10.128.0.0' => false, - '10.64.17.55' => false, - '10.64.20.0' => false, - '10.64.27.207' => false, - '10.64.31.255' => false, - '0::0' => false, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' => false, - '2001:DB8::1' => false, - '2620:0:861:106::45' => false, - '2620:0:862:103::' => false, - '2620:0:862:102:10:20:0:113' => false, - ), - ), - array( - 'edge_cases', - array( - '0.0.0.0', - '255.255.255.255', - '::', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', - '10.10.10.10/25', // host bits intentional - ), - array( - '0.0.0.0' => true, - '255.255.255.255' => true, - '10.2.4.25' => false, - '10.2.4.26' => false, - '10.2.4.27' => false, - '10.20.0.255' => false, - '10.128.0.0' => false, - '10.64.17.55' => false, - '10.64.20.0' => false, - '10.64.27.207' => false, - '10.64.31.255' => false, - '0::0' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' => true, - '2001:DB8::1' => false, - '2620:0:861:106::45' => false, - '2620:0:862:103::' => false, - '2620:0:862:102:10:20:0:113' => false, - '10.10.9.255' => false, - '10.10.10.0' => true, - '10.10.10.1' => true, - '10.10.10.10' => true, - '10.10.10.126' => true, - '10.10.10.127' => true, - '10.10.10.128' => false, - '10.10.10.177' => false, - '10.10.10.255' => false, - '10.10.11.0' => false, - ), - ), - array( - 'exercise_optimizer', - array( - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fffe:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fffd:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fffc:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fffb:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fffa:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff9:8000/113', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff9:0/113', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff8:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff7:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff6:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff5:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff4:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff3:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff2:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff1:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff0:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffef:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffee:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffec:0/111', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffeb:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffea:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe9:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe8:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe7:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe6:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe5:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe4:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe3:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe2:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe1:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe0:0/110', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffc0:0/107', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffa0:0/107', - ), - array( - '0.0.0.0' => false, - '255.255.255.255' => false, - '::' => false, - 'ffff:ffff:ffff:ffff:ffff:ffff:ff9f:ffff' => false, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffa0:0' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffc0:1234' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffed:ffff' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:fff4:4444' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:fff9:8080' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' => true, - ), - ), - ); - } - - /** - * Validates IPSet loading and matching code - * - * @covers IPSet - * @dataProvider provideIPSets - */ - public function testIPSet( $desc, array $cfg, array $tests ) { - $ipset = new IPSet( $cfg ); - foreach ( $tests as $ip => $expected ) { - $result = $ipset->match( $ip ); - $this->assertEquals( $expected, $result, "Incorrect match() result for $ip in dataset $desc" ); - } - } -} diff --git a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php index 149a28c1..d23534ed 100644 --- a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php +++ b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php @@ -140,6 +140,13 @@ class JavaScriptMinifierTest extends PHPUnit_Framework_TestCase { array( "5..toString();", "5..toString();" ), array( "5...toString();", false ), array( "5.\n.toString();", '5..toString();' ), + + // Boolean minification (!0 / !1) + array( "var a = { b: true };", "var a={b:!0};" ), + array( "var a = { true: 12 };", "var a={true:12};", false ), + array( "a.true = 12;", "a.true=12;", false ), + array( "a.foo = true;", "a.foo=!0;" ), + array( "a.foo = false;", "a.foo=!1;" ), ); } @@ -147,15 +154,17 @@ class JavaScriptMinifierTest extends PHPUnit_Framework_TestCase { * @dataProvider provideCases * @covers JavaScriptMinifier::minify */ - public function testJavaScriptMinifierOutput( $code, $expectedOutput ) { + public function testJavaScriptMinifierOutput( $code, $expectedOutput, $expectedValid = true ) { $minified = JavaScriptMinifier::minify( $code ); // JSMin+'s parser will throw an exception if output is not valid JS. // suppression of warnings needed for stupid crap - wfSuppressWarnings(); - $parser = new JSParser(); - wfRestoreWarnings(); - $parser->parse( $minified, 'minify-test.js', 1 ); + if ( $expectedValid ) { + MediaWiki\suppressWarnings(); + $parser = new JSParser(); + MediaWiki\restoreWarnings(); + $parser->parse( $minified, 'minify-test.js', 1 ); + } $this->assertEquals( $expectedOutput, diff --git a/tests/phpunit/includes/libs/ObjectFactoryTest.php b/tests/phpunit/includes/libs/ObjectFactoryTest.php index 92207325..aea037e0 100644 --- a/tests/phpunit/includes/libs/ObjectFactoryTest.php +++ b/tests/phpunit/includes/libs/ObjectFactoryTest.php @@ -26,11 +26,20 @@ class ObjectFactoryTest extends PHPUnit_Framework_TestCase { public function testClosureExpansionDisabled() { $obj = ObjectFactory::getObjectFromSpec( array( 'class' => 'ObjectFactoryTest_Fixture', - 'args' => array( function (){ return 'unwrapped'; }, ), + 'args' => array( function() { + return 'unwrapped'; + }, ), + 'calls' => array( + 'setter' => array( function() { + return 'unwrapped'; + }, ), + ), 'closure_expansion' => false, ) ); $this->assertInstanceOf( 'Closure', $obj->args[0] ); $this->assertSame( 'unwrapped', $obj->args[0]() ); + $this->assertInstanceOf( 'Closure', $obj->setterArgs[0] ); + $this->assertSame( 'unwrapped', $obj->setterArgs[0]() ); } /** @@ -39,22 +48,46 @@ class ObjectFactoryTest extends PHPUnit_Framework_TestCase { public function testClosureExpansionEnabled() { $obj = ObjectFactory::getObjectFromSpec( array( 'class' => 'ObjectFactoryTest_Fixture', - 'args' => array( function (){ return 'unwrapped'; }, ), + 'args' => array( function() { + return 'unwrapped'; + }, ), + 'calls' => array( + 'setter' => array( function() { + return 'unwrapped'; + }, ), + ), 'closure_expansion' => true, ) ); $this->assertInternalType( 'string', $obj->args[0] ); $this->assertSame( 'unwrapped', $obj->args[0] ); + $this->assertInternalType( 'string', $obj->setterArgs[0] ); + $this->assertSame( 'unwrapped', $obj->setterArgs[0] ); $obj = ObjectFactory::getObjectFromSpec( array( 'class' => 'ObjectFactoryTest_Fixture', - 'args' => array( function (){ return 'unwrapped'; }, ), + 'args' => array( function() { + return 'unwrapped'; + }, ), + 'calls' => array( + 'setter' => array( function() { + return 'unwrapped'; + }, ), + ), ) ); $this->assertInternalType( 'string', $obj->args[0] ); $this->assertSame( 'unwrapped', $obj->args[0] ); + $this->assertInternalType( 'string', $obj->setterArgs[0] ); + $this->assertSame( 'unwrapped', $obj->setterArgs[0] ); } } class ObjectFactoryTest_Fixture { public $args; - public function __construct( /*...*/ ) { $this->args = func_get_args(); } + public $setterArgs; + public function __construct( /*...*/ ) { + $this->args = func_get_args(); + } + public function setter( /*...*/ ) { + $this->setterArgs = func_get_args(); + } } diff --git a/tests/phpunit/includes/libs/ProcessCacheLRUTest.php b/tests/phpunit/includes/libs/ProcessCacheLRUTest.php index 43001979..cec662a9 100644 --- a/tests/phpunit/includes/libs/ProcessCacheLRUTest.php +++ b/tests/phpunit/includes/libs/ProcessCacheLRUTest.php @@ -70,7 +70,7 @@ class ProcessCacheLRUTest extends PHPUnit_Framework_TestCase { /** * @dataProvider provideInvalidConstructorArg - * @expectedException UnexpectedValueException + * @expectedException Wikimedia\Assert\ParameterAssertionException * @covers ProcessCacheLRU::__construct */ public function testConstructorGivenInvalidValue( $maxSize ) { diff --git a/tests/phpunit/includes/libs/SamplingStatsdClientTest.php b/tests/phpunit/includes/libs/SamplingStatsdClientTest.php new file mode 100644 index 00000000..be6732d5 --- /dev/null +++ b/tests/phpunit/includes/libs/SamplingStatsdClientTest.php @@ -0,0 +1,43 @@ +<?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 ), + ); + } +} diff --git a/tests/phpunit/includes/libs/XhprofTest.php b/tests/phpunit/includes/libs/XhprofTest.php index 2440fc08..77b188cf 100644 --- a/tests/phpunit/includes/libs/XhprofTest.php +++ b/tests/phpunit/includes/libs/XhprofTest.php @@ -255,43 +255,43 @@ class XhprofTest extends PHPUnit_Framework_TestCase { */ protected function getXhprofFixture( array $opts = array() ) { $xhprof = new Xhprof( $opts ); - $xhprof->loadRawData( array ( - 'foo==>bar' => array ( + $xhprof->loadRawData( array( + 'foo==>bar' => array( 'ct' => 2, 'wt' => 57, 'cpu' => 92, 'mu' => 1896, 'pmu' => 0, ), - 'foo==>strlen' => array ( + 'foo==>strlen' => array( 'ct' => 2, 'wt' => 21, 'cpu' => 141, 'mu' => 752, 'pmu' => 0, ), - 'bar==>bar@1' => array ( + 'bar==>bar@1' => array( 'ct' => 1, 'wt' => 18, 'cpu' => 19, 'mu' => 752, 'pmu' => 0, ), - 'main()==>foo' => array ( + 'main()==>foo' => array( 'ct' => 1, 'wt' => 304, 'cpu' => 307, 'mu' => 4008, 'pmu' => 0, ), - 'main()==>xhprof_disable' => array ( + 'main()==>xhprof_disable' => array( 'ct' => 1, 'wt' => 8, 'cpu' => 10, 'mu' => 768, 'pmu' => 392, ), - 'main()' => array ( + 'main()' => array( 'ct' => 1, 'wt' => 353, 'cpu' => 351, @@ -311,7 +311,7 @@ class XhprofTest extends PHPUnit_Framework_TestCase { */ protected function assertArrayStructure( $struct, $actual, $label = null ) { $this->assertInternalType( 'array', $actual, $label ); - $this->assertCount( count($struct), $actual, $label ); + $this->assertCount( count( $struct ), $actual, $label ); foreach ( $struct as $key => $type ) { $this->assertArrayHasKey( $key, $actual ); $this->assertInternalType( $type, $actual[$key] ); diff --git a/tests/phpunit/includes/libs/composer/ComposerLockTest.php b/tests/phpunit/includes/libs/composer/ComposerLockTest.php index b5fd5f6e..cac3b101 100644 --- a/tests/phpunit/includes/libs/composer/ComposerLockTest.php +++ b/tests/phpunit/includes/libs/composer/ComposerLockTest.php @@ -27,34 +27,95 @@ class ComposerLockTest extends MediaWikiTestCase { 'wikimedia/cdb' => array( 'version' => '1.0.1', 'type' => 'library', + 'licenses' => array( 'GPL-2.0' ), + 'authors' => array( + array( + 'name' => 'Tim Starling', + 'email' => 'tstarling@wikimedia.org', + ), + array( + 'name' => 'Chad Horohoe', + 'email' => 'chad@wikimedia.org', + ), + ), + 'description' => 'Constant Database (CDB) wrapper library for PHP. Provides pure-PHP fallback when dba_* functions are absent.', ), 'cssjanus/cssjanus' => array( 'version' => '1.1.1', 'type' => 'library', + 'licenses' => array( 'Apache-2.0' ), + 'authors' => array(), + 'description' => 'Convert CSS stylesheets between left-to-right and right-to-left.', ), 'leafo/lessphp' => array( 'version' => '0.5.0', 'type' => 'library', + 'licenses' => array( 'MIT', 'GPL-3.0' ), + 'authors' => array( + array( + 'name' => 'Leaf Corcoran', + 'email' => 'leafot@gmail.com', + 'homepage' => 'http://leafo.net', + ), + ), + 'description' => 'lessphp is a compiler for LESS written in PHP.', ), 'psr/log' => array( 'version' => '1.0.0', 'type' => 'library', + 'licenses' => array( 'MIT' ), + 'authors' => array( + array( + 'name' => 'PHP-FIG', + 'homepage' => 'http://www.php-fig.org/', + ), + ), + 'description' => 'Common interface for logging libraries', ), 'oojs/oojs-ui' => array( 'version' => '0.6.0', 'type' => 'library', + 'licenses' => array( 'MIT' ), + 'authors' => array(), + 'description' => '', ), 'composer/installers' => array( 'version' => '1.0.19', 'type' => 'composer-installer', + 'licenses' => array( 'MIT' ), + 'authors' => array( + array( + 'name' => 'Kyle Robinson Young', + 'email' => 'kyle@dontkry.com', + 'homepage' => 'https://github.com/shama', + ), + ), + 'description' => 'A multi-framework Composer library installer', ), 'mediawiki/translate' => array( 'version' => '2014.12', 'type' => 'mediawiki-extension', + 'licenses' => array( 'GPL-2.0+' ), + 'authors' => array( + array( + 'name' => 'Niklas Laxström', + 'email' => 'niklas.laxstrom@gmail.com', + 'role' => 'Lead nitpicker', + ), + array( + 'name' => 'Siebrand Mazeland', + 'email' => 's.mazeland@xs4all.nl', + 'role' => 'Developer', + ), + ), + 'description' => 'The only standard solution to translate any kind of text with an avant-garde web interface within MediaWiki, including your documentation and software', ), 'mediawiki/universal-language-selector' => array( 'version' => '2014.12', 'type' => 'mediawiki-extension', + 'licenses' => array( 'GPL-2.0+', 'MIT' ), + 'authors' => array(), + 'description' => 'The primary aim is to allow users to select a language and configure its support in an easy way. Main features are language selection, input methods and web fonts.', ), ), $lock->getInstalledDependencies(), false, true ); } diff --git a/tests/phpunit/includes/logging/BlockLogFormatterTest.php b/tests/phpunit/includes/logging/BlockLogFormatterTest.php new file mode 100644 index 00000000..c7dc641d --- /dev/null +++ b/tests/phpunit/includes/logging/BlockLogFormatterTest.php @@ -0,0 +1,372 @@ +<?php + +class BlockLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideBlockLogDatabaseRows() { + return array( + // Current log format + array( + array( + 'type' => 'block', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + '5::duration' => 'infinite', + '6::flags' => 'anononly', + ), + ), + array( + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // Old legacy log + array( + array( + 'type' => 'block', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + 'anononly', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // Old legacy log without flag + array( + array( + 'type' => 'block', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array(), + ), + ), + ), + + // Very old legacy log without duration + array( + array( + 'type' => 'block', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array(), + ), + ), + ), + ); + } + + /** + * @dataProvider provideBlockLogDatabaseRows + */ + public function testBlockLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideReblockLogDatabaseRows() { + return array( + // Current log format + array( + array( + 'type' => 'block', + 'action' => 'reblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + '5::duration' => 'infinite', + '6::flags' => 'anononly', + ), + ), + array( + 'text' => 'Sysop changed block settings for Logtestuser with an expiry time of' + . ' indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // Old log + array( + array( + 'type' => 'block', + 'action' => 'reblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + 'anononly', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop changed block settings for Logtestuser with an expiry time of' + . ' indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // Older log without flag + array( + array( + 'type' => 'block', + 'action' => 'reblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + ) + ), + array( + 'legacy' => true, + 'text' => 'Sysop changed block settings for Logtestuser with an expiry time of indefinite', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array(), + ), + ), + ), + ); + } + + /** + * @dataProvider provideReblockLogDatabaseRows + */ + public function testReblockLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideUnblockLogDatabaseRows() { + return array( + // Current log format + array( + array( + 'type' => 'block', + 'action' => 'unblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array(), + ), + array( + 'text' => 'Sysop unblocked Logtestuser', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideUnblockLogDatabaseRows + */ + public function testUnblockLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideSuppressBlockLogDatabaseRows() { + return array( + // Current log format + array( + array( + 'type' => 'suppress', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + '5::duration' => 'infinite', + '6::flags' => 'anononly', + ), + ), + array( + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // legacy log + array( + array( + 'type' => 'suppress', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + 'anononly', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideSuppressBlockLogDatabaseRows + */ + public function testSuppressBlockLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideSuppressReblockLogDatabaseRows() { + return array( + // Current log format + array( + array( + 'type' => 'suppress', + 'action' => 'reblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + '5::duration' => 'infinite', + '6::flags' => 'anononly', + ), + ), + array( + 'text' => 'Sysop changed block settings for Logtestuser with an expiry time of' + . ' indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'suppress', + 'action' => 'reblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + 'anononly', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop changed block settings for Logtestuser with an expiry time of' + . ' indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideSuppressReblockLogDatabaseRows + */ + public function testSuppressReblockLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/DeleteLogFormatterTest.php b/tests/phpunit/includes/logging/DeleteLogFormatterTest.php new file mode 100644 index 00000000..28e7efaf --- /dev/null +++ b/tests/phpunit/includes/logging/DeleteLogFormatterTest.php @@ -0,0 +1,527 @@ +<?php + +class DeleteLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideDeleteLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'delete', + 'action' => 'delete', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'text' => 'User deleted page Page', + 'api' => array(), + ), + ), + + // Legacy format + array( + array( + 'type' => 'delete', + 'action' => 'delete', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'User deleted page Page', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideDeleteLogDatabaseRows + */ + public function testDeleteLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideRestoreLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'delete', + 'action' => 'restore', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'text' => 'User restored page Page', + 'api' => array(), + ), + ), + + // Legacy format + array( + array( + 'type' => 'delete', + 'action' => 'restore', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'User restored page Page', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideRestoreLogDatabaseRows + */ + public function testRestoreLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideRevisionLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'delete', + 'action' => 'revision', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::type' => 'archive', + '5::ids' => array( '1', '3', '4' ), + '6::ofield' => '1', + '7::nfield' => '2', + ), + ), + array( + 'text' => 'User changed visibility of 3 revisions on page Page: edit summary ' + . 'hidden and content unhidden', + 'api' => array( + 'type' => 'archive', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 2, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => false, + ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'delete', + 'action' => 'revision', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + 'archive', + '1,3,4', + 'ofield=1', + 'nfield=2', + ), + ), + array( + 'legacy' => true, + 'text' => 'User changed visibility of 3 revisions on page Page: edit summary ' + . 'hidden and content unhidden', + 'api' => array( + 'type' => 'archive', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 2, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => false, + ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideRevisionLogDatabaseRows + */ + public function testRevisionLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideEventLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'delete', + 'action' => 'event', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::ids' => array( '1', '3', '4' ), + '5::ofield' => '1', + '6::nfield' => '2', + ), + ), + array( + 'text' => 'User changed visibility of 3 log events on Page: edit summary hidden ' + . 'and content unhidden', + 'api' => array( + 'type' => 'logging', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 2, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => false, + ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'delete', + 'action' => 'event', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '1,3,4', + 'ofield=1', + 'nfield=2', + ), + ), + array( + 'legacy' => true, + 'text' => 'User changed visibility of 3 log events on Page: edit summary hidden ' + . 'and content unhidden', + 'api' => array( + 'type' => 'logging', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 2, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => false, + ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideEventLogDatabaseRows + */ + public function testEventLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideSuppressRevisionLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'suppress', + 'action' => 'revision', + 'comment' => 'Suppress comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::type' => 'archive', + '5::ids' => array( '1', '3', '4' ), + '6::ofield' => '1', + '7::nfield' => '10', + ), + ), + array( + 'text' => 'User secretly changed visibility of 3 revisions on page Page: edit ' + . 'summary hidden, content unhidden and applied restrictions to administrators', + 'api' => array( + 'type' => 'archive', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 10, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => true, + ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'suppress', + 'action' => 'revision', + 'comment' => 'Suppress comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + 'archive', + '1,3,4', + 'ofield=1', + 'nfield=10', + ), + ), + array( + 'legacy' => true, + 'text' => 'User secretly changed visibility of 3 revisions on page Page: edit ' + . 'summary hidden, content unhidden and applied restrictions to administrators', + 'api' => array( + 'type' => 'archive', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 10, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => true, + ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideSuppressRevisionLogDatabaseRows + */ + public function testSuppressRevisionLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideSuppressEventLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'suppress', + 'action' => 'event', + 'comment' => 'Suppress comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::ids' => array( '1', '3', '4' ), + '5::ofield' => '1', + '6::nfield' => '10', + ), + ), + array( + 'text' => 'User secretly changed visibility of 3 log events on Page: edit ' + . 'summary hidden, content unhidden and applied restrictions to administrators', + 'api' => array( + 'type' => 'logging', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 10, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => true, + ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'suppress', + 'action' => 'event', + 'comment' => 'Suppress comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '1,3,4', + 'ofield=1', + 'nfield=10', + ), + ), + array( + 'legacy' => true, + 'text' => 'User secretly changed visibility of 3 log events on Page: edit ' + . 'summary hidden, content unhidden and applied restrictions to administrators', + 'api' => array( + 'type' => 'logging', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 10, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => true, + ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideSuppressEventLogDatabaseRows + */ + public function testSuppressEventLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideSuppressDeleteLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'suppress', + 'action' => 'delete', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'text' => 'User suppressed page Page', + 'api' => array(), + ), + ), + + // Legacy format + array( + array( + 'type' => 'suppress', + 'action' => 'delete', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'User suppressed page Page', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideSuppressDeleteLogDatabaseRows + */ + public function testSuppressDeleteLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/LogFormatterTest.php b/tests/phpunit/includes/logging/LogFormatterTest.php index 515990e6..844c9afb 100644 --- a/tests/phpunit/includes/logging/LogFormatterTest.php +++ b/tests/phpunit/includes/logging/LogFormatterTest.php @@ -1,4 +1,5 @@ <?php + /** * @group Database */ @@ -19,6 +20,16 @@ class LogFormatterTest extends MediaWikiLangTestCase { */ protected $context; + /** + * @var Title + */ + protected $target; + + /** + * @var string + */ + protected $user_comment; + protected function setUp() { parent::setUp(); @@ -35,12 +46,15 @@ class LogFormatterTest extends MediaWikiLangTestCase { Language::getLocalisationCache()->recache( $wgLang->getCode() ); $this->user = User::newFromName( 'Testuser' ); - $this->title = Title::newMainPage(); + $this->title = Title::newFromText( 'SomeTitle' ); + $this->target = Title::newFromText( 'TestTarget' ); $this->context = new RequestContext(); $this->context->setUser( $this->user ); $this->context->setTitle( $this->title ); $this->context->setLanguage( $wgLang ); + + $this->user_comment = '<User comment about action>'; } protected function tearDown() { @@ -292,4 +306,309 @@ class LogFormatterTest extends MediaWikiLangTestCase { array( '4:user-link:key', 'foo', array( 'key' => 'Foo' ) ), ); } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeBlock() { + $sep = $this->context->msg( 'colon-separator' )->text(); + + # block/block + $this->assertIRCComment( + $this->context->msg( 'blocklogentry', 'SomeTitle', 'duration', '(flags)' )->plain() + . $sep . $this->user_comment, + 'block', 'block', + array( + '5::duration' => 'duration', + '6::flags' => 'flags', + ), + $this->user_comment + ); + # block/block - legacy + $this->assertIRCComment( + $this->context->msg( 'blocklogentry', 'SomeTitle', 'duration', '(flags)' )->plain() + . $sep . $this->user_comment, + 'block', 'block', + array( + 'duration', + 'flags', + ), + $this->user_comment, + '', + true + ); + # block/unblock + $this->assertIRCComment( + $this->context->msg( 'unblocklogentry', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'block', 'unblock', + array(), + $this->user_comment + ); + # block/reblock + $this->assertIRCComment( + $this->context->msg( 'reblock-logentry', 'SomeTitle', 'duration', '(flags)' )->plain() + . $sep . $this->user_comment, + 'block', 'reblock', + array( + '5::duration' => 'duration', + '6::flags' => 'flags', + ), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeDelete() { + $sep = $this->context->msg( 'colon-separator' )->text(); + + # delete/delete + $this->assertIRCComment( + $this->context->msg( 'deletedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'delete', 'delete', + array(), + $this->user_comment + ); + + # delete/restore + $this->assertIRCComment( + $this->context->msg( 'undeletedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'delete', 'restore', + array(), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeNewusers() { + $this->assertIRCComment( + 'New user account', + 'newusers', 'newusers', + array() + ); + $this->assertIRCComment( + 'New user account', + 'newusers', 'create', + array() + ); + $this->assertIRCComment( + 'created new account SomeTitle', + 'newusers', 'create2', + array() + ); + $this->assertIRCComment( + 'Account created automatically', + 'newusers', 'autocreate', + array() + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeMove() { + $move_params = array( + '4::target' => $this->target->getPrefixedText(), + '5::noredir' => 0, + ); + $sep = $this->context->msg( 'colon-separator' )->text(); + + # move/move + $this->assertIRCComment( + $this->context->msg( '1movedto2', 'SomeTitle', 'TestTarget' ) + ->plain() . $sep . $this->user_comment, + 'move', 'move', + $move_params, + $this->user_comment + ); + + # move/move_redir + $this->assertIRCComment( + $this->context->msg( '1movedto2_redir', 'SomeTitle', 'TestTarget' ) + ->plain() . $sep . $this->user_comment, + 'move', 'move_redir', + $move_params, + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypePatrol() { + # patrol/patrol + $this->assertIRCComment( + $this->context->msg( 'patrol-log-line', 'revision 777', '[[SomeTitle]]', '' )->plain(), + 'patrol', 'patrol', + array( + '4::curid' => '777', + '5::previd' => '666', + '6::auto' => 0, + ) + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeProtect() { + $protectParams = array( + '[edit=sysop] (indefinite) [move=sysop] (indefinite)' + ); + $sep = $this->context->msg( 'colon-separator' )->text(); + + # protect/protect + $this->assertIRCComment( + $this->context->msg( 'protectedarticle', 'SomeTitle ' . $protectParams[0] ) + ->plain() . $sep . $this->user_comment, + 'protect', 'protect', + $protectParams, + $this->user_comment + ); + + # protect/unprotect + $this->assertIRCComment( + $this->context->msg( 'unprotectedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'protect', 'unprotect', + array(), + $this->user_comment + ); + + # protect/modify + $this->assertIRCComment( + $this->context->msg( 'modifiedarticleprotection', 'SomeTitle ' . $protectParams[0] ) + ->plain() . $sep . $this->user_comment, + 'protect', 'modify', + $protectParams, + $this->user_comment + ); + + # protect/move_prot + $this->assertIRCComment( + $this->context->msg( 'movedarticleprotection', 'SomeTitle', 'OldTitle' ) + ->plain() . $sep . $this->user_comment, + 'protect', 'move_prot', + array( + '4::oldtitle' => 'OldTitle' + ), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeUpload() { + $sep = $this->context->msg( 'colon-separator' )->text(); + + # upload/upload + $this->assertIRCComment( + $this->context->msg( 'uploadedimage', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'upload', 'upload', + array(), + $this->user_comment + ); + + # upload/overwrite + $this->assertIRCComment( + $this->context->msg( 'overwroteimage', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'upload', 'overwrite', + array(), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeMerge() { + $sep = $this->context->msg( 'colon-separator' )->text(); + + # merge/merge + $this->assertIRCComment( + $this->context->msg( 'pagemerge-logentry', 'SomeTitle', 'Dest', 'timestamp' )->plain() + . $sep . $this->user_comment, + 'merge', 'merge', + array( + '4::dest' => 'Dest', + '5::mergepoint' => 'timestamp', + ), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeImport() { + $sep = $this->context->msg( 'colon-separator' )->text(); + + # import/upload + $msg = $this->context->msg( 'import-logentry-upload', 'SomeTitle' )->plain() . + $sep . + $this->user_comment; + $this->assertIRCComment( + $msg, + 'import', 'upload', + array(), + $this->user_comment + ); + + # import/interwiki + $msg = $this->context->msg( 'import-logentry-interwiki', 'SomeTitle' )->plain() . + $sep . + $this->user_comment; + $this->assertIRCComment( + $msg, + 'import', 'interwiki', + array(), + $this->user_comment + ); + } + + /** + * @param string $expected Expected IRC text without colors codes + * @param string $type Log type (move, delete, suppress, patrol ...) + * @param string $action A log type action + * @param array $params + * @param string $comment (optional) A comment for the log action + * @param string $msg (optional) A message for PHPUnit :-) + */ + protected function assertIRCComment( $expected, $type, $action, $params, + $comment = null, $msg = '', $legacy = false + ) { + $logEntry = new ManualLogEntry( $type, $action ); + $logEntry->setPerformer( $this->user ); + $logEntry->setTarget( $this->title ); + if ( $comment !== null ) { + $logEntry->setComment( $comment ); + } + $logEntry->setParameters( $params ); + $logEntry->setLegacy( $legacy ); + + $formatter = LogFormatter::newFromEntry( $logEntry ); + $formatter->setContext( $this->context ); + + // Apply the same transformation as done in IRCColourfulRCFeedFormatter::getLine for rc_comment + $ircRcComment = IRCColourfulRCFeedFormatter::cleanupForIRC( $formatter->getIRCActionComment() ); + + $this->assertEquals( + $expected, + $ircRcComment, + $msg + ); + } + } diff --git a/tests/phpunit/includes/logging/LogFormatterTestCase.php b/tests/phpunit/includes/logging/LogFormatterTestCase.php new file mode 100644 index 00000000..e88452b7 --- /dev/null +++ b/tests/phpunit/includes/logging/LogFormatterTestCase.php @@ -0,0 +1,65 @@ +<?php + +/** + * @since 1.26 + */ +abstract class LogFormatterTestCase extends MediaWikiLangTestCase { + + public function doTestLogFormatter( $row, $extra ) { + RequestContext::resetMain(); + $row = $this->expandDatabaseRow( $row, $this->isLegacy( $extra ) ); + + $formatter = LogFormatter::newFromRow( $row ); + + $this->assertEquals( + $extra['text'], + self::removeSomeHtml( $formatter->getActionText() ), + 'Action text is equal to expected text' + ); + + $this->assertSame( // ensure types and array key order + $extra['api'], + self::removeApiMetaData( $formatter->formatParametersForApi() ), + 'Api log params is equal to expected array' + ); + } + + protected function isLegacy( $extra ) { + return isset( $extra['legacy'] ) && $extra['legacy']; + } + + protected function expandDatabaseRow( $data, $legacy ) { + return array( + // no log_id because no insert in database + 'log_type' => $data['type'], + 'log_action' => $data['action'], + 'log_timestamp' => isset( $data['timestamp'] ) ? $data['timestamp'] : wfTimestampNow(), + 'log_user' => isset( $data['user'] ) ? $data['user'] : 0, + 'log_user_text' => isset( $data['user_text'] ) ? $data['user_text'] : 'User', + 'log_namespace' => isset( $data['namespace'] ) ? $data['namespace'] : NS_MAIN, + 'log_title' => isset( $data['title'] ) ? $data['title'] : 'Main_Page', + 'log_page' => isset( $data['page'] ) ? $data['page'] : 0, + 'log_comment' => isset( $data['comment'] ) ? $data['comment'] : '', + 'log_params' => $legacy + ? LogPage::makeParamBlob( $data['params'] ) + : LogEntryBase::makeParamBlob( $data['params'] ), + 'log_deleted' => isset( $data['deleted'] ) ? $data['deleted'] : 0, + ); + } + + private static function removeSomeHtml( $html ) { + $html = str_replace( '"', '"', $html ); + return trim( preg_replace( '/<(a|span)[^>]*>([^<]*)<\/\1>/', '$2', $html ) ); + } + + private static function removeApiMetaData( $val ) { + if ( is_array( $val ) ) { + unset( $val['_element'] ); + unset( $val['_type'] ); + foreach ( $val as $key => $value ) { + $val[$key] = self::removeApiMetaData( $value ); + } + } + return $val; + } +} diff --git a/tests/phpunit/includes/logging/MergeLogFormatterTest.php b/tests/phpunit/includes/logging/MergeLogFormatterTest.php new file mode 100644 index 00000000..2ff0ddf5 --- /dev/null +++ b/tests/phpunit/includes/logging/MergeLogFormatterTest.php @@ -0,0 +1,67 @@ +<?php + +class MergeLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideMergeLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'merge', + 'action' => 'merge', + 'comment' => 'Merge comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + '4::dest' => 'NewPage', + '5::mergepoint' => '20140804160710', + ), + ), + array( + 'text' => 'User merged OldPage into NewPage (revisions up to 16:07, 4 August 2014)', + 'api' => array( + 'dest_ns' => 0, + 'dest_title' => 'NewPage', + 'mergepoint' => '2014-08-04T16:07:10Z', + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'merge', + 'action' => 'merge', + 'comment' => 'merge comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + '20140804160710', + ), + ), + array( + 'legacy' => true, + 'text' => 'User merged OldPage into NewPage (revisions up to 16:07, 4 August 2014)', + 'api' => array( + 'dest_ns' => 0, + 'dest_title' => 'NewPage', + 'mergepoint' => '2014-08-04T16:07:10Z', + ), + ), + ), + ); + } + + /** + * @dataProvider provideMergeLogDatabaseRows + */ + public function testMergeLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/MoveLogFormatterTest.php b/tests/phpunit/includes/logging/MoveLogFormatterTest.php new file mode 100644 index 00000000..fdc4b7e1 --- /dev/null +++ b/tests/phpunit/includes/logging/MoveLogFormatterTest.php @@ -0,0 +1,270 @@ +<?php + +class MoveLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideMoveLogDatabaseRows() { + return array( + // Current format - with redirect + array( + array( + 'type' => 'move', + 'action' => 'move', + 'comment' => 'move comment with redirect', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + '4::target' => 'NewPage', + '5::noredir' => '0', + ), + ), + array( + 'text' => 'User moved page OldPage to NewPage', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + + // Current format - without redirect + array( + array( + 'type' => 'move', + 'action' => 'move', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + '4::target' => 'NewPage', + '5::noredir' => '1', + ), + ), + array( + 'text' => 'User moved page OldPage to NewPage without leaving a redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => true, + ), + ), + ), + + // legacy format - with redirect + array( + array( + 'type' => 'move', + 'action' => 'move', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + '', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + + // legacy format - without redirect + array( + array( + 'type' => 'move', + 'action' => 'move', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + '1', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage without leaving a redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => true, + ), + ), + ), + + // old format without flag for redirect suppression + array( + array( + 'type' => 'move', + 'action' => 'move', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + ); + } + + /** + * @dataProvider provideMoveLogDatabaseRows + */ + public function testMoveLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideMoveRedirLogDatabaseRows() { + return array( + // Current format - with redirect + array( + array( + 'type' => 'move', + 'action' => 'move_redir', + 'comment' => 'move comment with redirect', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + '4::target' => 'NewPage', + '5::noredir' => '0', + ), + ), + array( + 'text' => 'User moved page OldPage to NewPage over redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + + // Current format - without redirect + array( + array( + 'type' => 'move', + 'action' => 'move_redir', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + '4::target' => 'NewPage', + '5::noredir' => '1', + ), + ), + array( + 'text' => 'User moved page OldPage to NewPage over a redirect without leaving a redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => true, + ), + ), + ), + + // legacy format - with redirect + array( + array( + 'type' => 'move', + 'action' => 'move_redir', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + '', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage over redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + + // legacy format - without redirect + array( + array( + 'type' => 'move', + 'action' => 'move_redir', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + '1', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage over a redirect without leaving a redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => true, + ), + ), + ), + + // old format without flag for redirect suppression + array( + array( + 'type' => 'move', + 'action' => 'move_redir', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage over redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + ); + } + + /** + * @dataProvider provideMoveRedirLogDatabaseRows + */ + public function testMoveRedirLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/NewUsersLogFormatterTest.php b/tests/phpunit/includes/logging/NewUsersLogFormatterTest.php new file mode 100644 index 00000000..5b03370d --- /dev/null +++ b/tests/phpunit/includes/logging/NewUsersLogFormatterTest.php @@ -0,0 +1,207 @@ +<?php + +/** + * @group Database + */ +class NewUsersLogFormatterTest extends LogFormatterTestCase { + + protected function setUp() { + parent::setUp(); + + // Register LogHandler, see $wgNewUserLog in Setup.php + $this->mergeMwGlobalArrayValue( 'wgLogActionsHandlers', array( + 'newusers/newusers' => 'NewUsersLogFormatter', + 'newusers/create' => 'NewUsersLogFormatter', + 'newusers/create2' => 'NewUsersLogFormatter', + 'newusers/byemail' => 'NewUsersLogFormatter', + 'newusers/autocreate' => 'NewUsersLogFormatter', + ) ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideNewUsersLogDatabaseRows() { + return array( + // Only old logs + array( + array( + 'type' => 'newusers', + 'action' => 'newusers', + 'comment' => 'newusers comment', + 'user' => 0, + 'user_text' => 'New user', + 'namespace' => NS_USER, + 'title' => 'New user', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'User account New user was created', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideNewUsersLogDatabaseRows + */ + public function testNewUsersLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideCreateLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'newusers', + 'action' => 'create', + 'comment' => 'newusers comment', + 'user' => 0, + 'user_text' => 'New user', + 'namespace' => NS_USER, + 'title' => 'New user', + 'params' => array( + '4::userid' => 1, + ), + ), + array( + 'text' => 'User account New user was created', + 'api' => array( + 'userid' => 1, + ), + ), + ), + ); + } + + /** + * @dataProvider provideCreateLogDatabaseRows + */ + public function testCreateLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideCreate2LogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'newusers', + 'action' => 'create2', + 'comment' => 'newusers comment', + 'user' => 0, + 'user_text' => 'User', + 'namespace' => NS_USER, + 'title' => 'UTSysop', + 'params' => array( + '4::userid' => 1, + ), + ), + array( + 'text' => 'User account UTSysop was created by User', + 'api' => array( + 'userid' => 1, + ), + ), + ), + ); + } + + /** + * @dataProvider provideCreate2LogDatabaseRows + */ + public function testCreate2LogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideByemailLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'newusers', + 'action' => 'byemail', + 'comment' => 'newusers comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'UTSysop', + 'params' => array( + '4::userid' => 1, + ), + ), + array( + 'text' => 'User account UTSysop was created by Sysop and password was sent by email', + 'api' => array( + 'userid' => 1, + ), + ), + ), + ); + } + + /** + * @dataProvider provideByemailLogDatabaseRows + */ + public function testByemailLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideAutocreateLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'newusers', + 'action' => 'autocreate', + 'comment' => 'newusers comment', + 'user' => 0, + 'user_text' => 'New user', + 'namespace' => NS_USER, + 'title' => 'New user', + 'params' => array( + '4::userid' => 1, + ), + ), + array( + 'text' => 'User account New user was created automatically', + 'api' => array( + 'userid' => 1, + ), + ), + ), + ); + } + + /** + * @dataProvider provideAutocreateLogDatabaseRows + */ + public function testAutocreateLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/PageLangLogFormatterTest.php b/tests/phpunit/includes/logging/PageLangLogFormatterTest.php new file mode 100644 index 00000000..226e492b --- /dev/null +++ b/tests/phpunit/includes/logging/PageLangLogFormatterTest.php @@ -0,0 +1,53 @@ +<?php + +class PageLangLogFormatterTest extends LogFormatterTestCase { + + protected function setUp() { + parent::setUp(); + + // Disable cldr extension + $this->setMwGlobals( 'wgHooks', array() ); + // Register LogHandler, see $wgPageLanguageUseDB in Setup.php + $this->mergeMwGlobalArrayValue( 'wgLogActionsHandlers', array( + 'pagelang/pagelang' => 'PageLangLogFormatter', + ) ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function providePageLangLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'pagelang', + 'action' => 'pagelang', + 'comment' => 'page lang comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::oldlanguage' => 'en', + '5::newlanguage' => 'de[def]', + ), + ), + array( + 'text' => 'User changed page language for Page from English (en) to Deutsch (de) [default].', + 'api' => array( + 'oldlanguage' => 'en', + 'newlanguage' => 'de[def]' + ), + ), + ), + ); + } + + /** + * @dataProvider providePageLangLogDatabaseRows + */ + public function testPageLangLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/PatrolLogFormatterTest.php b/tests/phpunit/includes/logging/PatrolLogFormatterTest.php new file mode 100644 index 00000000..6e1c5efc --- /dev/null +++ b/tests/phpunit/includes/logging/PatrolLogFormatterTest.php @@ -0,0 +1,118 @@ +<?php + +class PatrolLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function providePatrolLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'patrol', + 'action' => 'patrol', + 'comment' => 'patrol comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::curid' => 2, + '5::previd' => 1, + '6::auto' => 0, + ), + ), + array( + 'text' => 'User marked revision 2 of page Page patrolled', + 'api' => array( + 'curid' => 2, + 'previd' => 1, + 'auto' => false, + ), + ), + ), + + // Current format - autopatrol + array( + array( + 'type' => 'patrol', + 'action' => 'patrol', + 'comment' => 'patrol comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::curid' => 2, + '5::previd' => 1, + '6::auto' => 1, + ), + ), + array( + 'text' => 'User automatically marked revision 2 of page Page patrolled', + 'api' => array( + 'curid' => 2, + 'previd' => 1, + 'auto' => true, + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'patrol', + 'action' => 'patrol', + 'comment' => 'patrol comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '2', + '1', + '0', + ), + ), + array( + 'legacy' => true, + 'text' => 'User marked revision 2 of page Page patrolled', + 'api' => array( + 'curid' => 2, + 'previd' => 1, + 'auto' => false, + ), + ), + ), + + // Legacy format - autopatrol + array( + array( + 'type' => 'patrol', + 'action' => 'patrol', + 'comment' => 'patrol comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '2', + '1', + '1', + ), + ), + array( + 'legacy' => true, + 'text' => 'User automatically marked revision 2 of page Page patrolled', + 'api' => array( + 'curid' => 2, + 'previd' => 1, + 'auto' => true, + ), + ), + ), + ); + } + + /** + * @dataProvider providePatrolLogDatabaseRows + */ + public function testPatrolLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/ProtectLogFormatterTest.php b/tests/phpunit/includes/logging/ProtectLogFormatterTest.php new file mode 100644 index 00000000..611b2dfc --- /dev/null +++ b/tests/phpunit/includes/logging/ProtectLogFormatterTest.php @@ -0,0 +1,63 @@ +<?php + +class ProtectLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideMoveProtLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'protect', + 'action' => 'move_prot', + 'comment' => 'Move comment', + 'namespace' => NS_MAIN, + 'title' => 'NewPage', + 'params' => array( + '4::oldtitle' => 'OldPage', + ), + ), + array( + 'text' => 'User moved protection settings from OldPage to NewPage', + 'api' => array( + 'oldtitle_ns' => 0, + 'oldtitle_title' => 'OldPage', + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'protect', + 'action' => 'move_prot', + 'comment' => 'Move comment', + 'namespace' => NS_MAIN, + 'title' => 'NewPage', + 'params' => array( + 'OldPage', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved protection settings from OldPage to NewPage', + 'api' => array( + 'oldtitle_ns' => 0, + 'oldtitle_title' => 'OldPage', + ), + ), + ), + ); + } + + /** + * @dataProvider provideMoveProtLogDatabaseRows + */ + public function testMoveProtLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/RightsLogFormatterTest.php b/tests/phpunit/includes/logging/RightsLogFormatterTest.php new file mode 100644 index 00000000..e9577f11 --- /dev/null +++ b/tests/phpunit/includes/logging/RightsLogFormatterTest.php @@ -0,0 +1,157 @@ +<?php + +class RightsLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideRightsLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'rights', + 'action' => 'rights', + 'comment' => 'rights comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'User', + 'params' => array( + '4::oldgroups' => array(), + '5::newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + array( + 'text' => 'Sysop changed group membership for User:User from (none) to ' + . 'administrator and bureaucrat', + 'api' => array( + 'oldgroups' => array(), + 'newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'rights', + 'action' => 'rights', + 'comment' => 'rights comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'User', + 'params' => array( + '', + 'sysop, bureaucrat', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop changed group membership for User:User from (none) to ' + . 'administrator and bureaucrat', + 'api' => array( + 'oldgroups' => array(), + 'newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + ), + + // Really old entry + array( + array( + 'type' => 'rights', + 'action' => 'rights', + 'comment' => 'rights comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'User', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'Sysop changed group membership for User:User', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideRightsLogDatabaseRows + */ + public function testRightsLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideAutopromoteLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'rights', + 'action' => 'autopromote', + 'comment' => 'rights comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Sysop', + 'params' => array( + '4::oldgroups' => array( 'sysop' ), + '5::newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + array( + 'text' => 'Sysop was automatically promoted from administrator to ' + . 'administrator and bureaucrat', + 'api' => array( + 'oldgroups' => array( 'sysop' ), + 'newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'rights', + 'action' => 'autopromote', + 'comment' => 'rights comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Sysop', + 'params' => array( + 'sysop', + 'sysop, bureaucrat', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop was automatically promoted from administrator to ' + . 'administrator and bureaucrat', + 'api' => array( + 'oldgroups' => array( 'sysop' ), + 'newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideAutopromoteLogDatabaseRows + */ + public function testAutopromoteLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/UploadLogFormatterTest.php b/tests/phpunit/includes/logging/UploadLogFormatterTest.php new file mode 100644 index 00000000..12f51613 --- /dev/null +++ b/tests/phpunit/includes/logging/UploadLogFormatterTest.php @@ -0,0 +1,166 @@ +<?php + +class UploadLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideUploadLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'upload', + 'action' => 'upload', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '20150101000000', + ), + ), + array( + 'text' => 'User uploaded File:File.png', + 'api' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '2015-01-01T00:00:00Z', + ), + ), + ), + + // Old format without params + array( + array( + 'type' => 'upload', + 'action' => 'upload', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array(), + ), + array( + 'text' => 'User uploaded File:File.png', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideUploadLogDatabaseRows + */ + public function testUploadLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideOverwriteLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'upload', + 'action' => 'overwrite', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '20150101000000', + ), + ), + array( + 'text' => 'User uploaded a new version of File:File.png', + 'api' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '2015-01-01T00:00:00Z', + ), + ), + ), + + // Old format without params + array( + array( + 'type' => 'upload', + 'action' => 'overwrite', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array(), + ), + array( + 'text' => 'User uploaded a new version of File:File.png', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideOverwriteLogDatabaseRows + */ + public function testOverwriteLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideRevertLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'upload', + 'action' => 'revert', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '20150101000000', + ), + ), + array( + 'text' => 'User uploaded File:File.png', + 'api' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '2015-01-01T00:00:00Z', + ), + ), + ), + + // Old format without params + array( + array( + 'type' => 'upload', + 'action' => 'revert', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array(), + ), + array( + 'text' => 'User uploaded File:File.png', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideRevertLogDatabaseRows + */ + public function testRevertLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/media/ExifBitmapTest.php b/tests/phpunit/includes/media/ExifBitmapTest.php index 41330f41..adbc9775 100644 --- a/tests/phpunit/includes/media/ExifBitmapTest.php +++ b/tests/phpunit/includes/media/ExifBitmapTest.php @@ -3,7 +3,7 @@ /** * @group Media */ -class ExifBitmapTest extends MediaWikiTestCase { +class ExifBitmapTest extends MediaWikiMediaTestCase { /** * @var ExifBitmapHandler @@ -143,4 +143,41 @@ class ExifBitmapTest extends MediaWikiTestCase { $res = $this->handler->convertMetadataVersion( $metadata, 1 ); $this->assertEquals( $expected, $res ); } + + /** + * @dataProvider provideSwappingICCProfile + * @covers BitmapHandler::swapICCProfile + */ + public function testSwappingICCProfile( $sourceFilename, $controlFilename, $newProfileFilename, $oldProfileName ) { + global $wgExiftool; + + if ( !$wgExiftool || !is_file( $wgExiftool ) ) { + $this->markTestSkipped( "Exiftool not installed, cannot test ICC profile swapping" ); + } + + $this->setMwGlobals( 'wgUseTinyRGBForJPGThumbnails', true ); + + $sourceFilepath = $this->filePath . $sourceFilename; + $controlFilepath = $this->filePath . $controlFilename; + $profileFilepath = $this->filePath . $newProfileFilename; + $filepath = $this->getNewTempFile(); + + copy( $sourceFilepath, $filepath ); + + $file = $this->dataFile( $sourceFilename, 'image/jpeg' ); + $this->handler->swapICCProfile( $filepath, $oldProfileName, $profileFilepath ); + + $this->assertEquals( sha1( file_get_contents( $filepath ) ), sha1( file_get_contents( $controlFilepath ) ) ); + } + + public function provideSwappingICCProfile() { + return array( + // File with sRGB should end up with TinyRGB + array( 'srgb.jpg', 'tinyrgb.jpg', 'tinyrgb.icc', 'IEC 61966-2.1 Default RGB colour space - sRGB' ), + // File with TinyRGB should be left unchanged + array( 'tinyrgb.jpg', 'tinyrgb.jpg', 'tinyrgb.icc', 'IEC 61966-2.1 Default RGB colour space - sRGB' ), + // File with no profile should be left unchanged + array( 'test.jpg', 'test.jpg', 'tinyrgb.icc', 'IEC 61966-2.1 Default RGB colour space - sRGB' ) + ); + } } diff --git a/tests/phpunit/includes/media/FormatMetadataTest.php b/tests/phpunit/includes/media/FormatMetadataTest.php index 54758f94..b666c83c 100644 --- a/tests/phpunit/includes/media/FormatMetadataTest.php +++ b/tests/phpunit/includes/media/FormatMetadataTest.php @@ -37,39 +37,6 @@ class FormatMetadataTest extends MediaWikiMediaTestCase { } /** - * @param string $filename - * @param int $expected Total image area - * @dataProvider provideFlattenArray - * @covers FormatMetadata::flattenArray - */ - public function testFlattenArray( $vals, $type, $noHtml, $ctx, $expected ) { - $actual = FormatMetadata::flattenArray( $vals, $type, $noHtml, $ctx ); - $this->assertEquals( $expected, $actual ); - } - - public static function provideFlattenArray() { - return array( - array( - array( 1, 2, 3 ), 'ul', false, false, - "<ul><li>1</li>\n<li>2</li>\n<li>3</li></ul>", - ), - array( - array( 1, 2, 3 ), 'ol', false, false, - "<ol><li>1</li>\n<li>2</li>\n<li>3</li></ol>", - ), - array( - array( 1, 2, 3 ), 'ul', true, false, - "\n*1\n*2\n*3", - ), - array( - array( 1, 2, 3 ), 'ol', true, false, - "\n#1\n#2\n#3", - ), - // TODO: more test cases - ); - } - - /** * @param mixed $input * @param mixed $output * @dataProvider provideResolveMultivalueValue diff --git a/tests/phpunit/includes/media/WebPTest.php b/tests/phpunit/includes/media/WebPTest.php new file mode 100644 index 00000000..d36710a3 --- /dev/null +++ b/tests/phpunit/includes/media/WebPTest.php @@ -0,0 +1,127 @@ +<?php +class WebPHandlerTest extends MediaWikiTestCase { + public function setUp() { + parent::setUp(); + // Allocated file for testing + $this->tempFileName = tempnam( wfTempDir(), 'WEBP' ); + } + public function tearDown() { + parent::tearDown(); + unlink( $this->tempFileName ); + } + /** + * @dataProvider provideTestExtractMetaData + */ + public function testExtractMetaData( $header, $expectedResult ) { + // Put header into file + file_put_contents( $this->tempFileName, $header ); + + $this->assertEquals( $expectedResult, WebPHandler::extractMetadata( $this->tempFileName ) ); + } + public function provideTestExtractMetaData() { + return array( + // Files from https://developers.google.com/speed/webp/gallery2 + array( "\x52\x49\x46\x46\x90\x68\x01\x00\x57\x45\x42\x50\x56\x50\x38\x4C\x83\x68\x01\x00\x2F\x8F\x01\x4B\x10\x8D\x38\x6C\xDB\x46\x92\xE0\xE0\x82\x7B\x6C", + array( 'compression' => 'lossless', 'width' => 400, 'height' => 301 ) ), + array( "\x52\x49\x46\x46\x64\x5B\x00\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x8F\x01\x00\x2C\x01\x00\x41\x4C\x50\x48\xE5\x0E", + array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 400, 'height' => 301) ), + array( "\x52\x49\x46\x46\xA8\x72\x00\x00\x57\x45\x42\x50\x56\x50\x38\x4C\x9B\x72\x00\x00\x2F\x81\x81\x62\x10\x8D\x40\x8C\x24\x39\x6E\x73\x73\x38\x01\x96", + array( 'compression' => 'lossless', 'width' => 386, 'height' => 395 ) ), + array( "\x52\x49\x46\x46\xE0\x42\x00\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x81\x01\x00\x8A\x01\x00\x41\x4C\x50\x48\x56\x10", + array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 386, 'height' => 395 ) ), + array( "\x52\x49\x46\x46\x70\x61\x02\x00\x57\x45\x42\x50\x56\x50\x38\x4C\x63\x61\x02\x00\x2F\x1F\xC3\x95\x10\x8D\xC8\x72\xDB\xC8\x92\x24\xD8\x91\xD9\x91", + array( 'compression' => 'lossless', 'width' => 800, 'height' => 600 ) ), + array( "\x52\x49\x46\x46\x1C\x1D\x01\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x1F\x03\x00\x57\x02\x00\x41\x4C\x50\x48\x25\x8B", + array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 800, 'height' => 600 ) ), + array( "\x52\x49\x46\x46\xFA\xC5\x00\x00\x57\x45\x42\x50\x56\x50\x38\x4C\xEE\xC5\x00\x00\x2F\xA4\x81\x28\x10\x8D\x40\x68\x24\xC9\x91\xA4\xAE\xF3\x97\x75", + array( 'compression' => 'lossless', 'width' => 421, 'height' => 163 ) ), + array( "\x52\x49\x46\x46\xF6\x5D\x00\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\xA4\x01\x00\xA2\x00\x00\x41\x4C\x50\x48\x38\x1A", + array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 421, 'height' => 163 ) ), + array( "\x52\x49\x46\x46\xC4\x96\x01\x00\x57\x45\x42\x50\x56\x50\x38\x4C\xB8\x96\x01\x00\x2F\x2B\xC1\x4A\x10\x11\x87\x6D\xDB\x48\x12\xFC\x60\xB0\x83\x24", + array( 'compression' => 'lossless', 'width' => 300, 'height' => 300 ) ), + array( "\x52\x49\x46\x46\x0A\x11\x01\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x2B\x01\x00\x2B\x01\x00\x41\x4C\x50\x48\x67\x6E", + array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 300, 'height' => 300 ) ), + + // Lossy files from https://developers.google.com/speed/webp/gallery1 + array( "\x52\x49\x46\x46\x68\x76\x00\x00\x57\x45\x42\x50\x56\x50\x38\x20\x5C\x76\x00\x00\xD2\xBE\x01\x9D\x01\x2A\x26\x02\x70\x01\x3E\xD5\x4E\x97\x43\xA2", + array( 'compression' => 'lossy', 'width' => 550, 'height' => 368 ) ), + array( "\x52\x49\x46\x46\xB0\xEC\x00\x00\x57\x45\x42\x50\x56\x50\x38\x20\xA4\xEC\x00\x00\xB2\x4B\x02\x9D\x01\x2A\x26\x02\x94\x01\x3E\xD1\x50\x96\x46\x26", + array( 'compression' => 'lossy', 'width' => 550, 'height' => 404 ) ), + array( "\x52\x49\x46\x46\x7A\x19\x03\x00\x57\x45\x42\x50\x56\x50\x38\x20\x6E\x19\x03\x00\xB2\xF8\x09\x9D\x01\x2A\x00\x05\xD0\x02\x3E\xAD\x46\x99\x4A\xA5", + array( 'compression' => 'lossy', 'width' => 1280, 'height' => 720 ) ), + array( "\x52\x49\x46\x46\x44\xB3\x02\x00\x57\x45\x42\x50\x56\x50\x38\x20\x38\xB3\x02\x00\x52\x57\x06\x9D\x01\x2A\x00\x04\x04\x03\x3E\xA5\x44\x96\x49\x26", + array( 'compression' => 'lossy', 'width' => 1024, 'height' => 772) ), + array( "\x52\x49\x46\x46\x02\x43\x01\x00\x57\x45\x42\x50\x56\x50\x38\x20\xF6\x42\x01\x00\x12\xC0\x05\x9D\x01\x2A\x00\x04\xF0\x02\x3E\x79\x34\x93\x47\xA4", + array( 'compression' => 'lossy', 'width' => 1024, 'height' => 752) ), + + // Animated file from https://groups.google.com/a/chromium.org/d/topic/blink-dev/Y8tRC4mdQz8/discussion + array( "\x52\x49\x46\x46\xD0\x0B\x02\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x12\x00\x00\x00\x3F\x01\x00\x3F\x01\x00\x41\x4E", + array( 'compression' => 'unknown', 'animated' => true, 'transparency' => true, 'width' => 320, 'height' => 320 ) ), + + // Error cases + array( '', false ), + array( ' ', false ), + array( 'RIFF ', false ), + array( 'RIFF1234WEBP ', false ), + array( 'RIFF1234WEBPVP8 ', false ), + array( 'RIFF1234WEBPVP8L ', false ), + ); + } + + /** + * @dataProvider provideTestWithFileExtractMetaData + */ + public function testWithFileExtractMetaData( $filename, $expectedResult ) { + $this->assertEquals( $expectedResult, WebPHandler::extractMetadata( $filename ) ); + } + public function provideTestWithFileExtractMetaData() { + return array( + array( __DIR__ . '/../../data/media/2_webp_ll.webp', + array( 'compression' => 'lossless', 'width' => 386, 'height' => 395 ) ), + array( __DIR__ . '/../../data/media/2_webp_a.webp', + array( 'compression' => 'lossy', 'animated' => false, 'transparency' => true, 'width' => 386, 'height' => 395 ) ), + ); + } + + /** + * @dataProvider provideTestGetImageSize + */ + public function testGetImageSize( $path, $expectedResult ) { + $handler = new WebPHandler(); + $this->assertEquals( $expectedResult, $handler->getImageSize( null, $path ) ); + } + public function provideTestGetImageSize() { + return array( + // Public domain files from https://developers.google.com/speed/webp/gallery2 + array( __DIR__ . '/../../data/media/2_webp_a.webp', array( 386, 395 ) ), + array( __DIR__ . '/../../data/media/2_webp_ll.webp', array( 386, 395 ) ), + array( __DIR__ . '/../../data/media/webp_animated.webp', array( 300, 225 ) ), + + // Error cases + array( __FILE__, false ), + ); + } + + /** + * Tests the WebP MIME detection. This should really be a separate test, but sticking it + * here for now. + * + * @dataProvider provideTestGetMimeType + */ + public function testGuessMimeType( $path ) { + $mime = MimeMagic::singleton(); + $this->assertEquals( 'image/webp', $mime->guessMimeType( $path, false ) ); + } + public function provideTestGetMimeType() { + return array( + // Public domain files from https://developers.google.com/speed/webp/gallery2 + array( __DIR__ . '/../../data/media/2_webp_a.webp' ), + array( __DIR__ . '/../../data/media/2_webp_ll.webp' ), + array( __DIR__ . '/../../data/media/webp_animated.webp' ), + ); + } +} + +/* Python code to extract a header and convert to PHP format: + * print '"%s"' % ''.join( '\\x%02X' % ord(c) for c in urllib.urlopen(url).read(36) ) + */ diff --git a/tests/phpunit/includes/media/XMPValidateTest.php b/tests/phpunit/includes/media/XMPValidateTest.php index ebec8f6c..53671d42 100644 --- a/tests/phpunit/includes/media/XMPValidateTest.php +++ b/tests/phpunit/includes/media/XMPValidateTest.php @@ -1,5 +1,7 @@ <?php +use Psr\Log\NullLogger; + /** * @group Media */ @@ -11,7 +13,8 @@ class XMPValidateTest extends MediaWikiTestCase { */ public function testValidateDate( $value, $expected ) { // The method should modify $value. - XMPValidate::validateDate( array(), $value, true ); + $validate = new XMPValidate( new NullLogger() ); + $validate->validateDate( array(), $value, true ); $this->assertEquals( $expected, $value ); } diff --git a/tests/phpunit/includes/objectcache/BagOStuffTest.php b/tests/phpunit/includes/objectcache/BagOStuffTest.php index 4516bb4e..b6840062 100644 --- a/tests/phpunit/includes/objectcache/BagOStuffTest.php +++ b/tests/phpunit/includes/objectcache/BagOStuffTest.php @@ -1,8 +1,10 @@ <?php /** * @author Matthias Mullie <mmullie@wikimedia.org> + * @group BagOStuff */ class BagOStuffTest extends MediaWikiTestCase { + /** @var BagOStuff */ private $cache; protected function setUp() { @@ -136,20 +138,48 @@ class BagOStuffTest extends MediaWikiTestCase { public function testGetMulti() { $value1 = array( 'this' => 'is', 'a' => 'test' ); $value2 = array( 'this' => 'is', 'another' => 'test' ); + $value3 = array( 'testing a key that may be encoded when sent to cache backend' ); $key1 = wfMemcKey( 'test1' ); $key2 = wfMemcKey( 'test2' ); + $key3 = wfMemcKey( 'will-%-encode' ); // internally, MemcachedBagOStuffs will encode to will-%25-encode $this->cache->add( $key1, $value1 ); $this->cache->add( $key2, $value2 ); + $this->cache->add( $key3, $value3 ); $this->assertEquals( - $this->cache->getMulti( array( $key1, $key2 ) ), - array( $key1 => $value1, $key2 => $value2 ) + array( $key1 => $value1, $key2 => $value2, $key3 => $value3 ), + $this->cache->getMulti( array( $key1, $key2, $key3 ) ) ); // cleanup $this->cache->delete( $key1 ); $this->cache->delete( $key2 ); + $this->cache->delete( $key3 ); + } + + /** + * @covers BagOStuff::getScopedLock + */ + public function testGetScopedLock() { + $key = wfMemcKey( 'test' ); + $value1 = $this->cache->getScopedLock( $key, 0 ); + $value2 = $this->cache->getScopedLock( $key, 0 ); + + $this->assertType( 'ScopedCallback', $value1, 'First call returned lock' ); + $this->assertNull( $value2, 'Duplicate call returned no lock' ); + + unset( $value1 ); + + $value3 = $this->cache->getScopedLock( $key, 0 ); + $this->assertType( 'ScopedCallback', $value3, 'Lock returned callback after release' ); + unset( $value3 ); + + $value1 = $this->cache->getScopedLock( $key, 0, 5, 'reentry' ); + $value2 = $this->cache->getScopedLock( $key, 0, 5, 'reentry' ); + + $this->assertType( 'ScopedCallback', $value1, 'First reentrant call returned lock' ); + $this->assertType( 'ScopedCallback', $value1, 'Second reentrant call returned lock' ); } } diff --git a/tests/phpunit/includes/objectcache/MultiWriteBagOStuffTest.php b/tests/phpunit/includes/objectcache/MultiWriteBagOStuffTest.php new file mode 100644 index 00000000..2b66181c --- /dev/null +++ b/tests/phpunit/includes/objectcache/MultiWriteBagOStuffTest.php @@ -0,0 +1,55 @@ +<?php + +/** + * @group Database + */ +class MultiWriteBagOStuffTest extends MediaWikiTestCase { + /** @var HashBagOStuff */ + private $cache1; + /** @var HashBagOStuff */ + private $cache2; + /** @var MultiWriteBagOStuff */ + private $cache; + + protected function setUp() { + parent::setUp(); + + $this->cache1 = new HashBagOStuff(); + $this->cache2 = new HashBagOStuff(); + $this->cache = new MultiWriteBagOStuff( array( + 'caches' => array( $this->cache1, $this->cache2 ), + 'replication' => 'async' + ) ); + } + + public function testSetImmediate() { + $key = wfRandomString(); + $value = wfRandomString(); + $this->cache->set( $key, $value ); + + // Set in tier 1 + $this->assertEquals( $value, $this->cache1->get( $key ), 'Written to tier 1' ); + // Set in tier 2 + $this->assertEquals( $value, $this->cache2->get( $key ), 'Written to tier 2' ); + } + + public function testSetDelayed() { + $key = wfRandomString(); + $value = wfRandomString(); + + // XXX: DeferredUpdates bound to transactions in CLI mode + $dbw = wfGetDB( DB_MASTER ); + $dbw->begin(); + $this->cache->set( $key, $value ); + + // Set in tier 1 + $this->assertEquals( $value, $this->cache1->get( $key ), 'Written to tier 1' ); + // Not yet set in tier 2 + $this->assertEquals( false, $this->cache2->get( $key ), 'Not written to tier 2' ); + + $dbw->commit(); + + // Set in tier 2 + $this->assertEquals( $value, $this->cache2->get( $key ), 'Written to tier 2' ); + } +} diff --git a/tests/phpunit/includes/objectcache/ReplicatedBagOStuffTest.php b/tests/phpunit/includes/objectcache/ReplicatedBagOStuffTest.php new file mode 100644 index 00000000..a419f5b6 --- /dev/null +++ b/tests/phpunit/includes/objectcache/ReplicatedBagOStuffTest.php @@ -0,0 +1,62 @@ +<?php + +class ReplicatedBagOStuffTest extends MediaWikiTestCase { + /** @var HashBagOStuff */ + private $writeCache; + /** @var HashBagOStuff */ + private $readCache; + /** @var ReplicatedBagOStuff */ + private $cache; + + protected function setUp() { + parent::setUp(); + + $this->writeCache = new HashBagOStuff(); + $this->readCache = new HashBagOStuff(); + $this->cache = new ReplicatedBagOStuff( array( + 'writeFactory' => $this->writeCache, + 'readFactory' => $this->readCache, + ) ); + } + + /** + * @covers ReplicatedBagOStuff::set + */ + public function testSet() { + $key = wfRandomString(); + $value = wfRandomString(); + $this->cache->set( $key, $value ); + + // Write to master. + $this->assertEquals( $this->writeCache->get( $key ), $value ); + // Don't write to slave. Replication is deferred to backend. + $this->assertEquals( $this->readCache->get( $key ), false ); + } + + /** + * @covers ReplicatedBagOStuff::get + */ + public function testGet() { + $key = wfRandomString(); + + $write = wfRandomString(); + $this->writeCache->set( $key, $write ); + $read = wfRandomString(); + $this->readCache->set( $key, $read ); + + // Read from slave. + $this->assertEquals( $this->cache->get( $key ), $read ); + } + + /** + * @covers ReplicatedBagOStuff::get + */ + public function testGetAbsent() { + $key = wfRandomString(); + $value = wfRandomString(); + $this->writeCache->set( $key, $value ); + + // Don't read from master. No failover if value is absent. + $this->assertEquals( $this->cache->get( $key ), false ); + } +} diff --git a/tests/phpunit/includes/objectcache/WANObjectCacheTest.php b/tests/phpunit/includes/objectcache/WANObjectCacheTest.php new file mode 100644 index 00000000..40ae4613 --- /dev/null +++ b/tests/phpunit/includes/objectcache/WANObjectCacheTest.php @@ -0,0 +1,292 @@ +<?php + +class WANObjectCacheTest extends MediaWikiTestCase { + /** @var WANObjectCache */ + private $cache; + + protected function setUp() { + parent::setUp(); + + if ( $this->getCliArg( 'use-wanobjectcache' ) ) { + $name = $this->getCliArg( 'use-wanobjectcache' ); + + $this->cache = ObjectCache::getWANInstance( $name ); + } else { + $this->cache = new WANObjectCache( array( + 'cache' => new HashBagOStuff(), + 'pool' => 'testcache-hash', + 'relayer' => new EventRelayerNull( array() ) + ) ); + } + + $wanCache = TestingAccessWrapper::newFromObject( $this->cache ); + $this->internalCache = $wanCache->cache; + } + + /** + * @dataProvider provider_testSetAndGet + * @covers WANObjectCache::set() + * @covers WANObjectCache::get() + * @param mixed $value + * @param integer $ttl + */ + public function testSetAndGet( $value, $ttl ) { + $key = wfRandomString(); + $this->cache->set( $key, $value, $ttl ); + + $curTTL = null; + $this->assertEquals( $value, $this->cache->get( $key, $curTTL ) ); + if ( is_infinite( $ttl ) || $ttl == 0 ) { + $this->assertTrue( is_infinite( $curTTL ), "Current TTL is infinite" ); + } else { + $this->assertGreaterThan( 0, $curTTL, "Current TTL > 0" ); + $this->assertLessThanOrEqual( $ttl, $curTTL, "Current TTL < nominal TTL" ); + } + } + + public static function provider_testSetAndGet() { + return array( + array( 14141, 3 ), + array( 3535.666, 3 ), + array( array(), 3 ), + array( null, 3 ), + array( '0', 3 ), + array( (object)array( 'meow' ), 3 ), + array( INF, 3 ), + array( '', 3 ), + array( 'pizzacat', INF ), + ); + } + + public function testGetNotExists() { + $key = wfRandomString(); + $curTTL = null; + $value = $this->cache->get( $key, $curTTL ); + + $this->assertFalse( $value, "Non-existing key has false value" ); + $this->assertNull( $curTTL, "Non-existing key has null current TTL" ); + } + + public function testSetOver() { + $key = wfRandomString(); + for ( $i = 0; $i < 3; ++$i ) { + $value = wfRandomString(); + $this->cache->set( $key, $value, 3 ); + + $this->assertEquals( $this->cache->get( $key ), $value ); + } + } + + /** + * @covers WANObjectCache::getWithSetCallback() + */ + public function testGetWithSetCallback() { + $cache = $this->cache; + + $key = wfRandomString(); + $value = wfRandomString(); + $cKey1 = wfRandomString(); + $cKey2 = wfRandomString(); + + $wasSet = 0; + $func = function( $old, &$ttl ) use ( &$wasSet, $value ) { + ++$wasSet; + $ttl = 20; // override with another value + return $value; + }; + + $wasSet = 0; + $v = $cache->getWithSetCallback( $key, $func, 30, array(), array( 'lockTSE' => 5 ) ); + $this->assertEquals( $v, $value ); + $this->assertEquals( 1, $wasSet, "Value regenerated" ); + + $curTTL = null; + $v = $cache->get( $key, $curTTL ); + $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' ); + $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' ); + + $wasSet = 0; + $v = $cache->getWithSetCallback( $key, $func, 30, array(), array( 'lockTSE' => 5 ) ); + $this->assertEquals( $v, $value ); + $this->assertEquals( 0, $wasSet, "Value not regenerated" ); + + $priorTime = microtime( true ); + usleep( 1 ); + $wasSet = 0; + $v = $cache->getWithSetCallback( $key, $func, 30, array( $cKey1, $cKey2 ) ); + $this->assertEquals( $v, $value ); + $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" ); + $t1 = $cache->getCheckKeyTime( $cKey1 ); + $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' ); + $t2 = $cache->getCheckKeyTime( $cKey2 ); + $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' ); + + $priorTime = microtime( true ); + $wasSet = 0; + $v = $cache->getWithSetCallback( $key, $func, 30, array( $cKey1, $cKey2 ) ); + $this->assertEquals( $v, $value ); + $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" ); + $t1 = $cache->getCheckKeyTime( $cKey1 ); + $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' ); + $t2 = $cache->getCheckKeyTime( $cKey2 ); + $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' ); + + $curTTL = null; + $v = $cache->get( $key, $curTTL, array( $cKey1, $cKey2 ) ); + $this->assertEquals( $v, $value ); + $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" ); + } + + /** + * @covers WANObjectCache::getWithSetCallback() + */ + public function testLockTSE() { + $cache = $this->cache; + $key = wfRandomString(); + $value = wfRandomString(); + + $calls = 0; + $func = function() use ( &$calls, $value ) { + ++$calls; + return $value; + }; + + $cache->delete( $key ); + $ret = $cache->getWithSetCallback( $key, 30, $func, array(), array( 'lockTSE' => 5 ) ); + $this->assertEquals( $value, $ret ); + $this->assertEquals( 1, $calls, 'Value was populated' ); + + // Acquire a lock to verify that getWithSetCallback uses lockTSE properly + $this->internalCache->lock( $key, 0 ); + $ret = $cache->getWithSetCallback( $key, 30, $func, array(), array( 'lockTSE' => 5 ) ); + $this->assertEquals( $value, $ret ); + $this->assertEquals( 1, $calls, 'Callback was not used' ); + } + + /** + * @covers WANObjectCache::getMulti() + */ + public function testGetMulti() { + $cache = $this->cache; + + $value1 = array( 'this' => 'is', 'a' => 'test' ); + $value2 = array( 'this' => 'is', 'another' => 'test' ); + + $key1 = wfRandomString(); + $key2 = wfRandomString(); + $key3 = wfRandomString(); + + $cache->set( $key1, $value1, 5 ); + $cache->set( $key2, $value2, 10 ); + + $curTTLs = array(); + $this->assertEquals( + array( $key1 => $value1, $key2 => $value2 ), + $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs ) + ); + + $this->assertEquals( 2, count( $curTTLs ), "Two current TTLs in array" ); + $this->assertGreaterThan( 0, $curTTLs[$key1], "Key 1 has current TTL > 0" ); + $this->assertGreaterThan( 0, $curTTLs[$key2], "Key 2 has current TTL > 0" ); + + $cKey1 = wfRandomString(); + $cKey2 = wfRandomString(); + $curTTLs = array(); + $this->assertEquals( + array( $key1 => $value1, $key2 => $value2 ), + $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs ), + 'Result array populated' + ); + + $priorTime = microtime( true ); + usleep( 1 ); + $curTTLs = array(); + $this->assertEquals( + array( $key1 => $value1, $key2 => $value2 ), + $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs, array( $cKey1, $cKey2 ) ), + "Result array populated even with new check keys" + ); + $t1 = $cache->getCheckKeyTime( $cKey1 ); + $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key 1 generated on miss' ); + $t2 = $cache->getCheckKeyTime( $cKey2 ); + $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check key 2 generated on miss' ); + $this->assertEquals( 2, count( $curTTLs ), "Current TTLs array set" ); + $this->assertLessThanOrEqual( 0, $curTTLs[$key1], 'Key 1 has current TTL <= 0' ); + $this->assertLessThanOrEqual( 0, $curTTLs[$key2], 'Key 2 has current TTL <= 0' ); + + usleep( 1 ); + $curTTLs = array(); + $this->assertEquals( + array( $key1 => $value1, $key2 => $value2 ), + $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs, array( $cKey1, $cKey2 ) ), + "Result array still populated even with new check keys" + ); + $this->assertEquals( 2, count( $curTTLs ), "Current TTLs still array set" ); + $this->assertLessThan( 0, $curTTLs[$key1], 'Key 1 has negative current TTL' ); + $this->assertLessThan( 0, $curTTLs[$key2], 'Key 2 has negative current TTL' ); + } + + /** + * @covers WANObjectCache::delete() + */ + public function testDelete() { + $key = wfRandomString(); + $value = wfRandomString(); + $this->cache->set( $key, $value ); + + $curTTL = null; + $v = $this->cache->get( $key, $curTTL ); + $this->assertEquals( $value, $v, "Key was created with value" ); + $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" ); + + $this->cache->delete( $key ); + + $curTTL = null; + $v = $this->cache->get( $key, $curTTL ); + $this->assertFalse( $v, "Deleted key has false value" ); + $this->assertLessThan( 0, $curTTL, "Deleted key has current TTL < 0" ); + + $this->cache->set( $key, $value . 'more' ); + $this->assertFalse( $v, "Deleted key is tombstoned and has false value" ); + $this->assertLessThan( 0, $curTTL, "Deleted key is tombstoned and has current TTL < 0" ); + } + + /** + * @covers WANObjectCache::touchCheckKey() + * @covers WANObjectCache::resetCheckKey() + * @covers WANObjectCache::getCheckKeyTime() + */ + public function testTouchKeys() { + $key = wfRandomString(); + + $priorTime = microtime( true ); + usleep( 1 ); + $t0 = $this->cache->getCheckKeyTime( $key ); + $this->assertGreaterThanOrEqual( $priorTime, $t0, 'Check key auto-created' ); + + $priorTime = microtime( true ); + usleep( 1 ); + $this->cache->touchCheckKey( $key ); + $t1 = $this->cache->getCheckKeyTime( $key ); + $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key created' ); + + $t2 = $this->cache->getCheckKeyTime( $key ); + $this->assertEquals( $t1, $t2, 'Check key time did not change' ); + + usleep( 1 ); + $this->cache->touchCheckKey( $key ); + $t3 = $this->cache->getCheckKeyTime( $key ); + $this->assertGreaterThan( $t2, $t3, 'Check key time increased' ); + + $t4 = $this->cache->getCheckKeyTime( $key ); + $this->assertEquals( $t3, $t4, 'Check key time did not change' ); + + usleep( 1 ); + $this->cache->resetCheckKey( $key ); + $t5 = $this->cache->getCheckKeyTime( $key ); + $this->assertGreaterThan( $t4, $t5, 'Check key time increased' ); + + $t6 = $this->cache->getCheckKeyTime( $key ); + $this->assertEquals( $t5, $t6, 'Check key time did not change' ); + } +} diff --git a/tests/phpunit/includes/parser/MagicVariableTest.php b/tests/phpunit/includes/parser/MagicVariableTest.php index 17226113..cd54a9e3 100644 --- a/tests/phpunit/includes/parser/MagicVariableTest.php +++ b/tests/phpunit/includes/parser/MagicVariableTest.php @@ -10,6 +10,8 @@ * @copyright Copyright © 2011, Antoine Musso * @file * @todo covers tags + * + * @group Database */ class MagicVariableTest extends MediaWikiTestCase { diff --git a/tests/phpunit/includes/parser/MediaWikiParserTest.php b/tests/phpunit/includes/parser/MediaWikiParserTest.php index df891f5a..96ae3bec 100644 --- a/tests/phpunit/includes/parser/MediaWikiParserTest.php +++ b/tests/phpunit/includes/parser/MediaWikiParserTest.php @@ -91,7 +91,7 @@ class MediaWikiParserTest { // enough to cause there to be separate names for different // things, which is good enough for our purposes. $extensionName = basename( dirname( $fileName ) ); - $testsName = $extensionName . '⁄' . basename( $fileName, '.txt' ); + $testsName = $extensionName . '__' . basename( $fileName, '.txt' ); $escapedFileName = strtr( $fileName, array( "'" => "\\'", '\\' => '\\\\' ) ); $parserTestClassName = ucfirst( $testsName ); // Official spec for class names: http://php.net/manual/en/language.oop5.basic.php diff --git a/tests/phpunit/includes/parser/NewParserTest.php b/tests/phpunit/includes/parser/NewParserTest.php index 91aad10c..df7da98c 100644 --- a/tests/phpunit/includes/parser/NewParserTest.php +++ b/tests/phpunit/includes/parser/NewParserTest.php @@ -91,7 +91,7 @@ class NewParserTest extends MediaWikiTestCase { ); $tmpGlobals['wgForeignFileRepos'] = array(); $tmpGlobals['wgDefaultExternalStore'] = array(); - $tmpGlobals['wgEnableParserCache'] = false; + $tmpGlobals['wgParserCacheType'] = CACHE_NONE; $tmpGlobals['wgCapitalLinks'] = true; $tmpGlobals['wgNoFollowLinks'] = true; $tmpGlobals['wgNoFollowDomainExceptions'] = array(); @@ -106,7 +106,6 @@ class NewParserTest extends MediaWikiTestCase { $tmpGlobals['wgAdaptiveMessageCache'] = true; $tmpGlobals['wgUseDatabaseMessages'] = true; $tmpGlobals['wgLocaltimezone'] = 'UTC'; - $tmpGlobals['wgDeferredUpdateList'] = array(); $tmpGlobals['wgGroupPermissions'] = array( '*' => array( 'createaccount' => true, @@ -160,10 +159,10 @@ class NewParserTest extends MediaWikiTestCase { $this->djVuSupport = new DjVuSupport(); // Tidy support $this->tidySupport = new TidySupport(); + $tmpGlobals['wgTidyConfig'] = null; $tmpGlobals['wgUseTidy'] = false; - $tmpGlobals['wgAlwaysUseTidy'] = false; $tmpGlobals['wgDebugTidy'] = false; - $tmpGlobals['wgTidyConf'] = $IP . '/includes/tidy.conf'; + $tmpGlobals['wgTidyConf'] = $IP . '/includes/tidy/tidy.conf'; $tmpGlobals['wgTidyOpts'] = ''; $tmpGlobals['wgTidyInternal'] = $this->tidySupport->isInternal(); @@ -185,6 +184,8 @@ class NewParserTest extends MediaWikiTestCase { $wgNamespaceAliases['Image'] = $this->savedWeirdGlobals['image_alias']; $wgNamespaceAliases['Image_talk'] = $this->savedWeirdGlobals['image_talk_alias']; + MWTidy::destroySingleton(); + // Restore backends RepoGroup::destroySingleton(); FileBackendGroup::destroySingleton(); @@ -454,6 +455,7 @@ class NewParserTest extends MediaWikiTestCase { $GLOBALS[$var] = $val; } + MWTidy::destroySingleton(); MagicWord::clearCache(); # The entries saved into RepoGroup cache with previous globals will be wrong. diff --git a/tests/phpunit/includes/parser/ParserMethodsTest.php b/tests/phpunit/includes/parser/ParserMethodsTest.php index 1790086a..af143caa 100644 --- a/tests/phpunit/includes/parser/ParserMethodsTest.php +++ b/tests/phpunit/includes/parser/ParserMethodsTest.php @@ -1,5 +1,9 @@ <?php +/** + * @group Database + */ + class ParserMethodsTest extends MediaWikiLangTestCase { public static function providePreSaveTransform() { diff --git a/tests/phpunit/includes/parser/TagHooksTest.php b/tests/phpunit/includes/parser/TagHooksTest.php index 251da471..4af38985 100644 --- a/tests/phpunit/includes/parser/TagHooksTest.php +++ b/tests/phpunit/includes/parser/TagHooksTest.php @@ -1,6 +1,7 @@ <?php /** + * @group Database * @group Parser */ class TagHookTest extends MediaWikiTestCase { @@ -18,12 +19,6 @@ class TagHookTest extends MediaWikiTestCase { return array( array( "foo<bar" ), array( "foo>bar" ), array( "foo\nbar" ), array( "foo\rbar" ) ); } - protected function setUp() { - parent::setUp(); - - $this->setMwGlobals( 'wgAlwaysUseTidy', false ); - } - /** * @dataProvider provideValidNames * @covers Parser::setHook diff --git a/tests/phpunit/includes/parser/TidyTest.php b/tests/phpunit/includes/parser/TidyTest.php index f656a74d..5db29080 100644 --- a/tests/phpunit/includes/parser/TidyTest.php +++ b/tests/phpunit/includes/parser/TidyTest.php @@ -7,8 +7,7 @@ class TidyTest extends MediaWikiTestCase { protected function setUp() { parent::setUp(); - $check = MWTidy::tidy( '' ); - if ( strpos( $check, '<!--' ) !== false ) { + if ( !MWTidy::isEnabled() ) { $this->markTestSkipped( 'Tidy not found' ); } } diff --git a/tests/phpunit/includes/password/PasswordPolicyChecksTest.php b/tests/phpunit/includes/password/PasswordPolicyChecksTest.php new file mode 100644 index 00000000..af34282f --- /dev/null +++ b/tests/phpunit/includes/password/PasswordPolicyChecksTest.php @@ -0,0 +1,136 @@ +<?php +/** + * Testing password-policy check functions + * + * 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 + */ + +class PasswordPolicyChecksTest extends MediaWikiTestCase { + + /** + * @covers PasswordPolicyChecks::checkMinimalPasswordLength + */ + public function testCheckMinimalPasswordLength() { + $statusOK = PasswordPolicyChecks::checkMinimalPasswordLength( + 3, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertTrue( $statusOK->isGood(), 'Password is longer than minimal policy' ); + $statusShort = PasswordPolicyChecks::checkMinimalPasswordLength( + 10, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertFalse( + $statusShort->isGood(), + 'Password is shorter than minimal policy' + ); + $this->assertTrue( + $statusShort->isOk(), + 'Password is shorter than minimal policy, not fatal' + ); + } + + /** + * @covers PasswordPolicyChecks::checkMinimumPasswordLengthToLogin + */ + public function testCheckMinimumPasswordLengthToLogin() { + $statusOK = PasswordPolicyChecks::checkMinimumPasswordLengthToLogin( + 3, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertTrue( $statusOK->isGood(), 'Password is longer than minimal policy' ); + $statusShort = PasswordPolicyChecks::checkMinimumPasswordLengthToLogin( + 10, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertFalse( + $statusShort->isGood(), + 'Password is shorter than minimum login policy' + ); + $this->assertFalse( + $statusShort->isOk(), + 'Password is shorter than minimum login policy, fatal' + ); + } + + /** + * @covers PasswordPolicyChecks::checkMaximalPasswordLength + */ + public function testCheckMaximalPasswordLength() { + $statusOK = PasswordPolicyChecks::checkMaximalPasswordLength( + 100, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertTrue( $statusOK->isGood(), 'Password is shorter than maximal policy' ); + $statusLong = PasswordPolicyChecks::checkMaximalPasswordLength( + 4, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertFalse( $statusLong->isGood(), + 'Password is longer than maximal policy' + ); + $this->assertFalse( $statusLong->isOk(), + 'Password is longer than maximal policy, fatal' + ); + } + + /** + * @covers PasswordPolicyChecks::checkPasswordCannotMatchUsername + */ + public function testCheckPasswordCannotMatchUsername() { + $statusOK = PasswordPolicyChecks::checkPasswordCannotMatchUsername( + 1, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertTrue( $statusOK->isGood(), 'Password does not match username' ); + $statusLong = PasswordPolicyChecks::checkPasswordCannotMatchUsername( + 1, // policy value + User::newFromName( 'user' ), // User + 'user' // password + ); + $this->assertFalse( $statusLong->isGood(), 'Password matches username' ); + $this->assertTrue( $statusLong->isOk(), 'Password matches username, not fatal' ); + } + + /** + * @covers PasswordPolicyChecks::checkPasswordCannotMatchBlacklist + */ + public function testCheckPasswordCannotMatchBlacklist() { + $statusOK = PasswordPolicyChecks::checkPasswordCannotMatchBlacklist( + true, // policy value + User::newFromName( 'Username' ), // User + 'AUniquePassword' // password + ); + $this->assertTrue( $statusOK->isGood(), 'Password is not on blacklist' ); + $statusLong = PasswordPolicyChecks::checkPasswordCannotMatchBlacklist( + true, // policy value + User::newFromName( 'Useruser1' ), // User + 'Passpass1' // password + ); + $this->assertFalse( $statusLong->isGood(), 'Password matches blacklist' ); + $this->assertTrue( $statusLong->isOk(), 'Password matches blacklist, not fatal' ); + } + +} diff --git a/tests/phpunit/includes/password/UserPasswordPolicyTest.php b/tests/phpunit/includes/password/UserPasswordPolicyTest.php new file mode 100644 index 00000000..ce4e30ab --- /dev/null +++ b/tests/phpunit/includes/password/UserPasswordPolicyTest.php @@ -0,0 +1,234 @@ +<?php +/** + * Testing for password-policy enforcement, based on a user's groups. + * + * 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 + */ + +class UserPasswordPolicyTest extends MediaWikiTestCase { + + protected $policies = array( + 'checkuser' => array( + 'MinimalPasswordLength' => 10, + 'MinimumPasswordLengthToLogin' => 6, + 'PasswordCannotMatchUsername' => true, + ), + 'sysop' => array( + 'MinimalPasswordLength' => 8, + 'MinimumPasswordLengthToLogin' => 1, + 'PasswordCannotMatchUsername' => true, + ), + 'default' => array( + 'MinimalPasswordLength' => 4, + 'MinimumPasswordLengthToLogin' => 1, + 'PasswordCannotMatchBlacklist' => true, + 'MaximalPasswordLength' => 4096, + ), + ); + + protected $checks = array( + 'MinimalPasswordLength' => 'PasswordPolicyChecks::checkMinimalPasswordLength', + 'MinimumPasswordLengthToLogin' => 'PasswordPolicyChecks::checkMinimumPasswordLengthToLogin', + 'PasswordCannotMatchUsername' => 'PasswordPolicyChecks::checkPasswordCannotMatchUsername', + 'PasswordCannotMatchBlacklist' => 'PasswordPolicyChecks::checkPasswordCannotMatchBlacklist', + 'MaximalPasswordLength' => 'PasswordPolicyChecks::checkMaximalPasswordLength', + ); + + private function getUserPasswordPolicy() { + return new UserPasswordPolicy( $this->policies, $this->checks ); + } + + /** + * @covers UserPasswordPolicy::getPoliciesForUser + */ + public function testGetPoliciesForUser() { + + $upp = $this->getUserPasswordPolicy(); + + $user = User::newFromName( 'TestUserPolicy' ); + $user->addGroup( 'sysop' ); + + $this->assertArrayEquals( + array( + 'MinimalPasswordLength' => 8, + 'MinimumPasswordLengthToLogin' => 1, + 'PasswordCannotMatchUsername' => 1, + 'PasswordCannotMatchBlacklist' => true, + 'MaximalPasswordLength' => 4096, + ), + $upp->getPoliciesForUser( $user ) + ); + } + + /** + * @covers UserPasswordPolicy::getPoliciesForGroups + */ + public function testGetPoliciesForGroups() { + $effective = UserPasswordPolicy::getPoliciesForGroups( + $this->policies, + array( 'user', 'checkuser' ), + $this->policies['default'] + ); + + $this->assertArrayEquals( + array( + 'MinimalPasswordLength' => 10, + 'MinimumPasswordLengthToLogin' => 6, + 'PasswordCannotMatchUsername' => true, + 'PasswordCannotMatchBlacklist' => true, + 'MaximalPasswordLength' => 4096, + ), + $effective + ); + } + + /** + * @dataProvider provideCheckUserPassword + * @covers UserPasswordPolicy::checkUserPassword + */ + public function testCheckUserPassword( $username, $groups, $password, $valid, $ok, $msg ) { + + $upp = $this->getUserPasswordPolicy(); + + $user = User::newFromName( $username ); + foreach ( $groups as $group ) { + $user->addGroup( $group ); + } + + $status = $upp->checkUserPassword( $user, $password ); + $this->assertSame( $valid, $status->isGood(), $msg . ' - password valid' ); + $this->assertSame( $ok, $status->isOk(), $msg . ' - can login' ); + } + + public function provideCheckUserPassword() { + return array( + array( + 'PassPolicyUser', + array(), + '', + false, + false, + 'No groups, default policy, password too short to login' + ), + array( + 'PassPolicyUser', + array( 'user' ), + 'aaa', + false, + true, + 'Default policy, short password' + ), + array( + 'PassPolicyUser', + array( 'sysop' ), + 'abcdabcdabcd', + true, + true, + 'Sysop with good password' + ), + array( + 'PassPolicyUser', + array( 'sysop' ), + 'abcd', + false, + true, + 'Sysop with short password' + ), + array( + 'PassPolicyUser', + array( 'sysop', 'checkuser' ), + 'abcdabcd', + false, + true, + 'Checkuser with short password' + ), + array( + 'PassPolicyUser', + array( 'sysop', 'checkuser' ), + 'abcd', + false, + false, + 'Checkuser with too short password to login' + ), + array( + 'Useruser', + array( 'user' ), + 'Passpass', + false, + true, + 'Username & password on blacklist' + ), + ); + } + + /** + * @dataProvider provideMaxOfPolicies + * @covers UserPasswordPolicy::maxOfPolicies + */ + public function testMaxOfPolicies( $p1, $p2, $max, $msg ) { + $this->assertArrayEquals( + $max, + UserPasswordPolicy::maxOfPolicies( $p1, $p2 ), + $msg + ); + } + + public function provideMaxOfPolicies() { + return array( + array( + array( 'MinimalPasswordLength' => 8 ), //p1 + array( 'MinimalPasswordLength' => 2 ), //p2 + array( 'MinimalPasswordLength' => 8 ), //max + 'Basic max in p1' + ), + array( + array( 'MinimalPasswordLength' => 2 ), //p1 + array( 'MinimalPasswordLength' => 8 ), //p2 + array( 'MinimalPasswordLength' => 8 ), //max + 'Basic max in p2' + ), + array( + array( 'MinimalPasswordLength' => 8 ), //p1 + array( + 'MinimalPasswordLength' => 2, + 'PasswordCannotMatchUsername' => 1, + ), //p2 + array( + 'MinimalPasswordLength' => 8, + 'PasswordCannotMatchUsername' => 1, + ), //max + 'Missing items in p1' + ), + array( + array( + 'MinimalPasswordLength' => 8, + 'PasswordCannotMatchUsername' => 1, + ), //p1 + array( + 'MinimalPasswordLength' => 2, + ), //p2 + array( + 'MinimalPasswordLength' => 8, + 'PasswordCannotMatchUsername' => 1, + ), //max + 'Missing items in p2' + ), + ); + } + +} diff --git a/tests/phpunit/includes/registration/CoreVersionCheckerTest.php b/tests/phpunit/includes/registration/CoreVersionCheckerTest.php new file mode 100644 index 00000000..bc154b37 --- /dev/null +++ b/tests/phpunit/includes/registration/CoreVersionCheckerTest.php @@ -0,0 +1,38 @@ +<?php + +/** + * @covers CoreVersionChecker + */ +class CoreVersionCheckerTest extends PHPUnit_Framework_TestCase { + /** + * @dataProvider provideCheck + */ + public function testCheck( $coreVersion, $constraint, $expected ) { + $checker = new CoreVersionChecker( $coreVersion ); + $this->assertEquals( $expected, $checker->check( $constraint ) ); + } + + public static function provideCheck() { + return array( + // array( $wgVersion, constraint, expected ) + array( '1.25alpha', '>= 1.26', false ), + array( '1.25.0', '>= 1.26', false ), + array( '1.26alpha', '>= 1.26', true ), + array( '1.26alpha', '>= 1.26.0', true ), + array( '1.26alpha', '>= 1.26.0-stable', false ), + array( '1.26.0', '>= 1.26.0-stable', true ), + array( '1.26.1', '>= 1.26.0-stable', true ), + array( '1.27.1', '>= 1.26.0-stable', true ), + array( '1.26alpha', '>= 1.26.1', false ), + array( '1.26alpha', '>= 1.26alpha', true ), + array( '1.26alpha', '>= 1.25', true ), + array( '1.26.0-alpha.14', '>= 1.26.0-alpha.15', false ), + array( '1.26.0-alpha.14', '>= 1.26.0-alpha.10', true ), + array( '1.26.1', '>= 1.26.2, <=1.26.0', false ), + array( '1.26.1', '^1.26.2', false ), + // Accept anything for un-parsable version strings + array( '1.26mwf14', '== 1.25alpha', true ), + array( 'totallyinvalid', '== 1.0', true ), + ); + } +} diff --git a/tests/phpunit/includes/registration/ExtensionProcessorTest.php b/tests/phpunit/includes/registration/ExtensionProcessorTest.php index ff6be6c2..1cb8a5d9 100644 --- a/tests/phpunit/includes/registration/ExtensionProcessorTest.php +++ b/tests/phpunit/includes/registration/ExtensionProcessorTest.php @@ -14,7 +14,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { * * @var array */ - static $default = array( + public static $default = array( 'name' => 'FooBar', ); @@ -28,7 +28,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { '@metadata' => array( 'foobarbaz' ), 'AnAttribute' => array( 'omg' ), 'AutoloadClasses' => array( 'FooBar' => 'includes/FooBar.php' ), - ) ); + ), 1 ); $extracted = $processor->getExtractedInfo(); $attributes = $extracted['attributes']; @@ -96,7 +96,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { */ public function testRegisterHooks( $pre, $info, $expected ) { $processor = new MockExtensionProcessor( array( 'wgHooks' => $pre ) ); - $processor->extractInfo( $this->dir, $info ); + $processor->extractInfo( $this->dir, $info, 1 ); $extracted = $processor->getExtractedInfo(); $this->assertEquals( $expected, $extracted['globals']['wgHooks'] ); } @@ -119,8 +119,8 @@ class ExtensionProcessorTest extends MediaWikiTestCase { 'Bar' => 'somevalue' ), ) + self::$default; - $processor->extractInfo( $this->dir, $info ); - $processor->extractInfo( $this->dir, $info2 ); + $processor->extractInfo( $this->dir, $info, 1 ); + $processor->extractInfo( $this->dir, $info2, 1 ); $extracted = $processor->getExtractedInfo(); $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] ); $this->assertEquals( 10, $extracted['globals']['wgFoo'] ); @@ -159,7 +159,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { */ public function testExtracttExtensionMessagesFiles( $input, $expected ) { $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default ); + $processor->extractInfo( $this->dir, $input + self::$default, 1 ); $out = $processor->getExtractedInfo(); foreach ( $expected as $key => $value ) { $this->assertEquals( $value, $out['globals'][$key] ); @@ -187,7 +187,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { */ public function testExtractMessagesDirs( $input, $expected ) { $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default ); + $processor->extractInfo( $this->dir, $input + self::$default, 1 ); $out = $processor->getExtractedInfo(); foreach ( $expected as $key => $value ) { $this->assertEquals( $value, $out['globals'][$key] ); @@ -200,7 +200,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { */ public function testExtractResourceLoaderModules( $input, $expected ) { $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default ); + $processor->extractInfo( $this->dir, $input + self::$default, 1 ); $out = $processor->getExtractedInfo(); foreach ( $expected as $key => $value ) { $this->assertEquals( $value, $out['globals'][$key] ); diff --git a/tests/phpunit/includes/registration/ExtensionRegistryTest.php b/tests/phpunit/includes/registration/ExtensionRegistryTest.php index c3a0c8d4..201cbfcd 100644 --- a/tests/phpunit/includes/registration/ExtensionRegistryTest.php +++ b/tests/phpunit/includes/registration/ExtensionRegistryTest.php @@ -218,6 +218,7 @@ class ExtensionRegistryTest extends MediaWikiTestCase { 'user' => array( 'right' => true, 'somethingtwo' => false, + 'nonduplicated' => true, ), ExtensionRegistry::MERGE_STRATEGY => 'array_plus_2d', ), @@ -233,6 +234,7 @@ class ExtensionRegistryTest extends MediaWikiTestCase { 'user' => array( 'somethingtwo' => true, 'right' => true, + 'nonduplicated' => true, ) ), ), diff --git a/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php b/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php new file mode 100644 index 00000000..0d11f621 --- /dev/null +++ b/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php @@ -0,0 +1,78 @@ +<?php + +/** + * @group ResourceLoader + */ +class DerivativeResourceLoaderContextTest extends PHPUnit_Framework_TestCase { + + protected static function getResourceLoaderContext() { + $resourceLoader = new ResourceLoader(); + $request = new FauxRequest( array( + 'lang' => 'zh', + 'modules' => 'test.context', + 'only' => 'scripts', + 'skin' => 'fallback', + 'target' => 'test', + ) ); + return new ResourceLoaderContext( $resourceLoader, $request ); + } + + public function testGet() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $this->assertEquals( $derived->getLanguage(), 'zh' ); + $this->assertEquals( $derived->getModules(), array( 'test.context' ) ); + $this->assertEquals( $derived->getOnly(), 'scripts' ); + $this->assertEquals( $derived->getSkin(), 'fallback' ); + $this->assertEquals( $derived->getHash(), 'zh|ltr|fallback||||||scripts|' ); + } + + public function testSetLanguage() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $derived->setLanguage( 'nl' ); + $this->assertEquals( $derived->getLanguage(), 'nl' ); + + $derived->setLanguage( 'he' ); + $this->assertEquals( $derived->getDirection(), 'rtl' ); + } + + public function testSetModules() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $derived->setModules( array( 'test.override' ) ); + $this->assertEquals( $derived->getModules(), array( 'test.override' ) ); + } + + public function testSetOnly() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $derived->setOnly( 'styles' ); + $this->assertEquals( $derived->getOnly(), 'styles' ); + + $derived->setOnly( null ); + $this->assertEquals( $derived->getOnly(), null ); + } + + public function testSetSkin() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $derived->setSkin( 'override' ); + $this->assertEquals( $derived->getSkin(), 'override' ); + } + + public function testGetHash() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $derived->setLanguage( 'nl' ); + // Assert that subclass is able to clear parent class "hash" member + $this->assertEquals( $derived->getHash(), 'nl|ltr|fallback||||||scripts|' ); + } + +} diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php index 122995a5..9d97b282 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php @@ -1,6 +1,7 @@ <?php /** + * @group Database * @group ResourceLoader */ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase { @@ -157,7 +158,7 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase { * @covers ResourceLoaderFileModule::getStyles * @covers ResourceLoaderFileModule::getStyleFiles */ - public function testMixedCssAnnotations( ) { + public function testMixedCssAnnotations() { $basePath = __DIR__ . '/../../data/css'; $testModule = new ResourceLoaderFileModule( array( 'localBasePath' => $basePath, @@ -225,23 +226,4 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase { $this->assertEquals( $rl->getTemplates(), $expected ); } - - public static function providerGetModifiedTime() { - $modules = self::getModules(); - - return array( - // Check the default value when no templates present in module is 1 - array( $modules['noTemplateModule'], 1 ), - ); - } - - /** - * @dataProvider providerGetModifiedTime - * @covers ResourceLoaderFileModule::getModifiedTime - */ - public function testGetModifiedTime( $module, $expected ) { - $rl = new ResourceLoaderFileModule( $module ); - $ts = $rl->getModifiedTime( $this->getResourceLoaderContext() ); - $this->assertEquals( $ts, $expected ); - } } diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php index 758cfe19..cc121ba3 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php @@ -109,9 +109,6 @@ class ResourceLoaderImageTest extends ResourceLoaderTestCase { class ResourceLoaderImageTestable extends ResourceLoaderImage { // Make some protected methods public - public function getPath( ResourceLoaderContext $context ) { - return parent::getPath( $context ); - } public function massageSvgPathdata( $svg ) { return parent::massageSvgPathdata( $svg ); } diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php index 6d1ed4e0..41653fb0 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php @@ -3,10 +3,9 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase { /** - * @covers ResourceLoaderModule::getDefinitionSummary - * @covers ResourceLoaderFileModule::getDefinitionSummary + * @covers ResourceLoaderModule::getVersionHash */ - public function testDefinitionSummary() { + public function testGetVersionHash() { $context = $this->getResourceLoaderContext(); $baseParams = array( @@ -16,15 +15,13 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase { ); $module = new ResourceLoaderFileModule( $baseParams ); - - $jsonSummary = json_encode( $module->getDefinitionSummary( $context ) ); + $version = json_encode( $module->getVersionHash( $context ) ); // Exactly the same $module = new ResourceLoaderFileModule( $baseParams ); - $this->assertEquals( - $jsonSummary, - json_encode( $module->getDefinitionSummary( $context ) ), + $version, + json_encode( $module->getVersionHash( $context ) ), 'Instance is insignificant' ); @@ -32,10 +29,9 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase { $module = new ResourceLoaderFileModule( array( 'dependencies' => array( 'mediawiki', 'jquery' ), ) + $baseParams ); - $this->assertEquals( - $jsonSummary, - json_encode( $module->getDefinitionSummary( $context ) ), + $version, + json_encode( $module->getVersionHash( $context ) ), 'Order of dependencies is insignificant' ); @@ -43,10 +39,9 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase { $module = new ResourceLoaderFileModule( array( 'messages' => array( 'world', 'hello' ), ) + $baseParams ); - $this->assertEquals( - $jsonSummary, - json_encode( $module->getDefinitionSummary( $context ) ), + $version, + json_encode( $module->getVersionHash( $context ) ), 'Order of messages is insignificant' ); @@ -54,20 +49,43 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase { $module = new ResourceLoaderFileModule( array( 'scripts' => array( 'bar.js', 'foo.js' ), ) + $baseParams ); - $this->assertNotEquals( - $jsonSummary, - json_encode( $module->getDefinitionSummary( $context ) ), + $version, + json_encode( $module->getVersionHash( $context ) ), 'Order of scripts is significant' ); // Subclass $module = new ResourceLoaderFileModuleTestModule( $baseParams ); - $this->assertNotEquals( - $jsonSummary, - json_encode( $module->getDefinitionSummary( $context ) ), + $version, + json_encode( $module->getVersionHash( $context ) ), 'Class is significant' ); } + + /** + * @covers ResourceLoaderModule::validateScriptFile + */ + public function testValidateScriptFile() { + $context = $this->getResourceLoaderContext(); + + $module = new ResourceLoaderTestModule( array( + 'script' => "var a = 'this is';\n {\ninvalid" + ) ); + $this->assertEquals( + $module->getScript( $context ), + 'mw.log.error("JavaScript parse error: Parse error: Unexpected token; token } expected in file \'input\' on line 3");', + 'Replace invalid syntax with error logging' + ); + + $module = new ResourceLoaderTestModule( array( + 'script' => "\n'valid';" + ) ); + $this->assertEquals( + $module->getScript( $context ), + "\n'valid';", + 'Leave valid scripts as-is' + ); + } } diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php index 7f3506cc..cb916142 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php @@ -10,7 +10,8 @@ class ResourceLoaderStartUpModuleTest extends ResourceLoaderTestCase { 'out' => ' mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [] );' +} ); +mw.loader.register( [] );' ) ), array( array( 'msg' => 'Basic registry', @@ -20,10 +21,11 @@ mw.loader.addSource( { 'out' => ' mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400 + "wvTifjse" ] ] );', ) ), @@ -37,20 +39,21 @@ mw.loader.addSource( { 'out' => ' mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400 + "wvTifjse" ], [ "test.group.foo", - 1388534400, + "wvTifjse", [], "x-foo" ], [ "test.group.bar", - 1388534400, + "wvTifjse", [], "x-bar" ] @@ -65,10 +68,11 @@ mw.loader.addSource( { 'out' => ' mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400 + "wvTifjse" ] ] );' ) ), @@ -87,10 +91,11 @@ mw.loader.addSource( { mw.loader.addSource( { "local": "/w/load.php", "example": "http://example.org/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400, + "wvTifjse", [], null, "example" @@ -123,14 +128,15 @@ mw.loader.addSource( { 'out' => ' mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.x.core", - 1388534400 + "wvTifjse" ], [ "test.x.polyfill", - 1388534400, + "wvTifjse", [], null, null, @@ -138,7 +144,7 @@ mw.loader.addSource( { ], [ "test.y.polyfill", - 1388534400, + "wvTifjse", [], null, null, @@ -146,7 +152,7 @@ mw.loader.addSource( { ], [ "test.z.foo", - 1388534400, + "wvTifjse", [ 0, 1, @@ -219,39 +225,40 @@ mw.loader.addSource( { mw.loader.addSource( { "local": "/w/load.php", "example": "http://example.org/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400 + "wvTifjse" ], [ "test.x.core", - 1388534400 + "wvTifjse" ], [ "test.x.util", - 1388534400, + "wvTifjse", [ 1 ] ], [ "test.x.foo", - 1388534400, + "wvTifjse", [ 1 ] ], [ "test.x.bar", - 1388534400, + "wvTifjse", [ 2 ] ], [ "test.x.quux", - 1388534400, + "wvTifjse", [ 3, 4, @@ -260,25 +267,25 @@ mw.loader.addSource( { ], [ "test.group.foo.1", - 1388534400, + "wvTifjse", [], "x-foo" ], [ "test.group.foo.2", - 1388534400, + "wvTifjse", [], "x-foo" ], [ "test.group.bar.1", - 1388534400, + "wvTifjse", [], "x-bar" ], [ "test.group.bar.2", - 1388534400, + "wvTifjse", [], "x-bar", "example" @@ -342,10 +349,10 @@ mw.loader.addSource( { $rl->register( $modules ); $module = new ResourceLoaderStartUpModule(); $this->assertEquals( -'mw.loader.addSource({"local":"/w/load.php"});' +'mw.loader.addSource({"local":"/w/load.php"});' . "\n" . 'mw.loader.register([' -. '["test.blank",1388534400],' -. '["test.min",1388534400,[0],null,null,' +. '["test.blank","wvTifjse"],' +. '["test.min","wvTifjse",[0],null,null,' . '"return!!(window.JSON\u0026\u0026JSON.parse\u0026\u0026JSON.stringify);"' . ']]);', $module->getModuleRegistrations( $context ), @@ -364,14 +371,15 @@ mw.loader.addSource( { $this->assertEquals( 'mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400 + "wvTifjse" ], [ "test.min", - 1388534400, + "wvTifjse", [ 0 ], diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php index ca7307ec..b6838859 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php @@ -6,17 +6,8 @@ class ResourceLoaderTest extends ResourceLoaderTestCase { parent::setUp(); $this->setMwGlobals( array( - 'wgResourceLoaderLESSFunctions' => array( - 'test-sum' => function ( $frame, $less ) { - $sum = 0; - foreach ( $frame[2] as $arg ) { - $sum += (int)$arg[1]; - } - return $sum; - }, - ), 'wgResourceLoaderLESSImportPaths' => array( - dirname( dirname( __DIR__ ) ) . '/data/less/common', + dirname( dirname( __DIR__ ) ) . '/data/less/common', ), 'wgResourceLoaderLESSVars' => array( 'foo' => '2px', diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php index 93a3ebba..8cefec75 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php @@ -31,16 +31,15 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase { $module = new ResourceLoaderWikiModule( $params ); $module->setConfig( $config ); - // Use getDefinitionSummary because getPages is protected - $summary = $module->getDefinitionSummary( ResourceLoaderContext::newDummyContext() ); - $this->assertEquals( - $expected, - $summary['pages'] - ); + // Because getPages is protected.. + $getPages = new ReflectionMethod( $module, 'getPages' ); + $getPages->setAccessible( true ); + $out = $getPages->invoke( $module, ResourceLoaderContext::newDummyContext() ); + $this->assertEquals( $expected, $out ); } public static function provideGetPages() { - $settings = array( + $settings = self::getSettings() + array( 'UseSiteJs' => true, 'UseSiteCss' => true, ); @@ -110,39 +109,27 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase { array( array(), 'test1', true ), // 'site' module with a non-empty page array( - array( - 'MediaWiki:Common.js' => array( - 'timestamp' => 123456789, - 'length' => 1234 - ) - ), 'site', false, + array( 'MediaWiki:Common.js' => array( 'rev_sha1' => 'dmh6qn', 'rev_len' => 1234 ) ), + 'site', + false, ), // 'site' module with an empty page array( - array( - 'MediaWiki:Monobook.js' => array( - 'timestamp' => 987654321, - 'length' => 0, - ), - ), 'site', false, + array( 'MediaWiki:Foo.js' => array( 'rev_sha1' => 'phoi', 'rev_len' => 0 ) ), + 'site', + false, ), // 'user' module with a non-empty page array( - array( - 'User:FooBar/common.js' => array( - 'timestamp' => 246813579, - 'length' => 25, - ), - ), 'user', false, + array( 'User:Example/common.js' => array( 'rev_sha1' => 'j7ssba', 'rev_len' => 25 ) ), + 'user', + false, ), // 'user' module with an empty page array( - array( - 'User:FooBar/monobook.js' => array( - 'timestamp' => 1357924680, - 'length' => 0, - ), - ), 'user', true, + array( 'User:Example/foo.js' => array( 'rev_sha1' => 'phoi', 'rev_len' => 0 ) ), + 'user', + true, ), ); } diff --git a/tests/phpunit/includes/site/CachingSiteStoreTest.php b/tests/phpunit/includes/site/CachingSiteStoreTest.php index d0a79803..4305ceb9 100644 --- a/tests/phpunit/includes/site/CachingSiteStoreTest.php +++ b/tests/phpunit/includes/site/CachingSiteStoreTest.php @@ -96,17 +96,17 @@ class CachingSiteStoreTest extends MediaWikiTestCase { ->getMock(); // php 5.3 compatibility! - $self = $this; + $that = $this; $dbSiteStore->expects( $this->any() ) ->method( 'getSite' ) - ->will( $this->returnValue( $self->getTestSite() ) ); + ->will( $this->returnValue( $that->getTestSite() ) ); $dbSiteStore->expects( $this->any() ) ->method( 'getSites' ) - ->will( $this->returnCallback( function() use( $self ) { + ->will( $this->returnCallback( function() use ( $that ) { $siteList = new SiteList(); - $siteList->setSite( $self->getTestSite() ); + $siteList->setSite( $that->getTestSite() ); return $siteList; } ) ); diff --git a/tests/phpunit/includes/site/DBSiteStoreTest.php b/tests/phpunit/includes/site/DBSiteStoreTest.php index 673ba54d..48ef5243 100644 --- a/tests/phpunit/includes/site/DBSiteStoreTest.php +++ b/tests/phpunit/includes/site/DBSiteStoreTest.php @@ -130,4 +130,28 @@ class DBSiteStoreTest extends MediaWikiTestCase { $sites = $store->getSites(); $this->assertEquals( 0, $sites->count() ); } + + /** + * @covers DBSiteStore::getSites + */ + public function testGetSitesDefaultOrder() { + $store = new DBSiteStore(); + $siteB = new Site(); + $siteB->setGlobalId( 'B' ); + $siteA = new Site(); + $siteA->setGlobalId( 'A' ); + $store->saveSites( array( $siteB, $siteA ) ); + + $sites = $store->getSites(); + $siteIdentifiers = array(); + /** @var Site $site */ + foreach ( $sites as $site ) { + $siteIdentifiers[] = $site->getGlobalId(); + } + $this->assertSame( array( 'A', 'B' ), $siteIdentifiers ); + + // Note: SiteList::getGlobalIdentifiers uses an other internal state. Iteration must be + // tested separately. + $this->assertSame( array( 'A', 'B' ), $sites->getGlobalIdentifiers() ); + } } diff --git a/tests/phpunit/includes/site/HashSiteStoreTest.php b/tests/phpunit/includes/site/HashSiteStoreTest.php index 49a96338..bebc0936 100644 --- a/tests/phpunit/includes/site/HashSiteStoreTest.php +++ b/tests/phpunit/includes/site/HashSiteStoreTest.php @@ -32,7 +32,7 @@ class HashSiteStoreTest extends MediaWikiTestCase { public function testGetSites() { $expectedSites = array(); - foreach( TestSites::getSites() as $testSite ) { + foreach ( TestSites::getSites() as $testSite ) { $siteId = $testSite->getGlobalId(); $expectedSites[$siteId] = $testSite; } diff --git a/tests/phpunit/includes/site/SiteExporterTest.php b/tests/phpunit/includes/site/SiteExporterTest.php index 19dd0aa1..7be19ef9 100644 --- a/tests/phpunit/includes/site/SiteExporterTest.php +++ b/tests/phpunit/includes/site/SiteExporterTest.php @@ -53,7 +53,7 @@ class SiteExporterTest extends PHPUnit_Framework_TestCase { $exporter->exportSites( array( $foo, $acme ) ); fseek( $tmp, 0 ); - $xml = fread( $tmp, 16*1024 ); + $xml = fread( $tmp, 16 * 1024 ); $this->assertContains( '<sites ', $xml ); $this->assertContains( '<site>', $xml ); @@ -133,7 +133,7 @@ class SiteExporterTest extends PHPUnit_Framework_TestCase { $exporter->exportSites( $sites ); fseek( $tmp, 0 ); - $xml = fread( $tmp, 16*1024 ); + $xml = fread( $tmp, 16 * 1024 ); $actualSites = new SiteList(); $store = $this->newSiteStore( $actualSites ); diff --git a/tests/phpunit/includes/site/SiteImporterTest.php b/tests/phpunit/includes/site/SiteImporterTest.php index cb0316ab..b11b1a9f 100644 --- a/tests/phpunit/includes/site/SiteImporterTest.php +++ b/tests/phpunit/includes/site/SiteImporterTest.php @@ -34,11 +34,11 @@ class SiteImporterTest extends PHPUnit_Framework_TestCase { private function newSiteImporter( array $expectedSites, $errorCount ) { $store = $this->getMock( 'SiteStore' ); - $self = $this; + $that = $this; $store->expects( $this->once() ) ->method( 'saveSites' ) - ->will( $this->returnCallback( function ( $sites ) use ( $expectedSites, $self ) { - $self->assertSitesEqual( $expectedSites, $sites ); + ->will( $this->returnCallback( function ( $sites ) use ( $expectedSites, $that ) { + $that->assertSitesEqual( $expectedSites, $sites ); } ) ); $store->expects( $this->any() ) @@ -141,12 +141,12 @@ class SiteImporterTest extends PHPUnit_Framework_TestCase { /** * @dataProvider provideImportFromXML */ - public function testImportFromXML( $xml, array $expectedSites, $errorCount = 0 ) { + public function testImportFromXML( $xml, array $expectedSites, $errorCount = 0 ) { $importer = $this->newSiteImporter( $expectedSites, $errorCount ); $importer->importFromXML( $xml ); } - public function testImportFromXML_malformed() { + public function testImportFromXML_malformed() { $this->setExpectedException( 'Exception' ); $store = $this->getMock( 'SiteStore' ); @@ -154,7 +154,7 @@ class SiteImporterTest extends PHPUnit_Framework_TestCase { $importer->importFromXML( 'THIS IS NOT XML' ); } - public function testImportFromFile() { + public function testImportFromFile() { $foo = Site::newForType( Site::TYPE_UNKNOWN ); $foo->setGlobalId( 'Foo' ); diff --git a/tests/phpunit/includes/specials/SpecialBlankPageTest.php b/tests/phpunit/includes/specials/SpecialBlankPageTest.php new file mode 100644 index 00000000..1d4f5e51 --- /dev/null +++ b/tests/phpunit/includes/specials/SpecialBlankPageTest.php @@ -0,0 +1,25 @@ +<?php + +/** + * @licence GNU GPL v2+ + * @author Adam Shorland + * + * @covers SpecialBlankpage + */ +class SpecialBlankPageTest extends SpecialPageTestBase { + + /** + * Returns a new instance of the special page under test. + * + * @return SpecialPage + */ + protected function newSpecialPage() { + return new SpecialBlankpage(); + } + + public function testHasWikiMsg() { + list( $html, ) = $this->executeSpecialPage(); + $this->assertContains( wfMessage( 'intentionallyblankpage' )->text(), $html ); + } + +} diff --git a/tests/phpunit/includes/specials/SpecialPageTestBase.php b/tests/phpunit/includes/specials/SpecialPageTestBase.php new file mode 100644 index 00000000..9c7b0f00 --- /dev/null +++ b/tests/phpunit/includes/specials/SpecialPageTestBase.php @@ -0,0 +1,165 @@ +<?php + +/** + * Base class for testing special pages. + * + * @since 1.26 + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + * @author Daniel Kinzler + * @author Adam Shorland + * @author Thiemo Mättig + */ +abstract class SpecialPageTestBase extends MediaWikiTestCase { + + private $obLevel; + + protected function setUp() { + parent::setUp(); + + $this->obLevel = ob_get_level(); + } + + protected function tearDown() { + $obLevel = ob_get_level(); + + while ( ob_get_level() > $this->obLevel ) { + ob_end_clean(); + } + + if ( $obLevel !== $this->obLevel ) { + $this->fail( + "Test changed output buffer level: was {$this->obLevel} before test, but $obLevel after test." + ); + } + + parent::tearDown(); + } + + /** + * Returns a new instance of the special page under test. + * + * @return SpecialPage + */ + abstract protected function newSpecialPage(); + + /** + * @param string $subPage The subpage parameter to call the page with + * @param WebRequest|null $request Web request that may contain URL parameters, etc + * @param Language|string|null $language The language which should be used in the context + * @param User|null $user The user which should be used in the context of this special page + * + * @throws Exception + * @return array( string, WebResponse ) A two-elements array containing the HTML output + * generated by the special page as well as the response object. + */ + protected function executeSpecialPage( + $subPage = '', + WebRequest $request = null, + $language = null, + User $user = null + ) { + $context = $this->newContext( $request, $language, $user ); + + $output = new OutputPage( $context ); + $context->setOutput( $output ); + + $page = $this->newSpecialPage(); + $page->setContext( $context ); + $output->setTitle( $page->getPageTitle() ); + + $html = $this->getHTMLFromSpecialPage( $page, $subPage ); + $response = $context->getRequest()->response(); + + if ( $response instanceof FauxResponse ) { + $code = $response->getStatusCode(); + + if ( $code > 0 ) { + $response->header( 'Status: ' . $code . ' ' . HttpStatus::getMessage( $code ) ); + } + } + + return array( $html, $response ); + } + + /** + * @param WebRequest|null $request + * @param Language|string|null $language + * @param User|null $user + * + * @return DerivativeContext + */ + private function newContext( + WebRequest $request = null, + $language = null, + User $user = null + ) { + $context = new DerivativeContext( RequestContext::getMain() ); + + $context->setRequest( $request ?: new FauxRequest() ); + + if ( $language !== null ) { + $context->setLanguage( $language ); + } + + if ( $user !== null ) { + $context->setUser( $user ); + } + + $this->setEditTokenFromUser( $context ); + + return $context; + } + + /** + * If we are trying to edit and no token is set, supply one. + * + * @param DerivativeContext $context + */ + private function setEditTokenFromUser( DerivativeContext $context ) { + $request = $context->getRequest(); + + // Edits via GET are a security issue and should not succeed. On the other hand, not all + // POST requests are edits, but should ignore unused parameters. + if ( !$request->getCheck( 'wpEditToken' ) && $request->wasPosted() ) { + $request->setVal( 'wpEditToken', $context->getUser()->getEditToken() ); + } + } + + /** + * @param SpecialPage $page + * @param string $subPage + * + * @throws Exception + * @return string HTML + */ + private function getHTMLFromSpecialPage( SpecialPage $page, $subPage ) { + ob_start(); + + try { + $page->execute( $subPage ); + + $output = $page->getOutput(); + + if ( $output->getRedirect() !== '' ) { + $output->output(); + $html = ob_get_contents(); + } elseif ( $output->isDisabled() ) { + $html = ob_get_contents(); + } else { + $html = $output->getHTML(); + } + } catch ( Exception $ex ) { + ob_end_clean(); + + // Re-throw exception after "finally" handling because PHP 5.3 doesn't have "finally". + throw $ex; + } + + ob_end_clean(); + + return $html; + } + +} diff --git a/tests/phpunit/includes/specials/SpecialPreferencesTest.php b/tests/phpunit/includes/specials/SpecialPreferencesTest.php index 4f6c4116..1545d7ec 100644 --- a/tests/phpunit/includes/specials/SpecialPreferencesTest.php +++ b/tests/phpunit/includes/specials/SpecialPreferencesTest.php @@ -4,10 +4,11 @@ * * Copyright © 2013, Antoine Musso * Copyright © 2013, Wikimedia Foundation Inc. - * */ /** + * @group Database + * * @covers SpecialPreferences */ class SpecialPreferencesTest extends MediaWikiTestCase { diff --git a/tests/phpunit/includes/specials/SpecialSearchTest.php b/tests/phpunit/includes/specials/SpecialSearchTest.php index 83489c65..13c28381 100644 --- a/tests/phpunit/includes/specials/SpecialSearchTest.php +++ b/tests/phpunit/includes/specials/SpecialSearchTest.php @@ -136,9 +136,113 @@ class SpecialSearchTest extends MediaWikiTestCase { # Compare :-] $this->assertRegExp( - '/' . preg_quote( $term ) . '/', + '/' . preg_quote( $term, '/' ) . '/', $pageTitle, "Search term '{$term}' should not be expanded in Special:Search <title>" ); } + + public function provideRewriteQueryWithSuggestion() { + return array( + array( + 'With suggestion and no rewritten query shows did you mean', + '/Did you mean: <a[^>]+>first suggestion/', + new SpecialSearchTestMockResultSet( 'first suggestion', null, array( + SearchResult::newFromTitle( Title::newMainPage() ), + ) ), + ), + + array( + 'With rewritten query informs user of change', + '/Showing results for <a[^>]+>first suggestion/', + new SpecialSearchTestMockResultSet( 'asdf', 'first suggestion', array( + SearchResult::newFromTitle( Title::newMainPage() ), + ) ), + ), + + array( + 'When both queries have no results user gets no results', + '/There were no results matching the query/', + new SpecialSearchTestMockResultSet( 'first suggestion', 'first suggestion', array() ), + ), + ); + } + + /** + * @dataProvider provideRewriteQueryWithSuggestion + */ + public function testRewriteQueryWithSuggestion( $message, $expectRegex, $results ) { + $mockSearchEngine = $this->mockSearchEngine( $results ); + $search = $this->getMockBuilder( 'SpecialSearch' ) + ->setMethods( array( 'getSearchEngine' ) ) + ->getMock(); + $search->expects( $this->any() ) + ->method( 'getSearchEngine' ) + ->will( $this->returnValue( $mockSearchEngine ) ); + + $search->getContext()->setTitle( Title::makeTitle( NS_SPECIAL, 'Search' ) ); + $search->load(); + $search->showResults( 'this is a fake search' ); + + $html = $search->getContext()->getOutput()->getHTML(); + foreach ( (array)$expectRegex as $regex ) { + $this->assertRegExp( $regex, $html, $message ); + } + } + + protected function mockSearchEngine( $results ) { + $mock = $this->getMockBuilder( 'SearchEngine' ) + ->setMethods( array( 'searchText', 'searchTitle' ) ) + ->getMock(); + + $mock->expects( $this->any() ) + ->method( 'searchText' ) + ->will( $this->returnValue( $results ) ); + + return $mock; + } +} + +class SpecialSearchTestMockResultSet extends SearchResultSet { + protected $results; + protected $suggestion; + + public function __construct( $suggestion = null, $rewrittenQuery = null, array $results = array(), $containedSyntax = false) { + $this->suggestion = $suggestion; + $this->rewrittenQuery = $rewrittenQuery; + $this->results = $results; + $this->containedSyntax = $containedSyntax; + } + + public function numRows() { + return count( $this->results ); + } + + public function getTotalHits() { + return $this->numRows(); + } + + public function hasSuggestion() { + return $this->suggestion !== null; + } + + public function getSuggestionQuery() { + return $this->suggestion; + } + + public function getSuggestionSnippet() { + return $this->suggestion; + } + + public function hasRewrittenQuery() { + return $this->rewrittenQuery !== null; + } + + public function getQueryAfterRewrite() { + return $this->rewrittenQuery; + } + + public function getQueryAfterRewriteSnippet() { + return htmlspecialchars( $this->rewrittenQuery ); + } } diff --git a/tests/phpunit/includes/title/ForeignTitleTest.php b/tests/phpunit/includes/title/ForeignTitleTest.php index 599d2a33..10b7e28f 100644 --- a/tests/phpunit/includes/title/ForeignTitleTest.php +++ b/tests/phpunit/includes/title/ForeignTitleTest.php @@ -59,7 +59,7 @@ class ForeignTitleTest extends MediaWikiTestCase { $this->assertEquals( $expectedText, $title->getText() ); } - public function testUnknownNamespaceCheck( ) { + public function testUnknownNamespaceCheck() { $title = new ForeignTitle( null, 'this', 'that' ); $this->assertEquals( false, $title->isNamespaceIdKnown() ); @@ -67,7 +67,7 @@ class ForeignTitleTest extends MediaWikiTestCase { $this->assertEquals( 'that', $title->getText() ); } - public function testUnknownNamespaceError( ) { + public function testUnknownNamespaceError() { $this->setExpectedException( 'MWException' ); $title = new ForeignTitle( null, 'this', 'that' ); $title->getNamespaceId(); diff --git a/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php b/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php index cd0d0b1c..1e5f9d01 100644 --- a/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php +++ b/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php @@ -158,7 +158,7 @@ class MediaWikiPageLinkRendererTest extends MediaWikiTestCase { function ( TitleValue $title ) { return str_replace( '_', ' ', "$title" ); } - )); + ) ); $renderer = new MediaWikiPageLinkRenderer( $formatter, '/' ); $actual = $renderer->renderWikitextLink( $title, $text ); diff --git a/tests/phpunit/includes/upload/UploadStashTest.php b/tests/phpunit/includes/upload/UploadStashTest.php index d5d1188e..8f9eabe9 100644 --- a/tests/phpunit/includes/upload/UploadStashTest.php +++ b/tests/phpunit/includes/upload/UploadStashTest.php @@ -20,7 +20,7 @@ class UploadStashTest extends MediaWikiTestCase { parent::setUp(); // Setup a file for bug 29408 - $this->bug29408File = __DIR__ . '/bug29408'; + $this->bug29408File = wfTempDir() . '/bug29408'; file_put_contents( $this->bug29408File, "\x00" ); self::$users = array( diff --git a/tests/phpunit/includes/utils/AvroValidatorTest.php b/tests/phpunit/includes/utils/AvroValidatorTest.php new file mode 100644 index 00000000..52c242c1 --- /dev/null +++ b/tests/phpunit/includes/utils/AvroValidatorTest.php @@ -0,0 +1,96 @@ +<?php +/** + * Tests for IP validity functions. + * + * Ported from /t/inc/IP.t by avar. + * + * @group IP + * @todo Test methods in this call should be split into a method and a + * dataprovider. + */ + +class AvroValidatorTest extends PHPUnit_Framework_TestCase { + public function setUp() { + if ( !class_exists( 'AvroSchema' ) ) { + $this->markTestSkipped( 'Avro is required to run the AvroValidatorTest' ); + } + parent::setUp(); + } + + public function getErrorsProvider() { + $stringSchema = AvroSchema::parse( json_encode( array( 'type' => 'string' ) ) ); + $recordSchema = AvroSchema::parse( json_encode( array( + 'type' => 'record', + 'name' => 'ut', + 'fields' => array( + array( 'name' => 'id', 'type' => 'int', 'required' => true ), + ), + ) ) ); + $enumSchema = AvroSchema::parse( json_encode( array( + 'type' => 'record', + 'name' => 'ut', + 'fields' => array( + array( 'name' => 'count', 'type' => array( 'int', 'null' ) ), + ), + ) ) ); + + return array( + array( + 'No errors with a simple string serialization', + $stringSchema, 'foobar', array(), + ), + + array( + 'Cannot serialize integer into string', + $stringSchema, 5, 'Expected string, but recieved integer', + ), + + array( + 'Cannot serialize array into string', + $stringSchema, array(), 'Expected string, but recieved array', + ), + + array( + 'allows and ignores extra fields', + $recordSchema, array( 'id' => 4, 'foo' => 'bar' ), array(), + ), + + array( + 'detects missing fields', + $recordSchema, array(), array( 'id' => 'Missing expected field' ), + ), + + array( + 'handles first element in enum', + $enumSchema, array( 'count' => 4 ), array(), + ), + + array( + 'handles second element in enum', + $enumSchema, array( 'count' => null ), array(), + ), + + array( + 'rejects element not in union', + $enumSchema, array( 'count' => 'invalid' ), array( 'count' => array( + 'Expected any one of these to be true', + array( + 'Expected integer, but recieved string', + 'Expected null, but recieved string', + ) + ) ) + ), + ); + } + + /** + * @dataProvider getErrorsProvider + */ + public function testGetErrors( $message, $schema, $datum, $expected ) { + $this->assertEquals( + $expected, + AvroValidator::getErrors( $schema, $datum ), + $message + ); + } +} diff --git a/tests/phpunit/includes/utils/BatchRowUpdateTest.php b/tests/phpunit/includes/utils/BatchRowUpdateTest.php new file mode 100644 index 00000000..a2b35f39 --- /dev/null +++ b/tests/phpunit/includes/utils/BatchRowUpdateTest.php @@ -0,0 +1,243 @@ +<?php + +/** + * Tests for BatchRowUpdate and its components + * + * @group db + */ +class BatchRowUpdateTest extends MediaWikiTestCase { + + public function testWriterBasicFunctionality() { + $db = $this->mockDb(); + $writer = new BatchRowWriter( $db, 'echo_event' ); + + $updates = array( + self::mockUpdate( array( 'something' => 'changed' ) ), + self::mockUpdate( array( 'otherthing' => 'changed' ) ), + self::mockUpdate( array( 'and' => 'something', 'else' => 'changed' ) ), + ); + + $db->expects( $this->exactly( count( $updates ) ) ) + ->method( 'update' ); + + $writer->write( $updates ); + } + + static protected function mockUpdate( array $changes ) { + static $i = 0; + return array( + 'primaryKey' => array( 'event_id' => $i++ ), + 'changes' => $changes, + ); + } + + public function testReaderBasicIterate() { + $db = $this->mockDb(); + $batchSize = 2; + $reader = new BatchRowIterator( $db, 'some_table', 'id_field', $batchSize ); + + $response = $this->genSelectResult( $batchSize, /*numRows*/ 5, function() { + static $i = 0; + return array( 'id_field' => ++$i ); + } ); + $db->expects( $this->exactly( count( $response ) ) ) + ->method( 'select' ) + ->will( $this->consecutivelyReturnFromSelect( $response ) ); + + $pos = 0; + foreach ( $reader as $rows ) { + $this->assertEquals( $response[$pos], $rows, "Testing row in position $pos" ); + $pos++; + } + // -1 is because the final array() marks the end and isnt included + $this->assertEquals( count( $response ) - 1, $pos ); + } + + static public function provider_readerGetPrimaryKey() { + $row = array( + 'id_field' => 42, + 'some_col' => 'dvorak', + 'other_col' => 'samurai', + ); + return array( + + array( + 'Must return single column pk when requested', + array( 'id_field' => 42 ), + $row + ), + + array( + 'Must return multiple column pks when requested', + array( 'id_field' => 42, 'other_col' => 'samurai' ), + $row + ), + + ); + } + + /** + * @dataProvider provider_readerGetPrimaryKey + */ + public function testReaderGetPrimaryKey( $message, array $expected, array $row ) { + $reader = new BatchRowIterator( $this->mockDb(), 'some_table', array_keys( $expected ), 8675309 ); + $this->assertEquals( $expected, $reader->extractPrimaryKeys( (object) $row ), $message ); + } + + static public function provider_readerSetFetchColumns() { + return array( + + array( + 'Must merge primary keys into select conditions', + // Expected column select + array( 'foo', 'bar' ), + // primary keys + array( 'foo' ), + // setFetchColumn + array( 'bar' ) + ), + + array( + 'Must not merge primary keys into the all columns selector', + // Expected column select + array( '*' ), + // primary keys + array( 'foo' ), + // setFetchColumn + array( '*' ), + ), + + array( + 'Must not duplicate primary keys into column selector', + // Expected column select. + // TODO: figure out how to only assert the array_values portion and not the keys + array( 0 => 'foo', 1 => 'bar', 3 => 'baz' ), + // primary keys + array( 'foo', 'bar', ), + // setFetchColumn + array( 'bar', 'baz' ), + ), + ); + } + + /** + * @dataProvider provider_readerSetFetchColumns + */ + public function testReaderSetFetchColumns( $message, array $columns, array $primaryKeys, array $fetchColumns ) { + $db = $this->mockDb(); + $db->expects( $this->once() ) + ->method( 'select' ) + ->with( 'some_table', $columns ) // only testing second parameter of DatabaseBase::select + ->will( $this->returnValue( new ArrayIterator( array() ) ) ); + + $reader = new BatchRowIterator( $db, 'some_table', $primaryKeys, 22 ); + $reader->setFetchColumns( $fetchColumns ); + // triggers first database select + $reader->rewind(); + } + + static public function provider_readerSelectConditions() { + return array( + + array( + "With single primary key must generate id > 'value'", + // Expected second iteration + array( "( id_field > '3' )" ), + // Primary key(s) + 'id_field', + ), + + array( + 'With multiple primary keys the first conditions must use >= and the final condition must use >', + // Expected second iteration + array( "( id_field = '3' AND foo > '103' ) OR ( id_field > '3' )" ), + // Primary key(s) + array( 'id_field', 'foo' ), + ), + + ); + } + + /** + * Slightly hackish to use reflection, but asserting different parameters + * to consecutive calls of DatabaseBase::select in phpunit is error prone + * + * @dataProvider provider_readerSelectConditions + */ + public function testReaderSelectConditionsMultiplePrimaryKeys( $message, $expectedSecondIteration, $primaryKeys, $batchSize = 3 ) { + $results = $this->genSelectResult( $batchSize, $batchSize * 3, function() { + static $i = 0, $j = 100, $k = 1000; + return array( 'id_field' => ++$i, 'foo' => ++$j, 'bar' => ++$k ); + } ); + $db = $this->mockDbConsecutiveSelect( $results ); + + $conditions = array( 'bar' => 42, 'baz' => 'hai' ); + $reader = new BatchRowIterator( $db, 'some_table', $primaryKeys, $batchSize ); + $reader->addConditions( $conditions ); + + $buildConditions = new ReflectionMethod( $reader, 'buildConditions' ); + $buildConditions->setAccessible( true ); + + // On first iteration only the passed conditions must be used + $this->assertEquals( $conditions, $buildConditions->invoke( $reader ), + 'First iteration must return only the conditions passed in addConditions' ); + $reader->rewind(); + + // Second iteration must use the maximum primary key of last set + $this->assertEquals( + $conditions + $expectedSecondIteration, + $buildConditions->invoke( $reader ), + $message + ); + } + + protected function mockDbConsecutiveSelect( array $retvals ) { + $db = $this->mockDb(); + $db->expects( $this->any() ) + ->method( 'select' ) + ->will( $this->consecutivelyReturnFromSelect( $retvals ) ); + $db->expects( $this->any() ) + ->method( 'addQuotes' ) + ->will( $this->returnCallback( function( $value ) { + return "'$value'"; // not real quoting: doesn't matter in test + } ) ); + + return $db; + } + + protected function consecutivelyReturnFromSelect( array $results ) { + $retvals = array(); + foreach ( $results as $rows ) { + // The DatabaseBase::select method returns iterators, so we do too. + $retvals[] = $this->returnValue( new ArrayIterator( $rows ) ); + } + + return call_user_func_array( array( $this, 'onConsecutiveCalls' ), $retvals ); + } + + + protected function genSelectResult( $batchSize, $numRows, $rowGenerator ) { + $res = array(); + for ( $i = 0; $i < $numRows; $i += $batchSize ) { + $rows = array(); + for ( $j = 0; $j < $batchSize && $i + $j < $numRows; $j++ ) { + $rows [] = (object) call_user_func( $rowGenerator ); + } + $res[] = $rows; + } + $res[] = array(); // termination condition requires empty result for last row + return $res; + } + + protected function mockDb() { + // Cant mock from DatabaseType or DatabaseBase, they dont + // have the full gamut of methods + $databaseMysql = $this->getMockBuilder( 'DatabaseMysql' ) + ->disableOriginalConstructor() + ->getMock(); + $databaseMysql->expects( $this->any() ) + ->method( 'isOpen' ) + ->will( $this->returnValue( true ) ); + return $databaseMysql; + } +} diff --git a/tests/phpunit/includes/utils/IPTest.php b/tests/phpunit/includes/utils/IPTest.php index 09c1587d..04b8f486 100644 --- a/tests/phpunit/includes/utils/IPTest.php +++ b/tests/phpunit/includes/utils/IPTest.php @@ -11,29 +11,37 @@ class IPTest extends PHPUnit_Framework_TestCase { /** - * not sure it should be tested with boolean false. hashar 20100924 * @covers IP::isIPAddress + * @dataProvider provideInvalidIPs */ - public function testisIPAddress() { - $this->assertFalse( IP::isIPAddress( false ), 'Boolean false is not an IP' ); - $this->assertFalse( IP::isIPAddress( true ), 'Boolean true is not an IP' ); - $this->assertFalse( IP::isIPAddress( "" ), 'Empty string is not an IP' ); - $this->assertFalse( IP::isIPAddress( 'abc' ), 'Garbage IP string' ); - $this->assertFalse( IP::isIPAddress( ':' ), 'Single ":" is not an IP' ); - $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::1' ), 'IPv6 with a double :: occurrence' ); - $this->assertFalse( - IP::isIPAddress( '2001:0DB8::A:1::' ), - 'IPv6 with a double :: occurrence, last at end' - ); - $this->assertFalse( - IP::isIPAddress( '::2001:0DB8::5:1' ), - 'IPv6 with a double :: occurrence, firt at beginning' + public function isNotIPAddress( $val, $desc ) { + $this->assertFalse( IP::isIPAddress( $val ), $desc ); + } + + /** + * Provide a list of things that aren't IP addresses + */ + public function provideInvalidIPs() { + return array( + array( false, 'Boolean false is not an IP' ), + array( true, 'Boolean true is not an IP' ), + array( '', 'Empty string is not an IP' ), + array( 'abc', 'Garbage IP string' ), + array( ':', 'Single ":" is not an IP' ), + array( '2001:0DB8::A:1::1', 'IPv6 with a double :: occurrence' ), + array( '2001:0DB8::A:1::', 'IPv6 with a double :: occurrence, last at end' ), + array( '::2001:0DB8::5:1', 'IPv6 with a double :: occurrence, firt at beginning' ), + array( '124.24.52', 'IPv4 not enough quads' ), + array( '24.324.52.13', 'IPv4 out of range' ), + array( '.24.52.13', 'IPv4 starts with period' ), + array( 'fc:100:300', 'IPv6 with only 3 words' ), ); - $this->assertFalse( IP::isIPAddress( '124.24.52' ), 'IPv4 not enough quads' ); - $this->assertFalse( IP::isIPAddress( '24.324.52.13' ), 'IPv4 out of range' ); - $this->assertFalse( IP::isIPAddress( '.24.52.13' ), 'IPv4 starts with period' ); - $this->assertFalse( IP::isIPAddress( 'fc:100:300' ), 'IPv6 with only 3 words' ); + } + /** + * @covers IP::isIPAddress + */ + public function testisIPAddress() { $this->assertTrue( IP::isIPAddress( '::' ), 'RFC 4291 IPv6 Unspecified Address' ); $this->assertTrue( IP::isIPAddress( '::1' ), 'RFC 4291 IPv6 Loopback Address' ); $this->assertTrue( IP::isIPAddress( '74.24.52.13/20', 'IPv4 range' ) ); @@ -107,20 +115,42 @@ class IPTest extends PHPUnit_Framework_TestCase { /** * @covers IP::isIPv4 + * @dataProvider provideInvalidIPv4Addresses + */ + public function testisNotIPv4( $bogusIP, $desc ) { + $this->assertFalse( IP::isIPv4( $bogusIP ), $desc ); + } + + public function provideInvalidIPv4Addresses() { + return array( + array( false, 'Boolean false is not an IP' ), + array( true, 'Boolean true is not an IP' ), + array( '', 'Empty string is not an IP' ), + array( 'abc', 'Letters are not an IP' ), + array( ':', 'A colon is not an IP' ), + array( '124.24.52', 'IPv4 not enough quads' ), + array( '24.324.52.13', 'IPv4 out of range' ), + array( '.24.52.13', 'IPv4 starts with period' ), + ); + } + + /** + * @covers IP::isIPv4 + * @dataProvider provideValidIPv4Address */ - public function testisIPv4() { - $this->assertFalse( IP::isIPv4( false ), 'Boolean false is not an IP' ); - $this->assertFalse( IP::isIPv4( true ), 'Boolean true is not an IP' ); - $this->assertFalse( IP::isIPv4( "" ), 'Empty string is not an IP' ); - $this->assertFalse( IP::isIPv4( 'abc' ) ); - $this->assertFalse( IP::isIPv4( ':' ) ); - $this->assertFalse( IP::isIPv4( '124.24.52' ), 'IPv4 not enough quads' ); - $this->assertFalse( IP::isIPv4( '24.324.52.13' ), 'IPv4 out of range' ); - $this->assertFalse( IP::isIPv4( '.24.52.13' ), 'IPv4 starts with period' ); + public function testIsIPv4( $ip, $desc ) { + $this->assertTrue( IP::isIPv4( $ip ), $desc ); + } - $this->assertTrue( IP::isIPv4( '124.24.52.13' ) ); - $this->assertTrue( IP::isIPv4( '1.24.52.13' ) ); - $this->assertTrue( IP::isIPv4( '74.24.52.13/20', 'IPv4 range' ) ); + /** + * Provide some IPv4 addresses and ranges + */ + public function provideValidIPv4Address() { + return array( + array( '124.24.52.13', 'Valid IPv4 address' ), + array( '1.24.52.13', 'Another valid IPv4 address' ), + array( '74.24.52.13/20', 'An IPv4 range' ), + ); } /** @@ -224,49 +254,56 @@ class IPTest extends PHPUnit_Framework_TestCase { } /** - * @covers IP::isValidBlock + * Provide some valid IP blocks */ - public function testValidBlocks() { - $valid = array( - '116.17.184.5/32', - '0.17.184.5/30', - '16.17.184.1/24', - '30.242.52.14/1', - '10.232.52.13/8', - '30.242.52.14/0', - '::e:f:2001/96', - '::c:f:2001/128', - '::10:f:2001/70', - '::fe:f:2001/1', - '::6d:f:2001/8', - '::fe:f:2001/0', + public function provideValidBlocks() { + return array( + array( '116.17.184.5/32' ), + array( '0.17.184.5/30' ), + array( '16.17.184.1/24' ), + array( '30.242.52.14/1' ), + array( '10.232.52.13/8' ), + array( '30.242.52.14/0' ), + array( '::e:f:2001/96' ), + array( '::c:f:2001/128' ), + array( '::10:f:2001/70' ), + array( '::fe:f:2001/1' ), + array( '::6d:f:2001/8' ), + array( '::fe:f:2001/0' ), ); - foreach ( $valid as $i ) { - $this->assertTrue( IP::isValidBlock( $i ), "$i is a valid IP block" ); - } } /** * @covers IP::isValidBlock + * @dataProvider provideValidBlocks */ - public function testInvalidBlocks() { - $invalid = array( - '116.17.184.5/33', - '0.17.184.5/130', - '16.17.184.1/-1', - '10.232.52.13/*', - '7.232.52.13/ab', - '11.232.52.13/', - '::e:f:2001/129', - '::c:f:2001/228', - '::10:f:2001/-1', - '::6d:f:2001/*', - '::86:f:2001/ab', - '::23:f:2001/', + public function testValidBlocks( $block ) { + $this->assertTrue( IP::isValidBlock( $block ), "$block is a valid IP block" ); + } + + /** + * @covers IP::isValidBlock + * @dataProvider provideInvalidBlocks + */ + public function testInvalidBlocks( $invalid ) { + $this->assertFalse( IP::isValidBlock( $invalid ), "$invalid is not a valid IP block" ); + } + + public function provideInvalidBlocks() { + return array( + array( '116.17.184.5/33' ), + array( '0.17.184.5/130' ), + array( '16.17.184.1/-1' ), + array( '10.232.52.13/*' ), + array( '7.232.52.13/ab' ), + array( '11.232.52.13/' ), + array( '::e:f:2001/129' ), + array( '::c:f:2001/228' ), + array( '::10:f:2001/-1' ), + array( '::6d:f:2001/*' ), + array( '::86:f:2001/ab' ), + array( '::23:f:2001/' ), ); - foreach ( $invalid as $i ) { - $this->assertFalse( IP::isValidBlock( $i ), "$i is not a valid IP block" ); - } } /** @@ -310,16 +347,31 @@ class IPTest extends PHPUnit_Framework_TestCase { /** * @covers IP::isPublic + * @dataProvider provideIsPublic */ - public function testPrivateIPs() { - $private = array( 'fc00::3', 'fc00::ff', '::1', '10.0.0.1', '172.16.0.1', '192.168.0.1' ); - foreach ( $private as $p ) { - $this->assertFalse( IP::isPublic( $p ), "$p is not a public IP address" ); - } - $public = array( '2001:5c0:1000:a::133', 'fc::3', '00FC::' ); - foreach ( $public as $p ) { - $this->assertTrue( IP::isPublic( $p ), "$p is a public IP address" ); - } + public function testIsPublic( $expected, $input ) { + $result = IP::isPublic( $input ); + $this->assertEquals( $expected, $result ); + } + + /** + * Provider for IP::testIsPublic() + */ + public static function provideIsPublic() { + return array( + array( false, 'fc00::3' ), # RFC 4193 (local) + array( false, 'fc00::ff' ), # RFC 4193 (local) + array( false, '127.1.2.3' ), # loopback + array( false, '::1' ), # loopback + array( false, 'fe80::1' ), # link-local + array( false, '169.254.1.1' ), # link-local + array( false, '10.0.0.1' ), # RFC 1918 (private) + array( false, '172.16.0.1' ), # RFC 1918 (private) + array( false, '192.168.0.1' ), # RFC 1918 (private) + array( true, '2001:5c0:1000:a::133' ), # public + array( true, 'fc::3' ), # public + array( true, '00FC::' ) # public + ); } // Private wrapper used to test CIDR Parsing. @@ -336,40 +388,55 @@ class IPTest extends PHPUnit_Framework_TestCase { /** * @covers IP::hexToQuad + * @dataProvider provideIPsAndHexes */ - public function testHexToQuad() { - $this->assertEquals( '0.0.0.1', IP::hexToQuad( '00000001' ) ); - $this->assertEquals( '255.0.0.0', IP::hexToQuad( 'FF000000' ) ); - $this->assertEquals( '255.255.255.255', IP::hexToQuad( 'FFFFFFFF' ) ); - $this->assertEquals( '10.188.222.255', IP::hexToQuad( '0ABCDEFF' ) ); - // hex not left-padded... - $this->assertEquals( '0.0.0.0', IP::hexToQuad( '0' ) ); - $this->assertEquals( '0.0.0.1', IP::hexToQuad( '1' ) ); - $this->assertEquals( '0.0.0.255', IP::hexToQuad( 'FF' ) ); - $this->assertEquals( '0.0.255.0', IP::hexToQuad( 'FF00' ) ); + public function testHexToQuad( $ip, $hex ) { + $this->assertEquals( $ip, IP::hexToQuad( $hex ) ); + } + + /** + * Provide some IP addresses and their equivalent hex representations + */ + public function provideIPsandHexes() { + return array( + array( '0.0.0.1', '00000001' ), + array( '255.0.0.0', 'FF000000' ), + array( '255.255.255.255', 'FFFFFFFF' ), + array( '10.188.222.255', '0ABCDEFF' ), + // hex not left-padded... + array( '0.0.0.0', '0' ), + array( '0.0.0.1', '1' ), + array( '0.0.0.255', 'FF' ), + array( '0.0.255.0', 'FF00' ), + ); } /** * @covers IP::hexToOctet + * @dataProvider provideOctetsAndHexes */ - public function testHexToOctet() { - $this->assertEquals( '0:0:0:0:0:0:0:1', - IP::hexToOctet( '00000000000000000000000000000001' ) ); - $this->assertEquals( '0:0:0:0:0:0:FF:3', - IP::hexToOctet( '00000000000000000000000000FF0003' ) ); - $this->assertEquals( '0:0:0:0:0:0:FF00:6', - IP::hexToOctet( '000000000000000000000000FF000006' ) ); - $this->assertEquals( '0:0:0:0:0:0:FCCF:FAFF', - IP::hexToOctet( '000000000000000000000000FCCFFAFF' ) ); - $this->assertEquals( 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', - IP::hexToOctet( 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ) ); - // hex not left-padded... - $this->assertEquals( '0:0:0:0:0:0:0:0', IP::hexToOctet( '0' ) ); - $this->assertEquals( '0:0:0:0:0:0:0:1', IP::hexToOctet( '1' ) ); - $this->assertEquals( '0:0:0:0:0:0:0:FF', IP::hexToOctet( 'FF' ) ); - $this->assertEquals( '0:0:0:0:0:0:0:FFD0', IP::hexToOctet( 'FFD0' ) ); - $this->assertEquals( '0:0:0:0:0:0:FA00:0', IP::hexToOctet( 'FA000000' ) ); - $this->assertEquals( '0:0:0:0:0:0:FCCF:FAFF', IP::hexToOctet( 'FCCFFAFF' ) ); + public function testHexToOctet( $octet, $hex ) { + $this->assertEquals( $octet, IP::hexToOctet( $hex ) ); + } + + /** + * Provide some hex and octet representations of the same IPs + */ + public function provideOctetsAndHexes() { + return array( + array( '0:0:0:0:0:0:0:1', '00000000000000000000000000000001' ), + array( '0:0:0:0:0:0:FF:3', '00000000000000000000000000FF0003' ), + array( '0:0:0:0:0:0:FF00:6', '000000000000000000000000FF000006' ), + array( '0:0:0:0:0:0:FCCF:FAFF', '000000000000000000000000FCCFFAFF' ), + array( 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ), + // hex not left-padded... + array( '0:0:0:0:0:0:0:0', '0' ), + array( '0:0:0:0:0:0:0:1', '1' ), + array( '0:0:0:0:0:0:0:FF', 'FF' ), + array( '0:0:0:0:0:0:0:FFD0', 'FFD0' ), + array( '0:0:0:0:0:0:FA00:0', 'FA000000' ), + array( '0:0:0:0:0:0:FCCF:FAFF', 'FCCFFAFF' ), + ); } /** diff --git a/tests/phpunit/includes/utils/MWFunctionTest.php b/tests/phpunit/includes/utils/MWFunctionTest.php deleted file mode 100644 index f4d17999..00000000 --- a/tests/phpunit/includes/utils/MWFunctionTest.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php - -/** - * @covers MWFunction - */ -class MWFunctionTest extends MediaWikiTestCase { - public function testNewObjFunction() { - $arg1 = 'Foo'; - $arg2 = 'Bar'; - $arg3 = array( 'Baz' ); - $arg4 = new ExampleObject; - - $args = array( $arg1, $arg2, $arg3, $arg4 ); - - $newObject = new MWBlankClass( $arg1, $arg2, $arg3, $arg4 ); - $this->hideDeprecated( 'MWFunction::newObj' ); - $this->assertEquals( - MWFunction::newObj( 'MWBlankClass', $args )->args, - $newObject->args - ); - } -} - -class MWBlankClass { - - public $args = array(); - - function __construct( $arg1, $arg2, $arg3, $arg4 ) { - $this->args = array( $arg1, $arg2, $arg3, $arg4 ); - } -} - -class ExampleObject { -} diff --git a/tests/phpunit/includes/utils/UIDGeneratorTest.php b/tests/phpunit/includes/utils/UIDGeneratorTest.php index 0e11ccad..fedcc762 100644 --- a/tests/phpunit/includes/utils/UIDGeneratorTest.php +++ b/tests/phpunit/includes/utils/UIDGeneratorTest.php @@ -1,6 +1,6 @@ <?php -class UIDGeneratorTest extends MediaWikiTestCase { +class UIDGeneratorTest extends PHPUnit_Framework_TestCase { protected function tearDown() { // Bug: 44850 @@ -28,7 +28,7 @@ class UIDGeneratorTest extends MediaWikiTestCase { $lastId = array_shift( $ids ); - $this->assertArrayEquals( array_unique( $ids ), $ids, "All generated IDs are unique." ); + $this->assertSame( array_unique( $ids ), $ids, "All generated IDs are unique." ); foreach ( $ids as $id ) { $id_bin = wfBaseConvert( $id, 10, 2 ); @@ -105,8 +105,8 @@ class UIDGeneratorTest extends MediaWikiTestCase { $id1 = UIDGenerator::newSequentialPerNodeID( 'test', 32 ); $id2 = UIDGenerator::newSequentialPerNodeID( 'test', 32 ); - $this->assertType( 'float', $id1, "ID returned as float" ); - $this->assertType( 'float', $id2, "ID returned as float" ); + $this->assertInternalType( 'float', $id1, "ID returned as float" ); + $this->assertInternalType( 'float', $id2, "ID returned as float" ); $this->assertGreaterThan( 0, $id1, "ID greater than 1" ); $this->assertGreaterThan( $id1, $id2, "IDs increasing in value" ); } @@ -118,7 +118,7 @@ class UIDGeneratorTest extends MediaWikiTestCase { $ids = UIDGenerator::newSequentialPerNodeIDs( 'test', 32, 5 ); $lastId = null; foreach ( $ids as $id ) { - $this->assertType( 'float', $id, "ID returned as float" ); + $this->assertInternalType( 'float', $id, "ID returned as float" ); $this->assertGreaterThan( 0, $id, "ID greater than 1" ); if ( $lastId ) { $this->assertGreaterThan( $lastId, $id, "IDs increasing in value" ); diff --git a/tests/phpunit/languages/LanguageTest.php b/tests/phpunit/languages/LanguageTest.php index cff2e8fd..4fca0023 100644 --- a/tests/phpunit/languages/LanguageTest.php +++ b/tests/phpunit/languages/LanguageTest.php @@ -1030,7 +1030,7 @@ class LanguageTest extends LanguageClassesTestCase { return array( array( 0, - "0 B", + "0 bytes", "Zero bytes" ), array( @@ -1046,7 +1046,7 @@ class LanguageTest extends LanguageClassesTestCase { array( 1024 * 1024 * 1024, "1 GB", - "1 gigabytes" + "1 gigabyte" ), array( pow( 1024, 4 ), @@ -1413,6 +1413,77 @@ class LanguageTest extends LanguageClassesTestCase { } /** + * @dataProvider provideHebrewNumeralsData + * @covers Language::hebrewNumeral + */ + public function testHebrewNumeral( $num, $numerals ) { + $this->assertEquals( + $numerals, + Language::hebrewNumeral( $num ), + "hebrewNumeral('$num')" + ); + } + + public static function provideHebrewNumeralsData() { + return array( + array( -1, -1 ), + array( 0, 0 ), + array( 1, "א'" ), + array( 2, "ב'" ), + array( 3, "ג'" ), + array( 4, "ד'" ), + array( 5, "ה'" ), + array( 6, "ו'" ), + array( 7, "ז'" ), + array( 8, "ח'" ), + array( 9, "ט'" ), + array( 10, "י'" ), + array( 11, 'י"א' ), + array( 14, 'י"ד' ), + array( 15, 'ט"ו' ), + array( 16, 'ט"ז' ), + array( 17, 'י"ז' ), + array( 20, "כ'" ), + array( 21, 'כ"א' ), + array( 30, "ל'" ), + array( 40, "מ'" ), + array( 50, "נ'" ), + array( 60, "ס'" ), + array( 70, "ע'" ), + array( 80, "פ'" ), + array( 90, "צ'" ), + array( 99, 'צ"ט' ), + array( 100, "ק'" ), + array( 101, 'ק"א' ), + array( 110, 'ק"י' ), + array( 200, "ר'" ), + array( 300, "ש'" ), + array( 400, "ת'" ), + array( 500, 'ת"ק' ), + array( 800, 'ת"ת' ), + array( 1000, "א' אלף" ), + array( 1001, "א'א'" ), + array( 1012, "א'י\"ב" ), + array( 1020, "א'ך'" ), + array( 1030, "א'ל'" ), + array( 1081, "א'פ\"א" ), + array( 2000, "ב' אלפים" ), + array( 2016, "ב'ט\"ז" ), + array( 3000, "ג' אלפים" ), + array( 4000, "ד' אלפים" ), + array( 4904, "ד'תתק\"ד" ), + array( 5000, "ה' אלפים" ), + array( 5680, "ה'תר\"ף" ), + array( 5690, "ה'תר\"ץ" ), + array( 5708, "ה'תש\"ח" ), + array( 5720, "ה'תש\"ך" ), + array( 5740, "ה'תש\"ם" ), + array( 5750, "ה'תש\"ן" ), + array( 5775, "ה'תשע\"ה" ), + ); + } + + /** * @dataProvider providePluralData * @covers Language::convertPlural */ @@ -1458,6 +1529,31 @@ class LanguageTest extends LanguageClassesTestCase { } /** + * @covers Language::embedBidi() + */ + public function testEmbedBidi() { + $lre = "\xE2\x80\xAA"; // U+202A LEFT-TO-RIGHT EMBEDDING + $rle = "\xE2\x80\xAB"; // U+202B RIGHT-TO-LEFT EMBEDDING + $pdf = "\xE2\x80\xAC"; // U+202C POP DIRECTIONAL FORMATTING + $lang = $this->getLang(); + $this->assertEquals( + '123', + $lang->embedBidi( '123' ), + 'embedBidi with neutral argument' + ); + $this->assertEquals( + $lre . 'Ben_(WMF)' . $pdf, + $lang->embedBidi( 'Ben_(WMF)' ), + 'embedBidi with LTR argument' + ); + $this->assertEquals( + $rle . 'יהודי (מנוחין)' . $pdf, + $lang->embedBidi( 'יהודי (מנוחין)' ), + 'embedBidi with RTL argument' + ); + } + + /** * @covers Language::translateBlockExpiry() * @dataProvider provideTranslateBlockExpiry */ diff --git a/tests/phpunit/languages/classes/LanguageArqTest.php b/tests/phpunit/languages/classes/LanguageArqTest.php index 3fa56d78..71e05838 100644 --- a/tests/phpunit/languages/classes/LanguageArqTest.php +++ b/tests/phpunit/languages/classes/LanguageArqTest.php @@ -18,7 +18,7 @@ class LanguageArqTest extends LanguageClassesTestCase { public static function provideNumber() { return array( - array( '1.234.567', '1234567'), + array( '1.234.567', '1234567' ), array( '-12,89', -12.89 ), ); } diff --git a/tests/phpunit/maintenance/backupTextPassTest.php b/tests/phpunit/maintenance/backupTextPassTest.php index a5ef7624..c216864e 100644 --- a/tests/phpunit/maintenance/backupTextPassTest.php +++ b/tests/phpunit/maintenance/backupTextPassTest.php @@ -418,7 +418,10 @@ class TextPassDumperDatabaseTest extends DumpTestCase { } /** + * Broken per T70653. + * * @group large + * @group Broken */ function testCheckpointPlain() { $this->checkpointHelper(); @@ -434,7 +437,10 @@ class TextPassDumperDatabaseTest extends DumpTestCase { * PHP extensions, we go for gzip instead, which triggers the same relevant code * paths while still being testable on more systems. * + * Broken per T70653. + * * @group large + * @group Broken */ function testCheckpointGzip() { $this->checkHasGzip(); @@ -630,7 +636,7 @@ class TextPassDumperDatabaselessTest extends MediaWikiLangTestCase { */ function testBufferSizeSetting( $expected, $size, $msg ) { $dumper = new TextPassDumperAccessor( array( "--buffersize=" . $size ) ); - $this->assertEquals( $expected, $dumper->getBufferSize(), $msg); + $this->assertEquals( $expected, $dumper->getBufferSize(), $msg ); } /** diff --git a/tests/phpunit/mocks/MockWebRequest.php b/tests/phpunit/mocks/MockWebRequest.php new file mode 100644 index 00000000..3ac5bfb0 --- /dev/null +++ b/tests/phpunit/mocks/MockWebRequest.php @@ -0,0 +1,26 @@ +<?php + +/** + * A mock WebRequest. + * + * If the code under test accesses the response via the request (see + * WebRequest#response), then you might be able to use this mock to simplify + * your tests. + */ +class MockWebRequest extends WebRequest +{ + /** + * @var WebResponse + */ + protected $response; + + public function __construct( WebResponse $response ) { + parent::__construct(); + + $this->response = $response; + } + + public function response() { + return $this->response; + } +} diff --git a/tests/phpunit/mocks/content/DummyContentForTesting.php b/tests/phpunit/mocks/content/DummyContentForTesting.php new file mode 100644 index 00000000..0c69027d --- /dev/null +++ b/tests/phpunit/mocks/content/DummyContentForTesting.php @@ -0,0 +1,121 @@ +<?php + +class DummyContentForTesting extends AbstractContent { + + public function __construct( $data ) { + parent::__construct( "testing" ); + + $this->data = $data; + } + + public function serialize( $format = null ) { + return serialize( $this->data ); + } + + /** + * @return string A string representing the content in a way useful for + * building a full text search index. If no useful representation exists, + * this method returns an empty string. + */ + public function getTextForSearchIndex() { + return ''; + } + + /** + * @return string|bool The wikitext to include when another page includes this content, + * or false if the content is not includable in a wikitext page. + */ + public function getWikitextForTransclusion() { + return false; + } + + /** + * Returns a textual representation of the content suitable for use in edit + * summaries and log messages. + * + * @param int $maxlength Maximum length of the summary text. + * @return string The summary text. + */ + public function getTextForSummary( $maxlength = 250 ) { + return ''; + } + + /** + * Returns native represenation of the data. Interpretation depends on the data model used, + * as given by getDataModel(). + * + * @return mixed The native representation of the content. Could be a string, a nested array + * structure, an object, a binary blob... anything, really. + */ + public function getNativeData() { + return $this->data; + } + + /** + * returns the content's nominal size in bogo-bytes. + * + * @return int + */ + public function getSize() { + return strlen( $this->data ); + } + + /** + * Return a copy of this Content object. The following must be true for the object returned + * if $copy = $original->copy() + * + * * get_class($original) === get_class($copy) + * * $original->getModel() === $copy->getModel() + * * $original->equals( $copy ) + * + * If and only if the Content object is imutable, the copy() method can and should + * return $this. That is, $copy === $original may be true, but only for imutable content + * objects. + * + * @return Content A copy of this object + */ + public function copy() { + return $this; + } + + /** + * Returns true if this content is countable as a "real" wiki page, provided + * that it's also in a countable location (e.g. a current revision in the main namespace). + * + * @param bool $hasLinks If it is known whether this content contains links, + * provide this information here, to avoid redundant parsing to find out. + * @return bool + */ + public function isCountable( $hasLinks = null ) { + return false; + } + + /** + * @param Title $title + * @param int $revId Unused. + * @param null|ParserOptions $options + * @param bool $generateHtml Whether to generate Html (default: true). If false, the result + * of calling getText() on the ParserOutput object returned by this method is undefined. + * + * @return ParserOutput + */ + public function getParserOutput( Title $title, $revId = null, + ParserOptions $options = null, $generateHtml = true + ) { + return new ParserOutput( $this->getNativeData() ); + } + + /** + * @see AbstractContent::fillParserOutput() + * + * @param Title $title Context title for parsing + * @param int|null $revId Revision ID (for {{REVISIONID}}) + * @param ParserOptions $options Parser options + * @param bool $generateHtml Whether or not to generate HTML + * @param ParserOutput &$output The output object to fill (reference). + */ + protected function fillParserOutput( Title $title, $revId, + ParserOptions $options, $generateHtml, ParserOutput &$output ) { + $output = new ParserOutput( $this->getNativeData() ); + } +} diff --git a/tests/phpunit/mocks/content/DummyContentHandlerForTesting.php b/tests/phpunit/mocks/content/DummyContentHandlerForTesting.php new file mode 100644 index 00000000..fd253f21 --- /dev/null +++ b/tests/phpunit/mocks/content/DummyContentHandlerForTesting.php @@ -0,0 +1,42 @@ +<?php + +class DummyContentHandlerForTesting extends ContentHandler { + + public function __construct( $dataModel ) { + parent::__construct( $dataModel, array( "testing" ) ); + } + + /** + * @see ContentHandler::serializeContent + * + * @param Content $content + * @param string $format + * + * @return string + */ + public function serializeContent( Content $content, $format = null ) { + return $content->serialize(); + } + + /** + * @see ContentHandler::unserializeContent + * + * @param string $blob + * @param string $format Unused. + * + * @return Content + */ + public function unserializeContent( $blob, $format = null ) { + $d = unserialize( $blob ); + + return new DummyContentForTesting( $d ); + } + + /** + * Creates an empty Content object of the type supported by this ContentHandler. + * + */ + public function makeEmptyContent() { + return new DummyContentForTesting( '' ); + } +} diff --git a/tests/phpunit/mocks/content/DummyNonTextContent.php b/tests/phpunit/mocks/content/DummyNonTextContent.php new file mode 100644 index 00000000..889efb71 --- /dev/null +++ b/tests/phpunit/mocks/content/DummyNonTextContent.php @@ -0,0 +1,121 @@ +<?php + +class DummyNonTextContent extends AbstractContent { + + public function __construct( $data ) { + parent::__construct( "testing-nontext" ); + + $this->data = $data; + } + + public function serialize( $format = null ) { + return serialize( $this->data ); + } + + /** + * @return string A string representing the content in a way useful for + * building a full text search index. If no useful representation exists, + * this method returns an empty string. + */ + public function getTextForSearchIndex() { + return ''; + } + + /** + * @return string|bool The wikitext to include when another page includes this content, + * or false if the content is not includable in a wikitext page. + */ + public function getWikitextForTransclusion() { + return false; + } + + /** + * Returns a textual representation of the content suitable for use in edit + * summaries and log messages. + * + * @param int $maxlength Maximum length of the summary text. + * @return string The summary text. + */ + public function getTextForSummary( $maxlength = 250 ) { + return ''; + } + + /** + * Returns native represenation of the data. Interpretation depends on the data model used, + * as given by getDataModel(). + * + * @return mixed The native representation of the content. Could be a string, a nested array + * structure, an object, a binary blob... anything, really. + */ + public function getNativeData() { + return $this->data; + } + + /** + * returns the content's nominal size in bogo-bytes. + * + * @return int + */ + public function getSize() { + return strlen( $this->data ); + } + + /** + * Return a copy of this Content object. The following must be true for the object returned + * if $copy = $original->copy() + * + * * get_class($original) === get_class($copy) + * * $original->getModel() === $copy->getModel() + * * $original->equals( $copy ) + * + * If and only if the Content object is imutable, the copy() method can and should + * return $this. That is, $copy === $original may be true, but only for imutable content + * objects. + * + * @return Content A copy of this object + */ + public function copy() { + return $this; + } + + /** + * Returns true if this content is countable as a "real" wiki page, provided + * that it's also in a countable location (e.g. a current revision in the main namespace). + * + * @param bool $hasLinks If it is known whether this content contains links, + * provide this information here, to avoid redundant parsing to find out. + * @return bool + */ + public function isCountable( $hasLinks = null ) { + return false; + } + + /** + * @param Title $title + * @param int $revId Unused. + * @param null|ParserOptions $options + * @param bool $generateHtml Whether to generate Html (default: true). If false, the result + * of calling getText() on the ParserOutput object returned by this method is undefined. + * + * @return ParserOutput + */ + public function getParserOutput( Title $title, $revId = null, + ParserOptions $options = null, $generateHtml = true + ) { + return new ParserOutput( $this->getNativeData() ); + } + + /** + * @see AbstractContent::fillParserOutput() + * + * @param Title $title Context title for parsing + * @param int|null $revId Revision ID (for {{REVISIONID}}) + * @param ParserOptions $options Parser options + * @param bool $generateHtml Whether or not to generate HTML + * @param ParserOutput &$output The output object to fill (reference). + */ + protected function fillParserOutput( Title $title, $revId, + ParserOptions $options, $generateHtml, ParserOutput &$output ) { + $output = new ParserOutput( $this->getNativeData() ); + } +} diff --git a/tests/phpunit/mocks/content/DummyNonTextContentHandler.php b/tests/phpunit/mocks/content/DummyNonTextContentHandler.php new file mode 100644 index 00000000..6995ae78 --- /dev/null +++ b/tests/phpunit/mocks/content/DummyNonTextContentHandler.php @@ -0,0 +1,46 @@ +<?php + +class DummyNonTextContentHandler extends DummyContentHandlerForTesting { + + public function __construct( $dataModel ) { + parent::__construct( $dataModel, array( "testing-nontext" ) ); + } + + /** + * @see ContentHandler::serializeContent + * + * @param Content $content + * @param string $format + * + * @return string + */ + public function serializeContent( Content $content, $format = null ) { + return $content->serialize(); + } + + /** + * @see ContentHandler::unserializeContent + * + * @param string $blob + * @param string $format Unused. + * + * @return Content + */ + public function unserializeContent( $blob, $format = null ) { + $d = unserialize( $blob ); + + return new DummyNonTextContent( $d ); + } + + /** + * Creates an empty Content object of the type supported by this ContentHandler. + */ + public function makeEmptyContent() { + return new DummyNonTextContent( '' ); + } + + public function supportsDirectApiEditing() { + return true; + } + +} diff --git a/tests/phpunit/phpunit.php b/tests/phpunit/phpunit.php index e59b5063..587d6d0c 100644 --- a/tests/phpunit/phpunit.php +++ b/tests/phpunit/phpunit.php @@ -55,7 +55,7 @@ class PHPUnitMaintClass extends Maintenance { public function finalSetup() { parent::finalSetup(); - global $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType; + global $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType, $wgMainWANCache; global $wgLanguageConverterCacheType, $wgUseDatabaseMessages; global $wgLocaltimezone, $wgLocalisationCacheConf; global $wgDevelopmentWarnings; @@ -67,6 +67,7 @@ class PHPUnitMaintClass extends Maintenance { $wgDevelopmentWarnings = true; $wgMainCacheType = CACHE_NONE; + $wgMainWANCache = CACHE_NONE; $wgMessageCacheType = CACHE_NONE; $wgParserCacheType = CACHE_NONE; $wgLanguageConverterCacheType = CACHE_NONE; @@ -215,25 +216,35 @@ if ( version_compare( PHP_VERSION, '5.4.0', '<' ) ) { $ok = false; -foreach ( array( - stream_resolve_include_path( 'phpunit.phar' ), - 'PHPUnit/Runner/Version.php', - 'PHPUnit/Autoload.php' -) as $includePath ) { - @include_once $includePath; - if ( class_exists( 'PHPUnit_TextUI_Command' ) ) { - $ok = true; - break; +if ( class_exists( 'PHPUnit_TextUI_Command' ) ) { + echo "PHPUnit already present\n"; + $ok = true; +} else { + foreach ( array( + stream_resolve_include_path( 'phpunit.phar' ), + 'PHPUnit/Runner/Version.php', + 'PHPUnit/Autoload.php' + ) as $includePath ) { + // @codingStandardsIgnoreStart + @include_once $includePath; + // @codingStandardsIgnoreEnd + if ( class_exists( 'PHPUnit_TextUI_Command' ) ) { + $ok = true; + echo "Using PHPUnit from $includePath\n"; + break; + } } } if ( !$ok ) { - die( "Couldn't find a usable PHPUnit.\n" ); + echo "Couldn't find a usable PHPUnit.\n"; + exit( 1 ); } $puVersion = PHPUnit_Runner_Version::id(); if ( $puVersion !== '@package_version@' && version_compare( $puVersion, '3.7.0', '<' ) ) { - die( "PHPUnit 3.7.0 or later required; you have {$puVersion}.\n" ); + echo "PHPUnit 3.7.0 or later required; you have {$puVersion}.\n"; + exit( 1 ); } PHPUnit_TextUI_Command::main(); diff --git a/tests/phpunit/structure/AutoLoaderTest.php b/tests/phpunit/structure/AutoLoaderTest.php index cde6547a..8674329a 100644 --- a/tests/phpunit/structure/AutoLoaderTest.php +++ b/tests/phpunit/structure/AutoLoaderTest.php @@ -58,9 +58,9 @@ class AutoLoaderTest extends MediaWikiTestCase { continue; } - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $contents = file_get_contents( $filePath ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $contents === false ) { $actual[$class] = "[couldn't read file '$filePath']"; diff --git a/tests/phpunit/structure/AvailableRightsTest.php b/tests/phpunit/structure/AvailableRightsTest.php index 51d31aaa..ccf5393b 100644 --- a/tests/phpunit/structure/AvailableRightsTest.php +++ b/tests/phpunit/structure/AvailableRightsTest.php @@ -19,11 +19,11 @@ class AvailableRightsTest extends PHPUnit_Framework_TestCase { $rights = User::getAllRights(); - foreach( $wgGroupPermissions as $permissions ) { + foreach ( $wgGroupPermissions as $permissions ) { $rights = array_merge( $rights, array_keys( $permissions ) ); } - foreach( $wgRevokePermissions as $permissions ) { + foreach ( $wgRevokePermissions as $permissions ) { $rights = array_merge( $rights, array_keys( $permissions ) ); } diff --git a/tests/phpunit/structure/ResourcesTest.php b/tests/phpunit/structure/ResourcesTest.php index d2b699d9..eefc926a 100644 --- a/tests/phpunit/structure/ResourcesTest.php +++ b/tests/phpunit/structure/ResourcesTest.php @@ -36,9 +36,20 @@ class ResourcesTest extends MediaWikiTestCase { ); } + public function testVersionHash() { + $data = self::getAllModules(); + foreach ( $data['modules'] as $moduleName => $module ) { + $version = $module->getVersionHash( $data['context'] ); + $this->assertEquals( 8, strlen( $version ), "$moduleName must use ResourceLoader::makeHash" ); + } + } + /** * Verify that nothing explicitly depends on the 'jquery' and 'mediawiki' modules. * They are always loaded, depending on them is unsupported and leads to unexpected behaviour. + * TODO Modules can dynamically choose dependencies based on context. This method does not + * test such dependencies. The same goes for testMissingDependencies() and + * testUnsatisfiableDependencies(). */ public function testIllegalDependencies() { $data = self::getAllModules(); @@ -49,7 +60,7 @@ class ResourcesTest extends MediaWikiTestCase { foreach ( $illegalDeps as $illegalDep ) { $this->assertNotContains( $illegalDep, - $module->getDependencies(), + $module->getDependencies( $data['context'] ), "Module '$moduleName' must not depend on '$illegalDep'" ); } @@ -65,7 +76,7 @@ class ResourcesTest extends MediaWikiTestCase { /** @var ResourceLoaderModule $module */ foreach ( $data['modules'] as $moduleName => $module ) { - foreach ( $module->getDependencies() as $dep ) { + foreach ( $module->getDependencies( $data['context'] ) as $dep ) { $this->assertContains( $dep, $validDeps, @@ -89,7 +100,7 @@ class ResourcesTest extends MediaWikiTestCase { /** @var ResourceLoaderModule $module */ foreach ( $data['modules'] as $moduleName => $module ) { $moduleTargets = $module->getTargets(); - foreach ( $module->getDependencies() as $dep ) { + foreach ( $module->getDependencies( $data['context'] ) as $dep ) { if ( !isset( $data['modules'][$dep] ) ) { // Missing dependencies reported by testMissingDependencies continue; @@ -108,6 +119,22 @@ class ResourcesTest extends MediaWikiTestCase { } /** + * CSSMin::getAllLocalFileReferences should ignore url(...) expressions + * that have been commented out. + */ + public function testCommentedLocalFileReferences() { + $basepath = __DIR__ . '/../data/css/'; + $css = file_get_contents( $basepath . 'comments.css' ); + $files = CSSMin::getAllLocalFileReferences( $css, $basepath ); + $expected = array( $basepath . 'not-commented.gif' ); + $this->assertArrayEquals( + $expected, + $files, + 'Url(...) expression in comment should be omitted.' + ); + } + + /** * Get all registered modules from ResouceLoader. * @return array */ @@ -265,6 +292,25 @@ class ResourcesTest extends MediaWikiTestCase { ( $file instanceof ResourceLoaderFilePath ? $file->getPath() : $file ), ); } + + // To populate missingLocalFileRefs. Not sure how sane this is inside this test... + $module->readStyleFiles( + $module->getStyleFiles( $data['context'] ), + $module->getFlip( $data['context'] ), + $data['context'] + ); + + $property = $reflectedModule->getProperty( 'missingLocalFileRefs' ); + $property->setAccessible( true ); + $missingLocalFileRefs = $property->getValue( $module ); + + foreach ( $missingLocalFileRefs as $file ) { + $cases[] = array( + $file, + $moduleName, + $file, + ); + } } return $cases; diff --git a/tests/phpunit/suites/UploadFromUrlTestSuite.php b/tests/phpunit/suites/UploadFromUrlTestSuite.php index d4a7bd36..e8672501 100644 --- a/tests/phpunit/suites/UploadFromUrlTestSuite.php +++ b/tests/phpunit/suites/UploadFromUrlTestSuite.php @@ -18,7 +18,7 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { protected function setUp() { global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, - $wgEnableParserCache, $wgNamespaceAliases, $wgNamespaceProtection, + $wgParserCacheType, $wgNamespaceAliases, $wgNamespaceProtection, $parserMemc; $tmpGlobals = array(); @@ -56,7 +56,7 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { $wgNamespaceAliases['Image'] = NS_FILE; $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK; - $wgEnableParserCache = false; + $wgParserCacheType = CACHE_NONE; DeferredUpdates::clearPendingUpdates(); $wgMemc = wfGetMainCache(); $messageMemc = wfGetMessageCacheStorage(); diff --git a/tests/qunit/QUnitTestResources.php b/tests/qunit/QUnitTestResources.php index 17b8b632..f9ddcf27 100644 --- a/tests/qunit/QUnitTestResources.php +++ b/tests/qunit/QUnitTestResources.php @@ -8,14 +8,14 @@ return array( 'test.sinonjs' => array( 'scripts' => array( - 'resources/lib/sinonjs/sinon-1.10.3.js', + 'resources/lib/sinonjs/sinon-1.15.4.js', // We want tests to work in IE, but can't include this as it // will break the placeholders in Sinon because the hack it uses // to hijack IE globals relies on running in the global scope // and in ResourceLoader this won't be running in the global scope. // Including it results (among other things) in sandboxed timers // being broken due to Date inheritance being undefined. - // 'resources/lib/sinonjs/sinon-ie-1.10.3.js', + // 'resources/lib/sinonjs/sinon-ie-1.15.4.js', ), 'targets' => array( 'desktop', 'mobile' ), ), @@ -31,6 +31,7 @@ return array( 'mediawiki.page.ready', 'mediawiki.page.startup', 'test.sinonjs', + 'dom-level2-shim', ), 'position' => 'top', 'targets' => array( 'desktop', 'mobile' ), @@ -66,6 +67,8 @@ return array( 'tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js', 'tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js', 'tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js', + 'tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js', + 'tests/qunit/suites/resources/mediawiki/mediawiki.storage.test.js', 'tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js', 'tests/qunit/suites/resources/mediawiki/mediawiki.test.js', 'tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js', @@ -78,11 +81,14 @@ return array( 'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.category.test.js', 'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js', 'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.parse.test.js', + 'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js', 'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.watch.test.js', + 'tests/qunit/suites/resources/mediawiki.api/mediawiki.ForeignApi.test.js', 'tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js', 'tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js', 'tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js', 'tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js', + 'tests/qunit/suites/resources/mediawiki/mediawiki.experiments.test.js', ), 'dependencies' => array( 'jquery.accessKeyLabel', @@ -105,9 +111,13 @@ return array( 'mediawiki.api.category', 'mediawiki.api.options', 'mediawiki.api.parse', + 'mediawiki.api.upload', 'mediawiki.api.watch', + 'mediawiki.ForeignApi.core', 'mediawiki.jqueryMsg', 'mediawiki.messagePoster', + 'mediawiki.RegExp', + 'mediawiki.storage', 'mediawiki.Title', 'mediawiki.toc', 'mediawiki.Uri', @@ -118,6 +128,7 @@ return array( 'mediawiki.language', 'mediawiki.cldr', 'mediawiki.cookie', + 'mediawiki.experiments', 'test.mediawiki.qunit.testrunner', ), ) diff --git a/tests/qunit/data/generateJqueryMsgData.php b/tests/qunit/data/generateJqueryMsgData.php index 61ebbf8f..0fcaa5fe 100644 --- a/tests/qunit/data/generateJqueryMsgData.php +++ b/tests/qunit/data/generateJqueryMsgData.php @@ -133,8 +133,7 @@ class GenerateJqueryMsgData extends Maintenance { . "// languages, and parser modes. Intended for use by a unit test framework by looping\n" . "// through the object and comparing its parser return value with the 'result' property.\n" . '// Last generated with ' . basename( __FILE__ ) . ' at ' . gmdate( 'r' ) . "\n" - // This file will contain unquoted JSON strings as javascript native object literals, - // flip the quotemark convention for this file. + . "//jscs:disable\n" . "\n" . 'mediaWiki.libs.phpParserData = ' . FormatJson::encode( $phpParserData, true ) . ";\n"; diff --git a/tests/qunit/data/mediawiki.jqueryMsg.data.js b/tests/qunit/data/mediawiki.jqueryMsg.data.js index 4ab5f146..498acc19 100644 --- a/tests/qunit/data/mediawiki.jqueryMsg.data.js +++ b/tests/qunit/data/mediawiki.jqueryMsg.data.js @@ -1,7 +1,8 @@ // This file stores the output from the PHP parser for various messages, arguments, // languages, and parser modes. Intended for use by a unit test framework by looping // through the object and comparing its parser return value with the 'result' property. -// Last generated with generateJqueryMsgData.php at Thu, 30 Jan 2014 04:04:41 +0000 +// Last generated with generateJqueryMsgData.php at Fri, 10 Jul 2015 11:44:08 +0000 +//jscs:disable mediaWiki.libs.phpParserData = { "messages": { diff --git a/tests/qunit/data/testrunner.js b/tests/qunit/data/testrunner.js index 03aaf4af..01f96252 100644 --- a/tests/qunit/data/testrunner.js +++ b/tests/qunit/data/testrunner.js @@ -3,20 +3,17 @@ ( function ( $, mw, QUnit ) { 'use strict'; - var mwTestIgnore, mwTester, - addons, - ELEMENT_NODE = 1, - TEXT_NODE = 3; + var mwTestIgnore, mwTester, addons; /** * Add bogus to url to prevent IE crazy caching * - * @param value {String} a relative path (eg. 'data/foo.js' + * @param {String} value a relative path (eg. 'data/foo.js' * or 'data/test.php?foo=bar'). * @return {String} Such as 'data/foo.js?131031765087663960' */ QUnit.fixurl = function ( value ) { - return value + (/\?/.test( value ) ? '&' : '?') + return value + ( /\?/.test( value ) ? '&' : '?' ) + String( new Date().getTime() ) + String( parseInt( Math.random() * 100000, 10 ) ); }; @@ -69,7 +66,7 @@ sinon.config = { injectIntoThis: true, injectInto: null, - properties: ['spy', 'stub', 'mock', 'sandbox'], + properties: [ 'spy', 'stub', 'mock', 'sandbox' ], // Don't fake timers by default useFakeTimers: false, useFakeServer: false @@ -90,11 +87,11 @@ } }, teardown: function () { - this.sandbox.verifyAndRestore(); - if ( localEnv.teardown ) { localEnv.teardown.call( this ); } + + this.sandbox.verifyAndRestore(); } } ); }; @@ -165,34 +162,32 @@ } /** - * Test environment recommended for all QUnit test modules - * - * Whether to log environment changes to the console - */ - QUnit.config.urlConfig.push( 'mwlogenv' ); - - /** * Reset mw.config and others to a fresh copy of the live config for each test(), * and restore it back to the live one afterwards. - * @param localEnv {Object} [optional] + * + * @param {Object} [localEnv] * @example (see test suite at the bottom of this file) * </code> */ QUnit.newMwEnvironment = ( function () { - var warn, log, liveConfig, liveMessages; + var warn, error, liveConfig, liveMessages, + ajaxRequests = []; liveConfig = mw.config.values; liveMessages = mw.messages.values; function suppressWarnings() { warn = mw.log.warn; - mw.log.warn = $.noop; + error = mw.log.error; + mw.log.warn = mw.log.error = $.noop; } function restoreWarnings() { + // Guard against calls not balanced with suppressWarnings() if ( warn !== undefined ) { mw.log.warn = warn; - warn = undefined; + mw.log.error = error; + warn = error = undefined; } } @@ -217,7 +212,14 @@ return $.extend( /*deep=*/true, {}, liveMessages, custom ); } - log = QUnit.urlParams.mwlogenv ? mw.log : function () {}; + /** + * @param {jQuery.Event} event + * @param {jqXHR} jqXHR + * @param {Object} ajaxOptions + */ + function trackAjax( event, jqXHR, ajaxOptions ) { + ajaxRequests.push( { xhr: jqXHR, options: ajaxOptions } ); + } return function ( localEnv ) { localEnv = $.extend( { @@ -231,8 +233,6 @@ return { setup: function () { - log( 'MwEnvironment> SETUP for "' + QUnit.config.current.module - + ': ' + QUnit.config.current.testName + '"' ); // Greetings, mock environment! mw.config.values = freshConfigCopy( localEnv.config ); @@ -240,16 +240,20 @@ this.suppressWarnings = suppressWarnings; this.restoreWarnings = restoreWarnings; + // Start tracking ajax requests + $( document ).on( 'ajaxSend', trackAjax ); + localEnv.setup.call( this ); }, teardown: function () { - var timers; - log( 'MwEnvironment> TEARDOWN for "' + QUnit.config.current.module - + ': ' + QUnit.config.current.testName + '"' ); + var timers, pending, $activeLen; localEnv.teardown.call( this ); + // Stop tracking ajax requests + $( document ).off( 'ajaxSend', trackAjax ); + // Farewell, mock environment! mw.config.values = liveConfig; mw.messages.values = liveMessages; @@ -258,15 +262,14 @@ // still suppressed by the end of the test. restoreWarnings(); - // Check for incomplete animations/requests/etc and throw - // error if there are any. + // Tests should use fake timers or wait for animations to complete + // Check for incomplete animations/requests/etc and throw if there are any. if ( $.timers && $.timers.length !== 0 ) { timers = $.timers.length; - // Tests shoulld use fake timers or wait for animations to complete $.each( $.timers, function ( i, timer ) { var node = timer.elem; mw.log.warn( 'Unfinished animation #' + i + ' in ' + timer.queue + ' queue on ' + - mw.html.element( node.nodeName.toLowerCase(), $(node).getAttrs() ) + mw.html.element( node.nodeName.toLowerCase(), $( node ).getAttrs() ) ); } ); // Force animations to stop to give the next test a clean start @@ -274,10 +277,24 @@ throw new Error( 'Unfinished animations: ' + timers ); } - if ( $.active !== undefined && $.active !== 0 ) { - // Test may need to use fake XHR, wait for requests or - // call abort(). - throw new Error( 'Unfinished AJAX requests: ' + $.active ); + + // Test should use fake XHR, wait for requests, or call abort() + $activeLen = $.active; + if ( $activeLen !== undefined && $activeLen !== 0 ) { + pending = $.grep( ajaxRequests, function ( ajax ) { + return ajax.xhr.state() === 'pending'; + } ); + if ( pending.length !== $activeLen ) { + mw.log.warn( 'Pending requests does not match jQuery.active count' ); + } + // Force requests to stop to give the next test a clean start + $.each( pending, function ( i, ajax ) { + mw.log.warn( 'Pending AJAX request #' + i, ajax.options ); + ajax.xhr.abort(); + } ); + ajaxRequests = []; + + throw new Error( 'Pending AJAX requests: ' + pending.length + ' (active: ' + $activeLen + ')' ); } } }; @@ -313,12 +330,12 @@ function getDomStructure( node ) { var $node, children, processedChildren, i, len, el; $node = $( node ); - if ( node.nodeType === ELEMENT_NODE ) { + if ( node.nodeType === Node.ELEMENT_NODE ) { children = $node.contents(); processedChildren = []; for ( i = 0, len = children.length; i < len; i++ ) { - el = children[i]; - if ( el.nodeType === ELEMENT_NODE || el.nodeType === TEXT_NODE ) { + el = children[ i ]; + if ( el.nodeType === Node.ELEMENT_NODE || el.nodeType === Node.TEXT_NODE ) { processedChildren.push( getDomStructure( el ) ); } } @@ -340,7 +357,7 @@ * @param {string} html HTML markup for one or more nodes. */ function getHtmlStructure( html ) { - var el = $( '<div>' ).append( html )[0]; + var el = $( '<div>' ).append( html )[ 0 ]; return getDomStructure( el ); } @@ -472,11 +489,11 @@ missing = []; for ( i = 0, len = modules.length; i < len; i++ ) { - state = mw.loader.getState( modules[i] ); + state = mw.loader.getState( modules[ i ] ); if ( state === 'error' ) { - error.push( modules[i] ); + error.push( modules[ i ] ); } else if ( state === 'missing' ) { - missing.push( modules[i] ); + missing.push( modules[ i ] ); } } diff --git a/tests/qunit/suites/resources/jquery/jquery.accessKeyLabel.test.js b/tests/qunit/suites/resources/jquery/jquery.accessKeyLabel.test.js index 4484467d..cf34fc11 100644 --- a/tests/qunit/suites/resources/jquery/jquery.accessKeyLabel.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.accessKeyLabel.test.js @@ -1,7 +1,7 @@ ( function ( $ ) { QUnit.module( 'jquery.accessKeyLabel', QUnit.newMwEnvironment( { messages: { - 'brackets': '[$1]', + brackets: '[$1]', 'word-separator': ' ' } } ) ); @@ -9,23 +9,23 @@ var getAccessKeyPrefixTestData = [ // ua string, platform string, expected prefix // Internet Explorer - ['Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)', 'Win32', 'alt-'], - ['Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)', 'Win32', 'alt-'], - ['Mozilla/5.0 (Windows NT 6.3; Win64; x64; Trident/7.0; rv:11.0) like Gecko', 'Win64', 'alt-'], + [ 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)', 'Win32', 'alt-' ], + [ 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)', 'Win32', 'alt-' ], + [ 'Mozilla/5.0 (Windows NT 6.3; Win64; x64; Trident/7.0; rv:11.0) like Gecko', 'Win64', 'alt-' ], // Firefox - ['Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1.19) Gecko/20110420 Firefox/3.5.19', 'MacIntel', 'ctrl-'], - ['Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.17) Gecko/20110422 Ubuntu/10.10 (maverick) Firefox/3.6.17', 'Linux i686', 'alt-shift-'], - ['Mozilla/5.0 (Windows NT 6.0; rv:2.0.1) Gecko/20100101 Firefox/4.0.1', 'Win32', 'alt-shift-'], + [ 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1.19) Gecko/20110420 Firefox/3.5.19', 'MacIntel', 'ctrl-' ], + [ 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.17) Gecko/20110422 Ubuntu/10.10 (maverick) Firefox/3.6.17', 'Linux i686', 'alt-shift-' ], + [ 'Mozilla/5.0 (Windows NT 6.0; rv:2.0.1) Gecko/20100101 Firefox/4.0.1', 'Win32', 'alt-shift-' ], // Safari / Konqueror - ['Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; nl-nl) AppleWebKit/531.22.7 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7', 'MacIntel', 'ctrl-alt-'], - ['Mozilla/5.0 (Windows; U; Windows NT 6.0; cs-CZ) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7', 'Win32', 'alt-'], - ['Mozilla/5.0 (X11; Linux i686) KHTML/4.9.1 (like Gecko) Konqueror/4.9', 'Linux i686', 'ctrl-'], + [ 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; nl-nl) AppleWebKit/531.22.7 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7', 'MacIntel', 'ctrl-alt-' ], + [ 'Mozilla/5.0 (Windows; U; Windows NT 6.0; cs-CZ) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7', 'Win32', 'alt-' ], + [ 'Mozilla/5.0 (X11; Linux i686) KHTML/4.9.1 (like Gecko) Konqueror/4.9', 'Linux i686', 'ctrl-' ], // Opera - ['Opera/9.80 (Windows NT 5.1)', 'Win32', 'shift-esc-'], - ['Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.52 Safari/537.36 OPR/15.0.1147.130', 'Win32', 'shift-esc-'], + [ 'Opera/9.80 (Windows NT 5.1)', 'Win32', 'shift-esc-' ], + [ 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.52 Safari/537.36 OPR/15.0.1147.130', 'Win32', 'shift-esc-' ], // Chrome - ['Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30', 'MacIntel', 'ctrl-option-'], - ['Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.68 Safari/534.30', 'Linux i686', 'alt-shift-'] + [ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30', 'MacIntel', 'ctrl-option-' ], + [ 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.68 Safari/534.30', 'Linux i686', 'alt-shift-' ] ], // strings appended to title to make sure updateTooltipAccessKeys handles them correctly updateTooltipAccessKeysTestData = [ '', ' [a]', ' [test-a]', ' [alt-b]' ]; @@ -39,9 +39,9 @@ var i; for ( i = 0; i < getAccessKeyPrefixTestData.length; i++ ) { assert.equal( $.fn.updateTooltipAccessKeys.getAccessKeyPrefix( { - userAgent: getAccessKeyPrefixTestData[i][0], - platform: getAccessKeyPrefixTestData[i][1] - } ), getAccessKeyPrefixTestData[i][2], 'Correct prefix for ' + getAccessKeyPrefixTestData[i][0] ); + userAgent: getAccessKeyPrefixTestData[ i ][ 0 ], + platform: getAccessKeyPrefixTestData[ i ][ 1 ] + } ), getAccessKeyPrefixTestData[ i ][ 2 ], 'Correct prefix for ' + getAccessKeyPrefixTestData[ i ][ 0 ] ); } } ); @@ -52,13 +52,13 @@ // (no browser is known using such a short prefix, though) or "Alt+Umschalt+" in German Firefox. result = /^Title \[(.+)[aA]\]$/.exec( title ); assert.ok( result, 'title should match expected structure.' ); - assert.notEqual( result[1], 'test-', 'Prefix used for testing shouldn\'t be used in production.' ); + assert.notEqual( result[ 1 ], 'test-', 'Prefix used for testing shouldn\'t be used in production.' ); } ); QUnit.test( 'updateTooltipAccessKeys - no access key', updateTooltipAccessKeysTestData.length, function ( assert ) { var i, oldTitle, $input, newTitle; for ( i = 0; i < updateTooltipAccessKeysTestData.length; i++ ) { - oldTitle = 'Title' + updateTooltipAccessKeysTestData[i]; + oldTitle = 'Title' + updateTooltipAccessKeysTestData[ i ]; $input = $( makeInput( oldTitle ) ); $( '#qunit-fixture' ).append( $input ); newTitle = $input.updateTooltipAccessKeys().prop( 'title' ); @@ -70,7 +70,7 @@ $.fn.updateTooltipAccessKeys.setTestMode( true ); var i, oldTitle, $input, newTitle; for ( i = 0; i < updateTooltipAccessKeysTestData.length; i++ ) { - oldTitle = 'Title' + updateTooltipAccessKeysTestData[i]; + oldTitle = 'Title' + updateTooltipAccessKeysTestData[ i ]; $input = $( makeInput( oldTitle, 'a' ) ); $( '#qunit-fixture' ).append( $input ); newTitle = $input.updateTooltipAccessKeys().prop( 'title' ); diff --git a/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js b/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js index e8c51214..a1b2e5c3 100644 --- a/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js @@ -11,7 +11,7 @@ function findDivergenceIndex( a, b ) { var i = 0; - while ( i < a.length && i < b.length && a[i] === b[i] ) { + while ( i < a.length && i < b.length && a[ i ] === b[ i ] ) { i++; } return i; @@ -41,7 +41,7 @@ // Add two characters using scary black magic spanText = $span.text(); d = findDivergenceIndex( origText, spanText ); - spanTextNew = spanText.slice( 0, d ) + origText[d] + origText[d] + '...'; + spanTextNew = spanText.slice( 0, d ) + origText[ d ] + origText[ d ] + '...'; assert.gt( spanTextNew.length, spanText.length, 'Verify that the new span-length is indeed greater' ); diff --git a/tests/qunit/suites/resources/jquery/jquery.color.test.js b/tests/qunit/suites/resources/jquery/jquery.color.test.js index c8e8ac70..9afd793e 100644 --- a/tests/qunit/suites/resources/jquery/jquery.color.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.color.test.js @@ -10,7 +10,7 @@ $canvas.animate( { backgroundColor: '#000' }, 10 ).promise().then( function () { var endColors = $.colorUtil.getRGB( $canvas.css( 'background-color' ) ); - assert.deepEqual( endColors, [0, 0, 0], 'end state' ); + assert.deepEqual( endColors, [ 0, 0, 0 ], 'end state' ); } ); this.clock.tick( 20 ); diff --git a/tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js b/tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js index 39ae363c..00de895d 100644 --- a/tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js @@ -4,25 +4,25 @@ QUnit.test( 'getRGB', 18, function ( assert ) { assert.strictEqual( $.colorUtil.getRGB(), undefined, 'No arguments' ); assert.strictEqual( $.colorUtil.getRGB( '' ), undefined, 'Empty string' ); - assert.deepEqual( $.colorUtil.getRGB( [0, 100, 255] ), [0, 100, 255], 'Parse array of rgb values' ); - assert.deepEqual( $.colorUtil.getRGB( 'rgb(0,100,255)' ), [0, 100, 255], 'Parse simple rgb string' ); - assert.deepEqual( $.colorUtil.getRGB( 'rgb(0, 100, 255)' ), [0, 100, 255], 'Parse simple rgb string with spaces' ); - assert.deepEqual( $.colorUtil.getRGB( 'rgb(0%,20%,40%)' ), [0, 51, 102], 'Parse rgb string with percentages' ); - assert.deepEqual( $.colorUtil.getRGB( 'rgb(0%, 20%, 40%)' ), [0, 51, 102], 'Parse rgb string with percentages and spaces' ); - assert.deepEqual( $.colorUtil.getRGB( '#f2ddee' ), [242, 221, 238], 'Hex string: 6 char lowercase' ); - assert.deepEqual( $.colorUtil.getRGB( '#f2DDEE' ), [242, 221, 238], 'Hex string: 6 char uppercase' ); - assert.deepEqual( $.colorUtil.getRGB( '#f2DdEe' ), [242, 221, 238], 'Hex string: 6 char mixed' ); - assert.deepEqual( $.colorUtil.getRGB( '#eee' ), [238, 238, 238], 'Hex string: 3 char lowercase' ); - assert.deepEqual( $.colorUtil.getRGB( '#EEE' ), [238, 238, 238], 'Hex string: 3 char uppercase' ); - assert.deepEqual( $.colorUtil.getRGB( '#eEe' ), [238, 238, 238], 'Hex string: 3 char mixed' ); - assert.deepEqual( $.colorUtil.getRGB( 'rgba(0, 0, 0, 0)' ), [255, 255, 255], 'Zero rgba for Safari 3; Transparent (whitespace)' ); + assert.deepEqual( $.colorUtil.getRGB( [ 0, 100, 255 ] ), [ 0, 100, 255 ], 'Parse array of rgb values' ); + assert.deepEqual( $.colorUtil.getRGB( 'rgb(0,100,255)' ), [ 0, 100, 255 ], 'Parse simple rgb string' ); + assert.deepEqual( $.colorUtil.getRGB( 'rgb(0, 100, 255)' ), [ 0, 100, 255 ], 'Parse simple rgb string with spaces' ); + assert.deepEqual( $.colorUtil.getRGB( 'rgb(0%,20%,40%)' ), [ 0, 51, 102 ], 'Parse rgb string with percentages' ); + assert.deepEqual( $.colorUtil.getRGB( 'rgb(0%, 20%, 40%)' ), [ 0, 51, 102 ], 'Parse rgb string with percentages and spaces' ); + assert.deepEqual( $.colorUtil.getRGB( '#f2ddee' ), [ 242, 221, 238 ], 'Hex string: 6 char lowercase' ); + assert.deepEqual( $.colorUtil.getRGB( '#f2DDEE' ), [ 242, 221, 238 ], 'Hex string: 6 char uppercase' ); + assert.deepEqual( $.colorUtil.getRGB( '#f2DdEe' ), [ 242, 221, 238 ], 'Hex string: 6 char mixed' ); + assert.deepEqual( $.colorUtil.getRGB( '#eee' ), [ 238, 238, 238 ], 'Hex string: 3 char lowercase' ); + assert.deepEqual( $.colorUtil.getRGB( '#EEE' ), [ 238, 238, 238 ], 'Hex string: 3 char uppercase' ); + assert.deepEqual( $.colorUtil.getRGB( '#eEe' ), [ 238, 238, 238 ], 'Hex string: 3 char mixed' ); + assert.deepEqual( $.colorUtil.getRGB( 'rgba(0, 0, 0, 0)' ), [ 255, 255, 255 ], 'Zero rgba for Safari 3; Transparent (whitespace)' ); // Perhaps this is a bug in colorUtil, but it is the current behavior so, let's keep // track of it, so we will know in case it would ever change. assert.strictEqual( $.colorUtil.getRGB( 'rgba(0,0,0,0)' ), undefined, 'Zero rgba without whitespace' ); - assert.deepEqual( $.colorUtil.getRGB( 'lightGreen' ), [144, 238, 144], 'Color names (lightGreen)' ); - assert.deepEqual( $.colorUtil.getRGB( 'transparent' ), [255, 255, 255], 'Color names (transparent)' ); + assert.deepEqual( $.colorUtil.getRGB( 'lightGreen' ), [ 144, 238, 144 ], 'Color names (lightGreen)' ); + assert.deepEqual( $.colorUtil.getRGB( 'transparent' ), [ 255, 255, 255 ], 'Color names (transparent)' ); assert.strictEqual( $.colorUtil.getRGB( 'mediaWiki' ), undefined, 'Inexisting color name' ); } ); @@ -37,9 +37,9 @@ // Re-create the rgbToHsl return array items, limited to two decimals. hsl = $.colorUtil.rgbToHsl( 144, 238, 144 ); - ret = [ dualDecimals( hsl[0] ), dualDecimals( hsl[1] ), dualDecimals( hsl[2] ) ]; + ret = [ dualDecimals( hsl[ 0 ] ), dualDecimals( hsl[ 1 ] ), dualDecimals( hsl[ 2 ] ) ]; - assert.deepEqual( ret, [0.33, 0.73, 0.75], 'rgb(144, 238, 144): hsl(0.33, 0.73, 0.75)' ); + assert.deepEqual( ret, [ 0.33, 0.73, 0.75 ], 'rgb(144, 238, 144): hsl(0.33, 0.73, 0.75)' ); } ); QUnit.test( 'hslToRgb', 1, function ( assert ) { @@ -47,9 +47,9 @@ rgb = $.colorUtil.hslToRgb( 0.3, 0.7, 0.8 ); // Re-create the hslToRgb return array items, rounded to whole numbers. - ret = [ Math.round( rgb[0] ), Math.round( rgb[1] ), Math.round( rgb[2] ) ]; + ret = [ Math.round( rgb[ 0 ] ), Math.round( rgb[ 1 ] ), Math.round( rgb[ 2 ] ) ]; - assert.deepEqual( ret, [183, 240, 168], 'hsl(0.3, 0.7, 0.8): rgb(183, 240, 168)' ); + assert.deepEqual( ret, [ 183, 240, 168 ], 'hsl(0.3, 0.7, 0.8): rgb(183, 240, 168)' ); } ); QUnit.test( 'getColorBrightness', 2, function ( assert ) { diff --git a/tests/qunit/suites/resources/jquery/jquery.hidpi.test.js b/tests/qunit/suites/resources/jquery/jquery.hidpi.test.js index 906369ee..8c628765 100644 --- a/tests/qunit/suites/resources/jquery/jquery.hidpi.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.hidpi.test.js @@ -6,6 +6,22 @@ assert.equal( typeof devicePixelRatio, 'number', '$.devicePixelRatio() returns a number' ); } ); + QUnit.test( 'bracketedDevicePixelRatio', 1, function ( assert ) { + var devicePixelRatio = $.devicePixelRatio(); + assert.equal( typeof devicePixelRatio, 'number', '$.bracketedDevicePixelRatio() returns a number' ); + } ); + + QUnit.test( 'bracketDevicePixelRatio', 8, function ( assert ) { + assert.equal( $.bracketDevicePixelRatio( 0.75 ), 1, '0.75 gives 1' ); + assert.equal( $.bracketDevicePixelRatio( 1 ), 1, '1 gives 1' ); + assert.equal( $.bracketDevicePixelRatio( 1.25 ), 1.5, '1.25 gives 1.5' ); + assert.equal( $.bracketDevicePixelRatio( 1.5 ), 1.5, '1.5 gives 1.5' ); + assert.equal( $.bracketDevicePixelRatio( 1.75 ), 2, '1.75 gives 2' ); + assert.equal( $.bracketDevicePixelRatio( 2 ), 2, '2 gives 2' ); + assert.equal( $.bracketDevicePixelRatio( 2.5 ), 2, '2.5 gives 2' ); + assert.equal( $.bracketDevicePixelRatio( 3 ), 2, '3 gives 2' ); + } ); + QUnit.test( 'matchSrcSet', 6, function ( assert ) { var srcset = 'onefive.png 1.5x, two.png 2x'; diff --git a/tests/qunit/suites/resources/jquery/jquery.localize.test.js b/tests/qunit/suites/resources/jquery/jquery.localize.test.js index 3ef27903..c503fc99 100644 --- a/tests/qunit/suites/resources/jquery/jquery.localize.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.localize.test.js @@ -76,8 +76,8 @@ html = '<div><span title-msg="title"><html:msg key="label" /></span></div>'; $lc = $( html ).localize( { keys: { - 'title': 'foo-' + x + '-title', - 'label': 'foo-' + x + '-label' + title: 'foo-' + x + '-title', + label: 'foo-' + x + '-label' } } ).find( 'span' ); @@ -88,7 +88,7 @@ html = '<div><span><html:msg key="foo-welcome" /></span></div>'; $lc = $( html ).localize( { params: { - 'foo-welcome': [sitename, 'yesterday'] + 'foo-welcome': [ sitename, 'yesterday' ] } } ).find( 'span' ); @@ -100,12 +100,12 @@ $lc = $( html ).localize( { prefix: 'foo-', keys: { - 'title': x + '-title', - 'label': x + '-label' + title: x + '-title', + label: x + '-label' }, params: { - 'title': [sitename, '3 minutes ago'], - 'label': [sitename, '3 minutes ago'] + title: [ sitename, '3 minutes ago' ], + label: [ sitename, '3 minutes ago' ] } } ).find( 'span' ); diff --git a/tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js b/tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js index 80405819..c51e4093 100644 --- a/tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js @@ -336,4 +336,22 @@ this.clock.tick( 500 ); } ); + QUnit.test( 'cloned collapsibles can be made collapsible again', 2, function ( assert ) { + var test = this, + $collapsible = prepareCollapsible( + '<div class="mw-collapsible">' + loremIpsum + '</div>' + ), + $clone = $collapsible.clone() // clone without data and events + .appendTo( '#qunit-fixture' ).makeCollapsible(), + $content = $clone.find( '.mw-collapsible-content' ); + + assert.assertTrue( $content.is( ':visible' ), 'content is visible' ); + + $clone.on( 'afterCollapse.mw-collapsible', function () { + assert.assertTrue( $content.is( ':hidden' ), 'after collapsing: content is hidden' ); + } ); + + $clone.find( '.mw-collapsible-toggle a' ).trigger( 'click' ); + test.clock.tick( 500 ); + } ); }( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js b/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js index 795c2bbb..029edd55 100644 --- a/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js @@ -1,5 +1,14 @@ ( function ( $ ) { - QUnit.module( 'jquery.mwExtension', QUnit.newMwEnvironment() ); + QUnit.module( 'jquery.mwExtension', QUnit.newMwEnvironment( { + // This entire module is deprecated. + // Surpress deprecation warnings in test output. + setup: function () { + this.suppressWarnings(); + }, + teardown: function () { + this.restoreWarnings(); + } + } ) ); QUnit.test( 'String functions', 7, function ( assert ) { assert.equal( $.trimLeft( ' foo bar ' ), 'foo bar ', 'trimLeft' ); @@ -43,9 +52,9 @@ } ); QUnit.test( 'Comparison functions', 5, function ( assert ) { - assert.ok( $.compareArray( [0, 'a', [], [2, 'b'] ], [0, 'a', [], [2, 'b'] ] ), + assert.ok( $.compareArray( [ 0, 'a', [], [ 2, 'b' ] ], [ 0, 'a', [], [ 2, 'b' ] ] ), 'compareArray: Two deep arrays that are excactly the same' ); - assert.ok( !$.compareArray( [1], [2] ), 'compareArray: Two different arrays (false)' ); + assert.ok( !$.compareArray( [ 1 ], [ 2 ] ), 'compareArray: Two different arrays (false)' ); assert.ok( $.compareObject( {}, {} ), 'compareObject: Two empty objects' ); assert.ok( $.compareObject( { foo: 1 }, { foo: 1 } ), 'compareObject: Two the same objects' ); diff --git a/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js b/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js index 78c185f1..5d0ddebb 100644 --- a/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js @@ -1,13 +1,13 @@ -( function ($) { +( function ( $ ) { - QUnit.module('jquery.placeholder', QUnit.newMwEnvironment()); + QUnit.module( 'jquery.placeholder', QUnit.newMwEnvironment() ); - QUnit.test('caches results of feature tests', 2, function (assert) { - assert.strictEqual( typeof $.fn.placeholder.input, 'boolean', '$.fn.placeholder.input'); - assert.strictEqual( typeof $.fn.placeholder.textarea, 'boolean', '$.fn.placeholder.textarea'); - }); + QUnit.test( 'caches results of feature tests', 2, function ( assert ) { + assert.strictEqual( typeof $.fn.placeholder.input, 'boolean', '$.fn.placeholder.input' ); + assert.strictEqual( typeof $.fn.placeholder.textarea, 'boolean', '$.fn.placeholder.textarea' ); + } ); - if ($.fn.placeholder.input && $.fn.placeholder.textarea) { + if ( $.fn.placeholder.input && $.fn.placeholder.textarea ) { return; } @@ -20,126 +20,126 @@ '<input id="input-type-password" type="password" placeholder="e.g. hunter2">' + '<textarea id="textarea" name="message" placeholder="Your message goes here"></textarea>' + '</form>', - testElement = function ($el, assert) { + testElement = function ( $el, assert ) { - var el = $el[0], - placeholder = el.getAttribute('placeholder'); + var el = $el[ 0 ], + placeholder = el.getAttribute( 'placeholder' ); - assert.strictEqual($el.placeholder(), $el, 'should be chainable'); + assert.strictEqual( $el.placeholder(), $el, 'should be chainable' ); - assert.strictEqual(el.value, placeholder, 'should set `placeholder` text as `value`'); - assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); - assert.strictEqual($el.val(), '', 'valHooks works properly'); - assert.ok($el.hasClass('placeholder'), 'should have `placeholder` class'); + assert.strictEqual( el.value, placeholder, 'should set `placeholder` text as `value`' ); + assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' ); + assert.strictEqual( $el.val(), '', 'valHooks works properly' ); + assert.ok( $el.hasClass( 'placeholder' ), 'should have `placeholder` class' ); // test on focus $el.focus(); - assert.strictEqual(el.value, '', '`value` should be the empty string on focus'); - assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); - assert.strictEqual($el.val(), '', 'valHooks works properly'); - assert.ok(!$el.hasClass('placeholder'), 'should not have `placeholder` class on focus'); + assert.strictEqual( el.value, '', '`value` should be the empty string on focus' ); + assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' ); + assert.strictEqual( $el.val(), '', 'valHooks works properly' ); + assert.ok( !$el.hasClass( 'placeholder' ), 'should not have `placeholder` class on focus' ); // and unfocus (blur) again $el.blur(); - assert.strictEqual(el.value, placeholder, 'should set `placeholder` text as `value`'); - assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); - assert.strictEqual($el.val(), '', 'valHooks works properly'); - assert.ok($el.hasClass('placeholder'), 'should have `placeholder` class'); + assert.strictEqual( el.value, placeholder, 'should set `placeholder` text as `value`' ); + assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' ); + assert.strictEqual( $el.val(), '', 'valHooks works properly' ); + assert.ok( $el.hasClass( 'placeholder' ), 'should have `placeholder` class' ); // change the value - $el.val('lorem ipsum'); - assert.strictEqual($el.prop('value'), 'lorem ipsum', '`$el.val(string)` should change the `value` property'); - assert.strictEqual(el.value, 'lorem ipsum', '`$el.val(string)` should change the `value` attribute'); - assert.ok(!$el.hasClass('placeholder'), '`$el.val(string)` should remove `placeholder` class'); + $el.val( 'lorem ipsum' ); + assert.strictEqual( $el.prop( 'value' ), 'lorem ipsum', '`$el.val(string)` should change the `value` property' ); + assert.strictEqual( el.value, 'lorem ipsum', '`$el.val(string)` should change the `value` attribute' ); + assert.ok( !$el.hasClass( 'placeholder' ), '`$el.val(string)` should remove `placeholder` class' ); // and clear it again - $el.val(''); - assert.strictEqual($el.prop('value'), '', '`$el.val("")` should change the `value` property'); - assert.strictEqual(el.value, placeholder, '`$el.val("")` should change the `value` attribute'); - assert.ok($el.hasClass('placeholder'), '`$el.val("")` should re-enable `placeholder` class'); + $el.val( '' ); + assert.strictEqual( $el.prop( 'value' ), '', '`$el.val("")` should change the `value` property' ); + assert.strictEqual( el.value, placeholder, '`$el.val("")` should change the `value` attribute' ); + assert.ok( $el.hasClass( 'placeholder' ), '`$el.val("")` should re-enable `placeholder` class' ); // make sure the placeholder property works as expected. - assert.strictEqual($el.prop('placeholder'), placeholder, '$el.prop(`placeholder`) should return the placeholder value'); - $el.placeholder('new placeholder'); - assert.strictEqual(el.getAttribute('placeholder'), 'new placeholder', '$el.placeholder(<string>) should set the placeholder value'); - assert.strictEqual(el.value, 'new placeholder', '$el.placeholder(<string>) should update the displayed placeholder value'); - $el.placeholder(placeholder); + assert.strictEqual( $el.prop( 'placeholder' ), placeholder, '$el.prop(`placeholder`) should return the placeholder value' ); + $el.placeholder( 'new placeholder' ); + assert.strictEqual( el.getAttribute( 'placeholder' ), 'new placeholder', '$el.placeholder(<string>) should set the placeholder value' ); + assert.strictEqual( el.value, 'new placeholder', '$el.placeholder(<string>) should update the displayed placeholder value' ); + $el.placeholder( placeholder ); }; - QUnit.test('emulates placeholder for <input type=text>', 22, function (assert) { - $('<div>').html(html).appendTo($('#qunit-fixture')); - testElement($('#input-type-text'), assert); - }); + QUnit.test( 'emulates placeholder for <input type=text>', 22, function ( assert ) { + $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) ); + testElement( $( '#input-type-text' ), assert ); + } ); - QUnit.test('emulates placeholder for <input type=search>', 22, function (assert) { - $('<div>').html(html).appendTo($('#qunit-fixture')); - testElement($('#input-type-search'), assert); - }); + QUnit.test( 'emulates placeholder for <input type=search>', 22, function ( assert ) { + $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) ); + testElement( $( '#input-type-search' ), assert ); + } ); - QUnit.test('emulates placeholder for <input type=email>', 22, function (assert) { - $('<div>').html(html).appendTo($('#qunit-fixture')); - testElement($('#input-type-email'), assert); - }); + QUnit.test( 'emulates placeholder for <input type=email>', 22, function ( assert ) { + $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) ); + testElement( $( '#input-type-email' ), assert ); + } ); - QUnit.test('emulates placeholder for <input type=url>', 22, function (assert) { - $('<div>').html(html).appendTo($('#qunit-fixture')); - testElement($('#input-type-url'), assert); - }); + QUnit.test( 'emulates placeholder for <input type=url>', 22, function ( assert ) { + $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) ); + testElement( $( '#input-type-url' ), assert ); + } ); - QUnit.test('emulates placeholder for <input type=tel>', 22, function (assert) { - $('<div>').html(html).appendTo($('#qunit-fixture')); - testElement($('#input-type-tel'), assert); - }); + QUnit.test( 'emulates placeholder for <input type=tel>', 22, function ( assert ) { + $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) ); + testElement( $( '#input-type-tel' ), assert ); + } ); - QUnit.test('emulates placeholder for <input type=password>', 13, function (assert) { - $('<div>').html(html).appendTo($('#qunit-fixture')); + QUnit.test( 'emulates placeholder for <input type=password>', 13, function ( assert ) { + $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) ); var selector = '#input-type-password', - $el = $(selector), - el = $el[0], - placeholder = el.getAttribute('placeholder'); + $el = $( selector ), + el = $el[ 0 ], + placeholder = el.getAttribute( 'placeholder' ); - assert.strictEqual($el.placeholder(), $el, 'should be chainable'); + assert.strictEqual( $el.placeholder(), $el, 'should be chainable' ); // Re-select the element, as it gets replaced by another one in some browsers - $el = $(selector); - el = $el[0]; + $el = $( selector ); + el = $el[ 0 ]; - assert.strictEqual(el.value, placeholder, 'should set `placeholder` text as `value`'); - assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); - assert.strictEqual($el.val(), '', 'valHooks works properly'); - assert.ok($el.hasClass('placeholder'), 'should have `placeholder` class'); + assert.strictEqual( el.value, placeholder, 'should set `placeholder` text as `value`' ); + assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' ); + assert.strictEqual( $el.val(), '', 'valHooks works properly' ); + assert.ok( $el.hasClass( 'placeholder' ), 'should have `placeholder` class' ); // test on focus $el.focus(); // Re-select the element, as it gets replaced by another one in some browsers - $el = $(selector); - el = $el[0]; + $el = $( selector ); + el = $el[ 0 ]; - assert.strictEqual(el.value, '', '`value` should be the empty string on focus'); - assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); - assert.strictEqual($el.val(), '', 'valHooks works properly'); - assert.ok(!$el.hasClass('placeholder'), 'should not have `placeholder` class on focus'); + assert.strictEqual( el.value, '', '`value` should be the empty string on focus' ); + assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' ); + assert.strictEqual( $el.val(), '', 'valHooks works properly' ); + assert.ok( !$el.hasClass( 'placeholder' ), 'should not have `placeholder` class on focus' ); // and unfocus (blur) again $el.blur(); // Re-select the element, as it gets replaced by another one in some browsers - $el = $(selector); - el = $el[0]; + $el = $( selector ); + el = $el[ 0 ]; - assert.strictEqual(el.value, placeholder, 'should set `placeholder` text as `value`'); - assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); - assert.strictEqual($el.val(), '', 'valHooks works properly'); - assert.ok($el.hasClass('placeholder'), 'should have `placeholder` class'); + assert.strictEqual( el.value, placeholder, 'should set `placeholder` text as `value`' ); + assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' ); + assert.strictEqual( $el.val(), '', 'valHooks works properly' ); + assert.ok( $el.hasClass( 'placeholder' ), 'should have `placeholder` class' ); - }); + } ); - QUnit.test('emulates placeholder for <textarea></textarea>', 22, function (assert) { - $('<div>').html(html).appendTo($('#qunit-fixture')); - testElement($('#textarea'), assert); - }); + QUnit.test( 'emulates placeholder for <textarea></textarea>', 22, function ( assert ) { + $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) ); + testElement( $( '#textarea' ), assert ); + } ); -}(jQuery)); +}( jQuery ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js b/tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js index 97a3ae12..032551d8 100644 --- a/tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js @@ -12,32 +12,32 @@ setup: function () { this.liveMonths = mw.language.months; mw.language.months = { - 'keys': { - 'names': ['january', 'february', 'march', 'april', 'may_long', 'june', - 'july', 'august', 'september', 'october', 'november', 'december'], - 'genitive': ['january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen', - 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen', 'december-gen'], - 'abbrev': ['jan', 'feb', 'mar', 'apr', 'may', 'jun', - 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'] + keys: { + names: [ 'january', 'february', 'march', 'april', 'may_long', 'june', + 'july', 'august', 'september', 'october', 'november', 'december' ], + genitive: [ 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen', + 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen', 'december-gen' ], + abbrev: [ 'jan', 'feb', 'mar', 'apr', 'may', 'jun', + 'jul', 'aug', 'sep', 'oct', 'nov', 'dec' ] }, - 'names': ['January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', 'November', 'December'], - 'genitive': ['January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', 'November', 'December'], - 'abbrev': ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + names: [ 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' ], + genitive: [ 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' ], + abbrev: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ] }; }, teardown: function () { mw.language.months = this.liveMonths; }, config: { - wgContentLanguage: 'en', + wgPageContentLanguage: 'en', /* default date format of the content language */ wgDefaultDateFormat: 'dmy', /* These two are important for numeric interpretations */ - wgSeparatorTransformTable: ['', ''], - wgDigitTransformTable: ['', ''] + wgSeparatorTransformTable: [ '', '' ], + wgDigitTransformTable: [ '', '' ] } } ) ); @@ -57,17 +57,17 @@ QUnit.test( msg, data.length * 2, function ( assert ) { var extractedR, extractedF, parser; - if (callback !== undefined ) { + if ( callback !== undefined ) { callback(); } parser = $.tablesorter.getParser( parserId ); $.each( data, function ( index, testcase ) { - extractedR = parser.is( testcase[0] ); - extractedF = parser.format( testcase[0] ); + extractedR = parser.is( testcase[ 0 ] ); + extractedF = parser.format( testcase[ 0 ] ); - assert.strictEqual( extractedR, testcase[1], 'Detect: ' + testcase[3] ); - assert.strictEqual( extractedF, testcase[2], 'Sortkey: ' + testcase[3] ); + assert.strictEqual( extractedR, testcase[ 1 ], 'Detect: ' + testcase[ 3 ] ); + assert.strictEqual( extractedF, testcase[ 2 ], 'Sortkey: ' + testcase[ 3 ] ); } ); } ); @@ -82,139 +82,139 @@ ipv4 = [ // Some randomly generated fake IPs - ['0.0.0.0', true, 0, 'An IP address' ], - ['255.255.255.255', true, 255255255255, 'An IP address' ], - ['45.238.27.109', true, 45238027109, 'An IP address' ], - ['1.238.27.1', true, 1238027001, 'An IP address with small numbers' ], - ['238.27.1', false, 238027001, 'A malformed IP Address' ], - ['1', false, 1, 'A super malformed IP Address' ], - ['Just text', false, 0, 'A line with just text' ], - ['45.238.27.109Postfix', false, 45238027109, 'An IP address with a connected postfix' ], - ['45.238.27.109 postfix', false, 45238027109, 'An IP address with a seperated postfix' ] + [ '0.0.0.0', true, 0, 'An IP address' ], + [ '255.255.255.255', true, 255255255255, 'An IP address' ], + [ '45.238.27.109', true, 45238027109, 'An IP address' ], + [ '1.238.27.1', true, 1238027001, 'An IP address with small numbers' ], + [ '238.27.1', false, 238027001, 'A malformed IP Address' ], + [ '1', false, 1, 'A super malformed IP Address' ], + [ 'Just text', false, 0, 'A line with just text' ], + [ '45.238.27.109Postfix', false, 45238027109, 'An IP address with a connected postfix' ], + [ '45.238.27.109 postfix', false, 45238027109, 'An IP address with a seperated postfix' ] ]; parserTest( 'IPv4', 'IPAddress', ipv4 ); simpleMDYDatesInMDY = [ - ['January 17, 2010', true, 20100117, 'Long middle endian date'], - ['Jan 17, 2010', true, 20100117, 'Short middle endian date'], - ['1/17/2010', true, 20100117, 'Numeric middle endian date'], - ['01/17/2010', true, 20100117, 'Numeric middle endian date with padding on month'], - ['01/07/2010', true, 20100107, 'Numeric middle endian date with padding on day'], - ['01/07/0010', true, 20100107, 'Numeric middle endian date with padding on year'], - ['5.12.1990', true, 19900512, 'Numeric middle endian date with . separator'] + [ 'January 17, 2010', true, 20100117, 'Long middle endian date' ], + [ 'Jan 17, 2010', true, 20100117, 'Short middle endian date' ], + [ '1/17/2010', true, 20100117, 'Numeric middle endian date' ], + [ '01/17/2010', true, 20100117, 'Numeric middle endian date with padding on month' ], + [ '01/07/2010', true, 20100107, 'Numeric middle endian date with padding on day' ], + [ '01/07/0010', true, 20100107, 'Numeric middle endian date with padding on year' ], + [ '5.12.1990', true, 19900512, 'Numeric middle endian date with . separator' ] ]; parserTest( 'MDY Dates using mdy content language', 'date', simpleMDYDatesInMDY ); simpleMDYDatesInDMY = [ - ['January 17, 2010', true, 20100117, 'Long middle endian date'], - ['Jan 17, 2010', true, 20100117, 'Short middle endian date'], - ['1/17/2010', true, 20101701, 'Numeric middle endian date'], - ['01/17/2010', true, 20101701, 'Numeric middle endian date with padding on month'], - ['01/07/2010', true, 20100701, 'Numeric middle endian date with padding on day'], - ['01/07/0010', true, 20100701, 'Numeric middle endian date with padding on year'], - ['5.12.1990', true, 19901205, 'Numeric middle endian date with . separator'] + [ 'January 17, 2010', true, 20100117, 'Long middle endian date' ], + [ 'Jan 17, 2010', true, 20100117, 'Short middle endian date' ], + [ '1/17/2010', true, 20101701, 'Numeric middle endian date' ], + [ '01/17/2010', true, 20101701, 'Numeric middle endian date with padding on month' ], + [ '01/07/2010', true, 20100701, 'Numeric middle endian date with padding on day' ], + [ '01/07/0010', true, 20100701, 'Numeric middle endian date with padding on year' ], + [ '5.12.1990', true, 19901205, 'Numeric middle endian date with . separator' ] ]; parserTest( 'MDY Dates using dmy content language', 'date', simpleMDYDatesInDMY, function () { mw.config.set( { - 'wgDefaultDateFormat': 'dmy', - 'wgContentLanguage': 'de' + wgDefaultDateFormat: 'dmy', + wgPageContentLanguage: 'de' } ); } ); oldMDYDates = [ - ['January 19, 1400 BC', false, '99999999', 'BC'], - ['January 19, 1400BC', false, '99999999', 'Connected BC'], - ['January, 19 1400 B.C.', false, '99999999', 'B.C.'], - ['January 19, 1400 AD', false, '99999999', 'AD'], - ['January, 19 10', true, 20100119, 'AD'], - ['January, 19 1', false, '99999999', 'AD'] + [ 'January 19, 1400 BC', false, '99999999', 'BC' ], + [ 'January 19, 1400BC', false, '99999999', 'Connected BC' ], + [ 'January, 19 1400 B.C.', false, '99999999', 'B.C.' ], + [ 'January 19, 1400 AD', false, '99999999', 'AD' ], + [ 'January, 19 10', true, 20100119, 'AD' ], + [ 'January, 19 1', false, '99999999', 'AD' ] ]; parserTest( 'Very old MDY dates', 'date', oldMDYDates ); complexMDYDates = [ - ['January, 19 2010', true, 20100119, 'Comma after month'], - ['January 19, 2010', true, 20100119, 'Comma after day'], - ['January/19/2010', true, 20100119, 'Forward slash separator'], - ['04 22 1991', true, 19910422, 'Month with 0 padding'], - ['April 21 1991', true, 19910421, 'Space separation'], - ['04 22 1991', true, 19910422, 'Month with 0 padding'], - ['December 12 \'10', true, 20101212, ''], - ['Dec 12 \'10', true, 20101212, ''], - ['Dec. 12 \'10', true, 20101212, ''] + [ 'January, 19 2010', true, 20100119, 'Comma after month' ], + [ 'January 19, 2010', true, 20100119, 'Comma after day' ], + [ 'January/19/2010', true, 20100119, 'Forward slash separator' ], + [ '04 22 1991', true, 19910422, 'Month with 0 padding' ], + [ 'April 21 1991', true, 19910421, 'Space separation' ], + [ '04 22 1991', true, 19910422, 'Month with 0 padding' ], + [ 'December 12 \'10', true, 20101212, '' ], + [ 'Dec 12 \'10', true, 20101212, '' ], + [ 'Dec. 12 \'10', true, 20101212, '' ] ]; parserTest( 'MDY Dates', 'date', complexMDYDates ); clobberedDates = [ - ['January, 19 2010 - January, 20 2010', false, '99999999', 'Date range with hyphen'], - ['January, 19 2010 — January, 20 2010', false, '99999999', 'Date range with mdash'], - ['prefixJanuary, 19 2010', false, '99999999', 'Connected prefix'], - ['prefix January, 19 2010', false, '99999999', 'Prefix'], - ['December 12 2010postfix', false, '99999999', 'ConnectedPostfix'], - ['December 12 2010 postfix', false, '99999999', 'Postfix'], - ['A simple text', false, '99999999', 'Plain text in date sort'], - ['04l22l1991', false, '99999999', 'l char as separator'], - ['January\\19\\2010', false, '99999999', 'backslash as date separator'] + [ 'January, 19 2010 - January, 20 2010', false, '99999999', 'Date range with hyphen' ], + [ 'January, 19 2010 — January, 20 2010', false, '99999999', 'Date range with mdash' ], + [ 'prefixJanuary, 19 2010', false, '99999999', 'Connected prefix' ], + [ 'prefix January, 19 2010', false, '99999999', 'Prefix' ], + [ 'December 12 2010postfix', false, '99999999', 'ConnectedPostfix' ], + [ 'December 12 2010 postfix', false, '99999999', 'Postfix' ], + [ 'A simple text', false, '99999999', 'Plain text in date sort' ], + [ '04l22l1991', false, '99999999', 'l char as separator' ], + [ 'January\\19\\2010', false, '99999999', 'backslash as date separator' ] ]; parserTest( 'Clobbered Dates', 'date', clobberedDates ); MYDates = [ - ['December 2010', false, '99999999', 'Plain month year'], - ['Dec 2010', false, '99999999', 'Abreviated month year'], - ['12 2010', false, '99999999', 'Numeric month year'] + [ 'December 2010', false, '99999999', 'Plain month year' ], + [ 'Dec 2010', false, '99999999', 'Abreviated month year' ], + [ '12 2010', false, '99999999', 'Numeric month year' ] ]; parserTest( 'MY Dates', 'date', MYDates ); YDates = [ - ['2010', false, '99999999', 'Plain 4-digit year'], - ['876', false, '99999999', '3-digit year'], - ['76', false, '99999999', '2-digit year'], - ['\'76', false, '99999999', '2-digit millenium bug year'], - ['2010 BC', false, '99999999', '4-digit year BC'] + [ '2010', false, '99999999', 'Plain 4-digit year' ], + [ '876', false, '99999999', '3-digit year' ], + [ '76', false, '99999999', '2-digit year' ], + [ '\'76', false, '99999999', '2-digit millenium bug year' ], + [ '2010 BC', false, '99999999', '4-digit year BC' ] ]; parserTest( 'Y Dates', 'date', YDates ); currencyData = [ - ['1.02 $', true, 1.02, ''], - ['$ 3.00', true, 3, ''], - ['€ 2,99', true, 299, ''], - ['$ 1.00', true, 1, ''], - ['$3.50', true, 3.50, ''], - ['$ 1.50', true, 1.50, ''], - ['€ 0.99', true, 0.99, ''], - ['$ 299.99', true, 299.99, ''], - ['$ 2,299.99', true, 2299.99, ''], - ['$ 2,989', true, 2989, ''], - ['$ 2 299.99', true, 2299.99, ''], - ['$ 2 989', true, 2989, ''], - ['$ 2.989', true, 2.989, ''] + [ '1.02 $', true, 1.02, '' ], + [ '$ 3.00', true, 3, '' ], + [ '€ 2,99', true, 299, '' ], + [ '$ 1.00', true, 1, '' ], + [ '$3.50', true, 3.50, '' ], + [ '$ 1.50', true, 1.50, '' ], + [ '€ 0.99', true, 0.99, '' ], + [ '$ 299.99', true, 299.99, '' ], + [ '$ 2,299.99', true, 2299.99, '' ], + [ '$ 2,989', true, 2989, '' ], + [ '$ 2 299.99', true, 2299.99, '' ], + [ '$ 2 989', true, 2989, '' ], + [ '$ 2.989', true, 2.989, '' ] ]; parserTest( 'Currency', 'currency', currencyData ); transformedCurrencyData = [ - ['1.02 $', true, 102, ''], - ['$ 3.00', true, 300, ''], - ['€ 2,99', true, 2.99, ''], - ['$ 1.00', true, 100, ''], - ['$3.50', true, 350, ''], - ['$ 1.50', true, 150, ''], - ['€ 0.99', true, 99, ''], - ['$ 299.99', true, 29999, ''], - ['$ 2\'299,99', true, 2299.99, ''], - ['$ 2,989', true, 2.989, ''], - ['$ 2 299.99', true, 229999, ''], - ['2 989 $', true, 2989, ''], - ['299.99 $', true, 29999, ''], - ['2\'299,99 $', true, 2299.99, ''], - ['2,989 $', true, 2.989, ''], - ['2 299.99 $', true, 229999, ''], - ['2 989 $', true, 2989, ''] + [ '1.02 $', true, 102, '' ], + [ '$ 3.00', true, 300, '' ], + [ '€ 2,99', true, 2.99, '' ], + [ '$ 1.00', true, 100, '' ], + [ '$3.50', true, 350, '' ], + [ '$ 1.50', true, 150, '' ], + [ '€ 0.99', true, 99, '' ], + [ '$ 299.99', true, 29999, '' ], + [ '$ 2\'299,99', true, 2299.99, '' ], + [ '$ 2,989', true, 2.989, '' ], + [ '$ 2 299.99', true, 229999, '' ], + [ '2 989 $', true, 2989, '' ], + [ '299.99 $', true, 29999, '' ], + [ '2\'299,99 $', true, 2299.99, '' ], + [ '2,989 $', true, 2.989, '' ], + [ '2 299.99 $', true, 229999, '' ], + [ '2 989 $', true, 2989, '' ] ]; parserTest( 'Currency with european separators', 'currency', transformedCurrencyData, function () { mw.config.set( { // We expect 22'234.444,22 // Map from ascii separators => localized separators - wgSeparatorTransformTable: [', . ,', '\' , .'], - wgDigitTransformTable: ['', ''] + wgSeparatorTransformTable: [ ', . ,', '\' , .' ], + wgDigitTransformTable: [ '', '' ] } ); } ); diff --git a/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js index f63aa27a..d4d0ce1e 100644 --- a/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js @@ -1,5 +1,19 @@ ( function ( $, mw ) { - var header, + var header = [ 'Planet', 'Radius (km)' ], + + // Data set "planets" + mercury = [ 'Mercury', '2439.7' ], + venus = [ 'Venus', '6051.8' ], + earth = [ 'Earth', '6371.0' ], + mars = [ 'Mars', '3390.0' ], + jupiter = [ 'Jupiter', '69911' ], + saturn = [ 'Saturn', '58232' ], + planets = [ mercury, venus, earth, mars, jupiter, saturn ], + planetsAscName = [ earth, jupiter, mars, mercury, saturn, venus ], + planetsAscRadius = [ mercury, mars, venus, earth, saturn, jupiter ], + planetsRowspan, + planetsRowspanII, + planetsAscNameLegacy, // Data set "simple" a1 = [ 'A', '1' ], @@ -8,11 +22,12 @@ b1 = [ 'B', '1' ], b2 = [ 'B', '2' ], b3 = [ 'B', '3' ], - simple = [a2, b3, a1, a3, b2, b1], - simpleAsc = [a1, a2, a3, b1, b2, b3], - simpleDescasc = [b1, b2, b3, a1, a2, a3], + simple = [ a2, b3, a1, a3, b2, b1 ], + simpleAsc = [ a1, a2, a3, b1, b2, b3 ], + simpleDescasc = [ b1, b2, b3, a1, a2, a3 ], // Data set "colspan" + header4 = [ 'column1a', 'column1b', 'column1c', 'column2' ], aaa1 = [ 'A', 'A', 'A', '1' ], aab5 = [ 'A', 'A', 'B', '5' ], abc3 = [ 'A', 'B', 'C', '3' ], @@ -20,159 +35,145 @@ caa4 = [ 'C', 'A', 'A', '4' ], colspanInitial = [ aab5, aaa1, abc3, bbc2, caa4 ], - // Data set "planets" - mercury = [ 'Mercury', '2439.7' ], - venus = [ 'Venus', '6051.8' ], - earth = [ 'Earth', '6371.0' ], - mars = [ 'Mars', '3390.0' ], - jupiter = [ 'Jupiter', '69911' ], - saturn = [ 'Saturn', '58232' ], - planets = [mercury, venus, earth, mars, jupiter, saturn], - planetsAscName = [earth, jupiter, mars, mercury, saturn, venus], - planetsAscRadius = [mercury, mars, venus, earth, saturn, jupiter], - planetsRowspan, - planetsRowspanII, - planetsAscNameLegacy, - // Data set "ipv4" ipv4 = [ // Some randomly generated fake IPs - ['45.238.27.109'], - ['44.172.9.22'], - ['247.240.82.209'], - ['204.204.132.158'], - ['170.38.91.162'], - ['197.219.164.9'], - ['45.68.154.72'], - ['182.195.149.80'] + [ '45.238.27.109' ], + [ '44.172.9.22' ], + [ '247.240.82.209' ], + [ '204.204.132.158' ], + [ '170.38.91.162' ], + [ '197.219.164.9' ], + [ '45.68.154.72' ], + [ '182.195.149.80' ] ], ipv4Sorted = [ // Sort order should go octet by octet - ['44.172.9.22'], - ['45.68.154.72'], - ['45.238.27.109'], - ['170.38.91.162'], - ['182.195.149.80'], - ['197.219.164.9'], - ['204.204.132.158'], - ['247.240.82.209'] + [ '44.172.9.22' ], + [ '45.68.154.72' ], + [ '45.238.27.109' ], + [ '170.38.91.162' ], + [ '182.195.149.80' ], + [ '197.219.164.9' ], + [ '204.204.132.158' ], + [ '247.240.82.209' ] ], // Data set "umlaut" umlautWords = [ - ['Günther'], - ['Peter'], - ['Björn'], - ['Bjorn'], - ['Apfel'], - ['Äpfel'], - ['Strasse'], - ['Sträßschen'] + [ 'Günther' ], + [ 'Peter' ], + [ 'Björn' ], + [ 'Bjorn' ], + [ 'Apfel' ], + [ 'Äpfel' ], + [ 'Strasse' ], + [ 'Sträßschen' ] ], umlautWordsSorted = [ - ['Äpfel'], - ['Apfel'], - ['Björn'], - ['Bjorn'], - ['Günther'], - ['Peter'], - ['Sträßschen'], - ['Strasse'] + [ 'Äpfel' ], + [ 'Apfel' ], + [ 'Björn' ], + [ 'Bjorn' ], + [ 'Günther' ], + [ 'Peter' ], + [ 'Sträßschen' ], + [ 'Strasse' ] ], complexMDYDates = [ - ['January, 19 2010'], - ['April 21 1991'], - ['04 22 1991'], - ['5.12.1990'], - ['December 12 \'10'] + [ 'January, 19 2010' ], + [ 'April 21 1991' ], + [ '04 22 1991' ], + [ '5.12.1990' ], + [ 'December 12 \'10' ] ], complexMDYSorted = [ - ['5.12.1990'], - ['April 21 1991'], - ['04 22 1991'], - ['January, 19 2010'], - ['December 12 \'10'] + [ '5.12.1990' ], + [ 'April 21 1991' ], + [ '04 22 1991' ], + [ 'January, 19 2010' ], + [ 'December 12 \'10' ] ], currencyUnsorted = [ - ['1.02 $'], - ['$ 3.00'], - ['€ 2,99'], - ['$ 1.00'], - ['$3.50'], - ['$ 1.50'], - ['€ 0.99'] + [ '1.02 $' ], + [ '$ 3.00' ], + [ '€ 2,99' ], + [ '$ 1.00' ], + [ '$3.50' ], + [ '$ 1.50' ], + [ '€ 0.99' ] ], currencySorted = [ - ['€ 0.99'], - ['$ 1.00'], - ['1.02 $'], - ['$ 1.50'], - ['$ 3.00'], - ['$3.50'], + [ '€ 0.99' ], + [ '$ 1.00' ], + [ '1.02 $' ], + [ '$ 1.50' ], + [ '$ 3.00' ], + [ '$3.50' ], // Comma's sort after dots // Not intentional but test to detect changes - ['€ 2,99'] + [ '€ 2,99' ] ], numbers = [ - [ '12' ], - [ '7' ], - [ '13,000'], - [ '9' ], - [ '14' ], - [ '8.0' ] + [ '12' ], + [ '7' ], + [ '13,000' ], + [ '9' ], + [ '14' ], + [ '8.0' ] ], numbersAsc = [ - [ '7' ], - [ '8.0' ], - [ '9' ], - [ '12' ], - [ '14' ], - [ '13,000'] + [ '7' ], + [ '8.0' ], + [ '9' ], + [ '12' ], + [ '14' ], + [ '13,000' ] ], correctDateSorting1 = [ - ['01 January 2010'], - ['05 February 2010'], - ['16 January 2010'] + [ '01 January 2010' ], + [ '05 February 2010' ], + [ '16 January 2010' ] ], correctDateSortingSorted1 = [ - ['01 January 2010'], - ['16 January 2010'], - ['05 February 2010'] + [ '01 January 2010' ], + [ '16 January 2010' ], + [ '05 February 2010' ] ], correctDateSorting2 = [ - ['January 01 2010'], - ['February 05 2010'], - ['January 16 2010'] + [ 'January 01 2010' ], + [ 'February 05 2010' ], + [ 'January 16 2010' ] ], correctDateSortingSorted2 = [ - ['January 01 2010'], - ['January 16 2010'], - ['February 05 2010'] + [ 'January 01 2010' ], + [ 'January 16 2010' ], + [ 'February 05 2010' ] ]; QUnit.module( 'jquery.tablesorter', QUnit.newMwEnvironment( { setup: function () { this.liveMonths = mw.language.months; mw.language.months = { - 'keys': { - 'names': ['january', 'february', 'march', 'april', 'may_long', 'june', - 'july', 'august', 'september', 'october', 'november', 'december'], - 'genitive': ['january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen', - 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen', 'december-gen'], - 'abbrev': ['jan', 'feb', 'mar', 'apr', 'may', 'jun', - 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'] + keys: { + names: [ 'january', 'february', 'march', 'april', 'may_long', 'june', + 'july', 'august', 'september', 'october', 'november', 'december' ], + genitive: [ 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen', + 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen', 'december-gen' ], + abbrev: [ 'jan', 'feb', 'mar', 'apr', 'may', 'jun', + 'jul', 'aug', 'sep', 'oct', 'nov', 'dec' ] }, - 'names': ['January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', 'November', 'December'], - 'genitive': ['January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', 'November', 'December'], - 'abbrev': ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + names: [ 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' ], + genitive: [ 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' ], + abbrev: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ] }; }, teardown: function () { @@ -180,9 +181,9 @@ }, config: { wgDefaultDateFormat: 'dmy', - wgSeparatorTransformTable: ['', ''], - wgDigitTransformTable: ['', ''], - wgContentLanguage: 'en' + wgSeparatorTransformTable: [ '', '' ], + wgDigitTransformTable: [ '', '' ], + wgPageContentLanguage: 'en' } } ) ); @@ -192,7 +193,7 @@ * * @param {String[]} header * @param {String[][]} data - * @return jQuery + * @return {jQuery} */ function tableCreate( header, data ) { var i, @@ -210,7 +211,7 @@ for ( i = 0; i < data.length; i++ ) { /*jshint loopfunc: true */ $tr = $( '<tr>' ); - $.each( data[i], function ( j, str ) { + $.each( data[ i ], function ( j, str ) { var $td = $( '<td>' ); $td.text( str ).appendTo( $tr ); } ); @@ -223,7 +224,7 @@ * Extract text from table. * * @param {jQuery} $table - * @return String[][] + * @return {String[][]} */ function tableExtract( $table ) { var data = []; @@ -302,7 +303,6 @@ } // Sample data set using planets named and their radius - header = [ 'Planet', 'Radius (km)']; tableTest( 'Basic planet table: sorting initially - ascending by name', @@ -388,9 +388,6 @@ $table.find( '.headerSort:eq(1)' ).click().click(); } ); - - header = [ 'column1', 'column2' ]; - tableTest( 'Sorting multiple columns by passing sort list', header, @@ -466,7 +463,7 @@ // Pretend to click while pressing the multi-sort key var event = $.Event( 'click' ); - event[$table.data( 'tablesorter' ).config.sortMultiSortKey] = true; + event[ $table.data( 'tablesorter' ).config.sortMultiSortKey ] = true; $table.find( '.headerSort:eq(1)' ).trigger( event ); } ); @@ -500,10 +497,9 @@ } ); // Sorting with colspans - header = [ 'column1a', 'column1b', 'column1c', 'column2' ]; tableTest( 'Sorting with colspanned headers: spanned column', - header, + header4, colspanInitial, [ aaa1, aab5, abc3, bbc2, caa4 ], function ( $table ) { @@ -516,7 +512,7 @@ } ); tableTest( 'Sorting with colspanned headers: sort spanned column twice', - header, + header4, colspanInitial, [ caa4, bbc2, abc3, aab5, aaa1 ], function ( $table ) { @@ -530,7 +526,7 @@ } ); tableTest( 'Sorting with colspanned headers: subsequent column', - header, + header4, colspanInitial, [ aaa1, bbc2, abc3, caa4, aab5 ], function ( $table ) { @@ -543,7 +539,7 @@ } ); tableTest( 'Sorting with colspanned headers: sort subsequent column twice', - header, + header4, colspanInitial, [ aab5, caa4, abc3, bbc2, aaa1 ], function ( $table ) { @@ -557,42 +553,60 @@ } ); - tableTest( - 'Basic planet table: one unsortable column', - header, - planets, - planets, - function ( $table ) { - $table.find( 'tr:eq(0) > th:eq(0)' ).addClass( 'unsortable' ); + QUnit.test( 'Basic planet table: one unsortable column', 3, function ( assert ) { + var $table = tableCreate( header, planets ), + $cell; + $table.find( 'tr:eq(0) > th:eq(0)' ).addClass( 'unsortable' ); - $table.tablesorter(); - $table.find( 'tr:eq(0) > th:eq(0)' ).click(); - } - ); + $table.tablesorter(); + $table.find( 'tr:eq(0) > th:eq(0)' ).click(); + + assert.deepEqual( + tableExtract( $table ), + planets, + 'table not sorted' + ); + + $cell = $table.find( 'tr:eq(0) > th:eq(0)' ); + $table.find( 'tr:eq(0) > th:eq(1)' ).click(); + + assert.equal( + $cell.hasClass( 'headerSortUp' ) || $cell.hasClass( 'headerSortDown' ), + false, + 'after sort: no class headerSortUp or headerSortDown' + ); + + assert.equal( + $cell.attr( 'title' ), + undefined, + 'after sort: no title tag added' + ); + + } ); // Regression tests! tableTest( 'Bug 28775: German-style (dmy) short numeric dates', - ['Date'], + [ 'Date' ], [ // German-style dates are day-month-year - ['11.11.2011'], - ['01.11.2011'], - ['02.10.2011'], - ['03.08.2011'], - ['09.11.2011'] + [ '11.11.2011' ], + [ '01.11.2011' ], + [ '02.10.2011' ], + [ '03.08.2011' ], + [ '09.11.2011' ] ], [ // Sorted by ascending date - ['03.08.2011'], - ['02.10.2011'], - ['01.11.2011'], - ['09.11.2011'], - ['11.11.2011'] + [ '03.08.2011' ], + [ '02.10.2011' ], + [ '01.11.2011' ], + [ '09.11.2011' ], + [ '11.11.2011' ] ], function ( $table ) { mw.config.set( 'wgDefaultDateFormat', 'dmy' ); - mw.config.set( 'wgContentLanguage', 'de' ); + mw.config.set( 'wgPageContentLanguage', 'de' ); $table.tablesorter(); $table.find( '.headerSort:eq(0)' ).click(); @@ -601,22 +615,22 @@ tableTest( 'Bug 28775: American-style (mdy) short numeric dates', - ['Date'], + [ 'Date' ], [ // American-style dates are month-day-year - ['11.11.2011'], - ['01.11.2011'], - ['02.10.2011'], - ['03.08.2011'], - ['09.11.2011'] + [ '11.11.2011' ], + [ '01.11.2011' ], + [ '02.10.2011' ], + [ '03.08.2011' ], + [ '09.11.2011' ] ], [ // Sorted by ascending date - ['01.11.2011'], - ['02.10.2011'], - ['03.08.2011'], - ['09.11.2011'], - ['11.11.2011'] + [ '01.11.2011' ], + [ '02.10.2011' ], + [ '03.08.2011' ], + [ '09.11.2011' ], + [ '11.11.2011' ] ], function ( $table ) { mw.config.set( 'wgDefaultDateFormat', 'mdy' ); @@ -628,7 +642,7 @@ tableTest( 'Bug 17141: IPv4 address sorting', - ['IP'], + [ 'IP' ], ipv4, ipv4Sorted, function ( $table ) { @@ -638,7 +652,7 @@ ); tableTest( 'Bug 17141: IPv4 address sorting (reverse)', - ['IP'], + [ 'IP' ], ipv4, reversed( ipv4Sorted ), function ( $table ) { @@ -649,15 +663,15 @@ tableTest( 'Accented Characters with custom collation', - ['Name'], + [ 'Name' ], umlautWords, umlautWordsSorted, function ( $table ) { mw.config.set( 'tableSorterCollation', { - 'ä': 'ae', - 'ö': 'oe', - 'ß': 'ss', - 'ü': 'ue' + ä: 'ae', + ö: 'oe', + ß: 'ss', + ü: 'ue' } ); $table.tablesorter(); @@ -749,7 +763,7 @@ tableTest( 'Complex date parsing I', - ['date'], + [ 'date' ], complexMDYDates, complexMDYSorted, function ( $table ) { @@ -762,7 +776,7 @@ tableTest( 'Currency parsing I', - ['currency'], + [ 'currency' ], currencyUnsorted, currencySorted, function ( $table ) { @@ -772,7 +786,7 @@ ); planetsAscNameLegacy = planetsAscName.slice( 0 ); - planetsAscNameLegacy[4] = planetsAscNameLegacy[5]; + planetsAscNameLegacy[ 4 ] = planetsAscNameLegacy[ 5 ]; planetsAscNameLegacy.pop(); tableTest( @@ -801,7 +815,7 @@ $table.find( '.headerSort:eq(0)' ).click(); assert.equal( - $table.data( 'tablesorter' ).config.parsers[0].id, + $table.data( 'tablesorter' ).config.parsers[ 0 ].id, 'number', 'Correctly detected column content skipping sortbottom' ); @@ -989,7 +1003,7 @@ } ); tableTest( 'bug 8115: sort numbers with commas (ascending)', - ['Numbers'], numbers, numbersAsc, + [ 'Numbers' ], numbers, numbersAsc, function ( $table ) { $table.tablesorter(); $table.find( '.headerSort:eq(0)' ).click(); @@ -997,7 +1011,7 @@ ); tableTest( 'bug 8115: sort numbers with commas (descending)', - ['Numbers'], numbers, reversed( numbersAsc ), + [ 'Numbers' ], numbers, reversed( numbersAsc ), function ( $table ) { $table.tablesorter(); $table.find( '.headerSort:eq(0)' ).click().click(); @@ -1032,7 +1046,7 @@ tableTest( 'Correct date sorting I', - ['date'], + [ 'date' ], correctDateSorting1, correctDateSortingSorted1, function ( $table ) { @@ -1045,7 +1059,7 @@ tableTest( 'Correct date sorting II', - ['date'], + [ 'date' ], correctDateSorting2, correctDateSortingSorted2, function ( $table ) { @@ -1238,7 +1252,7 @@ '</tbody></table>' ); $table.tablesorter(); - assert.equal( $table.find( 'tr:eq(1) th:eq(1)').data('headerIndex'), + assert.equal( $table.find( 'tr:eq(1) th:eq(1)' ).data( 'headerIndex' ), 2, 'Incorrect index of sort header' ); @@ -1262,14 +1276,14 @@ tableTestHTML( 'Rowspan exploding with colspanned cells (2)', '<table class="sortable">' + - '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th><th>quux</th></tr></thead>' + + '<thead><tr><th>n</th><th>foo</th><th>bar</th><th>baz</th><th id="sortme">n2</th></tr></thead>' + '<tbody>' + - '<tr><td>1</td><td>foo</td><td>bar</td><td rowspan="2">baz</td><td>quux</td></tr>' + - '<tr><td>2</td><td colspan="2">foobar</td><td>quux</td></tr>' + + '<tr><td>1</td><td>foo</td><td>bar</td><td rowspan="2">baz</td><td>2</td></tr>' + + '<tr><td>2</td><td colspan="2">foobar</td><td>1</td></tr>' + '</tbody></table>', [ - [ '1', 'foo', 'bar', 'baz', 'quux' ], - [ '2', 'foobar', 'baz', 'quux' ] + [ '2', 'foobar', 'baz', '1' ], + [ '1', 'foo', 'bar', 'baz', '2' ] ] ); @@ -1345,4 +1359,39 @@ ] ); + QUnit.test( 'bug 105731 - incomplete rows in table body', 3, function ( assert ) { + var $table, parsers; + $table = $( + '<table class="sortable">' + + '<tr><th>A</th><th>B</th></tr>' + + '<tr><td>3</td></tr>' + + '<tr><td>1</td><td>2</td></tr>' + + '</table>' + ); + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + // now the first row have 2 columns + $table.find( '.headerSort:eq(1)' ).click(); + + parsers = $table.data( 'tablesorter' ).config.parsers; + + assert.equal( + parsers.length, + 2, + 'detectParserForColumn() detect 2 parsers' + ); + + assert.equal( + parsers[ 1 ].id, + 'number', + 'detectParserForColumn() detect parser.id "number" for second column' + ); + + assert.equal( + parsers[ 1 ].format( $table.find( 'tbody > tr > td:eq(1)' ).text() ), + 0, + 'empty cell is sorted as number 0' + ); + + } ); }( jQuery, mediaWiki ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js b/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js index 56b0fa92..2e6f05ed 100644 --- a/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js @@ -5,13 +5,13 @@ /** * Test factory for $.fn.textSelection( 'encapsulateText' ) * - * @param options {object} associative array containing: - * description {string} - * input {string} - * output {string} - * start {int} starting char for selection - * end {int} ending char for selection - * params {object} add'l parameters for $().textSelection( 'encapsulateText' ) + * @param {Object} options Associative configuration array + * @param {string} options.description Description + * @param {string} options.input Input + * @param {string} options.output Output + * @param {int} options.start Starting char for selection + * @param {int} options.end Ending char for selection + * @param {object} options.params Additional parameters for $().textSelection( 'encapsulateText' ) */ function encapsulateTest( options ) { var opt = $.extend( { @@ -237,23 +237,22 @@ } pos = $textarea.textSelection( 'getCaretPosition', { startAndEnd: true } ); - among( pos[0], options.start, 'Caret start should be where we set it.' ); - among( pos[1], options.end, 'Caret end should be where we set it.' ); + among( pos[ 0 ], options.start, 'Caret start should be where we set it.' ); + among( pos[ 1 ], options.end, 'Caret end should be where we set it.' ); } ); } caretSample = 'Some big text that we like to work with. Nothing fancy... you know what I mean?'; -/* - // @broken: Disabled per bug 34820 + /* @broken: Disabled per bug 34820 caretTest({ - description: 'getCaretPosition with original/empty selection - bug 31847 with IE 6/7/8', - text: caretSample, - start: [0, caretSample.length], // Opera and Firefox (prior to FF 6.0) default caret to the end of the box (caretSample.length) - end: [0, caretSample.length], // Other browsers default it to the beginning (0), so check both. - mode: 'get' + description: 'getCaretPosition with original/empty selection - bug 31847 with IE 6/7/8', + text: caretSample, + start: [0, caretSample.length], // Opera and Firefox (prior to FF 6.0) default caret to the end of the box (caretSample.length) + end: [0, caretSample.length], // Other browsers default it to the beginning (0), so check both. + mode: 'get' }); -*/ + */ caretTest( { description: 'set/getCaretPosition with forced empty selection', diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.ForeignApi.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.ForeignApi.test.js new file mode 100644 index 00000000..9d0fdf54 --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.ForeignApi.test.js @@ -0,0 +1,39 @@ +( function ( mw ) { + QUnit.module( 'mediawiki.ForeignApi', QUnit.newMwEnvironment( { + setup: function () { + this.server = this.sandbox.useFakeServer(); + this.server.respondImmediately = true; + this.clock = this.sandbox.useFakeTimers(); + }, + teardown: function () { + // https://github.com/jquery/jquery/issues/2453 + this.clock.tick(); + } + } ) ); + + QUnit.test( 'origin is included in GET requests', function ( assert ) { + QUnit.expect( 1 ); + var api = new mw.ForeignApi( '//localhost:4242/w/api.php' ); + + this.server.respond( function ( request ) { + assert.ok( request.url.match( /origin=/ ), 'origin is included in GET requests' ); + request.respond( 200, { 'Content-Type': 'application/json' }, '[]' ); + } ); + + api.get( {} ); + } ); + + QUnit.test( 'origin is included in POST requests', function ( assert ) { + QUnit.expect( 2 ); + var api = new mw.ForeignApi( '//localhost:4242/w/api.php' ); + + this.server.respond( function ( request ) { + assert.ok( request.requestBody.match( /origin=/ ), 'origin is included in POST request body' ); + assert.ok( request.url.match( /origin=/ ), 'origin is included in POST request URL, too' ); + request.respond( 200, { 'Content-Type': 'application/json' }, '[]' ); + } ); + + api.post( {} ); + } ); + +}( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js index b89526fb..56a346fb 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js @@ -1,15 +1,40 @@ -( function ( mw ) { +( function ( mw, $ ) { QUnit.module( 'mediawiki.api', QUnit.newMwEnvironment( { setup: function () { this.server = this.sandbox.useFakeServer(); + this.server.respondImmediately = true; + this.clock = this.sandbox.useFakeTimers(); + }, + teardown: function () { + // https://github.com/jquery/jquery/issues/2453 + this.clock.tick(); } } ) ); + function sequence( responses ) { + var i = 0; + return function ( request ) { + var response = responses[ i ]; + if ( response ) { + i++; + request.respond.apply( request, response ); + } + }; + } + + function sequenceBodies( status, headers, bodies ) { + jQuery.each( bodies, function ( i, body ) { + bodies[ i ] = [ status, headers, body ]; + } ); + return sequence( bodies ); + } + QUnit.test( 'Basic functionality', function ( assert ) { QUnit.expect( 2 ); - var api = new mw.Api(); + this.server.respond( [ 200, { 'Content-Type': 'application/json' }, '[]' ] ); + api.get( {} ) .done( function ( data ) { assert.deepEqual( data, [], 'If request succeeds without errors, resolve deferred' ); @@ -19,36 +44,26 @@ .done( function ( data ) { assert.deepEqual( data, [], 'Simple POST request' ); } ); - - this.server.respond( function ( request ) { - request.respond( 200, { 'Content-Type': 'application/json' }, '[]' ); - } ); } ); QUnit.test( 'API error', function ( assert ) { QUnit.expect( 1 ); - var api = new mw.Api(); + this.server.respond( [ 200, { 'Content-Type': 'application/json' }, + '{ "error": { "code": "unknown_action" } }' + ] ); + api.get( { action: 'doesntexist' } ) .fail( function ( errorCode ) { assert.equal( errorCode, 'unknown_action', 'API error should reject the deferred' ); } ); - - this.server.respond( function ( request ) { - request.respond( 200, { 'Content-Type': 'application/json' }, - '{ "error": { "code": "unknown_action" } }' - ); - } ); } ); QUnit.test( 'FormData support', function ( assert ) { QUnit.expect( 2 ); - var api = new mw.Api(); - api.post( { action: 'test' }, { contentType: 'multipart/form-data' } ); - this.server.respond( function ( request ) { if ( window.FormData ) { assert.ok( !request.url.match( /action=/ ), 'Request has no query string' ); @@ -59,27 +74,41 @@ } request.respond( 200, { 'Content-Type': 'application/json' }, '[]' ); } ); + + api.post( { action: 'test' }, { contentType: 'multipart/form-data' } ); } ); QUnit.test( 'Converting arrays to pipe-separated', function ( assert ) { QUnit.expect( 1 ); - var api = new mw.Api(); - api.get( { test: [ 'foo', 'bar', 'baz' ] } ); this.server.respond( function ( request ) { assert.ok( request.url.match( /test=foo%7Cbar%7Cbaz/ ), 'Pipe-separated value was submitted' ); request.respond( 200, { 'Content-Type': 'application/json' }, '[]' ); } ); + + api.get( { test: [ 'foo', 'bar', 'baz' ] } ); } ); - QUnit.test( 'getToken( pre-populated )', function ( assert ) { + QUnit.test( 'Omitting false booleans', function ( assert ) { QUnit.expect( 2 ); + var api = new mw.Api(); + + this.server.respond( function ( request ) { + assert.ok( !request.url.match( /foo/ ), 'foo query parameter is not present' ); + assert.ok( request.url.match( /bar=true/ ), 'bar query parameter is present with value true' ); + request.respond( 200, { 'Content-Type': 'application/json' }, '[]' ); + } ); + api.get( { foo: false, bar: true } ); + } ); + + QUnit.test( 'getToken() - cached', function ( assert ) { + QUnit.expect( 2 ); var api = new mw.Api(); // Get editToken for local wiki, this should not make - // a request as it should be retrieved from user.tokens. + // a request as it should be retrieved from mw.user.tokens. api.getToken( 'edit' ) .done( function ( token ) { assert.ok( token.length, 'Got a token' ); @@ -91,112 +120,128 @@ assert.equal( this.server.requests.length, 0, 'Requests made' ); } ); - QUnit.test( 'getToken()', function ( assert ) { - QUnit.expect( 5 ); + QUnit.test( 'getToken() - uncached', function ( assert ) { + QUnit.expect( 3 ); + var api = new mw.Api(); - var test = this, - api = new mw.Api(); + this.server.respondWith( /type=testuncached/, [ 200, { 'Content-Type': 'application/json' }, + '{ "tokens": { "testuncachedtoken": "good" } }' + ] ); // Get a token of a type that isn't prepopulated by user.tokens. // Could use "block" or "delete" here, but those could in theory // be added to user.tokens, use a fake one instead. - api.getToken( 'testaction' ) + api.getToken( 'testuncached' ) .done( function ( token ) { - assert.ok( token.length, 'Got testaction token' ); + assert.equal( token, 'good', 'The token' ); } ) .fail( function ( err ) { assert.equal( err, '', 'API error' ); } ); - api.getToken( 'testaction' ) + + api.getToken( 'testuncached' ) .done( function ( token ) { - assert.ok( token.length, 'Got testaction token (cached)' ); + assert.equal( token, 'good', 'The cached token' ); } ) .fail( function ( err ) { assert.equal( err, '', 'API error' ); } ); + assert.equal( this.server.requests.length, 1, 'Requests made' ); + } ); + + QUnit.test( 'getToken() - error', function ( assert ) { + QUnit.expect( 2 ); + var api = new mw.Api(); + + this.server.respondWith( /type=testerror/, sequenceBodies( 200, { 'Content-Type': 'application/json' }, + [ + '{ "error": { "code": "bite-me", "info": "Smite me, O Mighty Smiter" } }', + '{ "tokens": { "testerrortoken": "good" } }' + ] + ) ); + // Don't cache error (bug 65268) - api.getToken( 'testaction2' ) - .fail( function ( err ) { - assert.equal( err, 'bite-me', 'Expected error' ); - } ) - .always( function () { - // Make this request after the first one has finished. - // If we make it simultaneously we still want it to share - // the cache, but as soon as it is fulfilled as error we - // reject it so that the next one tries fresh. - api.getToken( 'testaction2' ) - .done( function ( token ) { - assert.ok( token.length, 'Got testaction2 token (error was not be cached)' ); - } ) - .fail( function ( err ) { - assert.equal( err, '', 'API error' ); - } ); - - assert.equal( test.server.requests.length, 3, 'Requests made' ); - - test.server.requests[2].respond( 200, { 'Content-Type': 'application/json' }, - '{ "tokens": { "testaction2token": "0123abc" } }' - ); + api.getToken( 'testerror' ).fail( function ( err ) { + assert.equal( err, 'bite-me', 'Expected error' ); + + // Make this request after the first one has finished. + // If we make it simultaneously we still want it to share + // the cache, but as soon as it is fulfilled as error we + // reject it so that the next one tries fresh. + api.getToken( 'testerror' ).done( function ( token ) { + assert.equal( token, 'good', 'The token' ); } ); + } ); + } ); - this.server.requests[0].respond( 200, { 'Content-Type': 'application/json' }, - '{ "tokens": { "testactiontoken": "0123abc" } }' - ); + QUnit.test( 'badToken()', function ( assert ) { + QUnit.expect( 2 ); + var api = new mw.Api(), + test = this; + + this.server.respondWith( /type=testbad/, sequenceBodies( 200, { 'Content-Type': 'application/json' }, + [ + '{ "tokens": { "testbadtoken": "bad" } }', + '{ "tokens": { "testbadtoken": "good" } }' + ] + ) ); + + api.getToken( 'testbad' ) + .then( function () { + api.badToken( 'testbad' ); + return api.getToken( 'testbad' ); + } ) + .then( function ( token ) { + assert.equal( token, 'good', 'The token' ); + assert.equal( test.server.requests.length, 2, 'Requests made' ); + } ); - this.server.requests[1].respond( 200, { 'Content-Type': 'application/json' }, - '{ "error": { "code": "bite-me", "info": "Smite me, O Mighty Smiter" } }' - ); } ); QUnit.test( 'postWithToken( tokenType, params )', function ( assert ) { QUnit.expect( 1 ); - var api = new mw.Api( { ajax: { url: '/postWithToken/api.php' } } ); - // - Requests token - // - Performs action=example - api.postWithToken( 'testsimpletoken', { action: 'example', key: 'foo' } ) + this.server.respondWith( 'GET', /type=testpost/, [ 200, { 'Content-Type': 'application/json' }, + '{ "tokens": { "testposttoken": "good" } }' + ] ); + this.server.respondWith( 'POST', /api/, function ( request ) { + if ( request.requestBody.match( /token=good/ ) ) { + request.respond( 200, { 'Content-Type': 'application/json' }, + '{ "example": { "foo": "quux" } }' + ); + } + } ); + + api.postWithToken( 'testpost', { action: 'example', key: 'foo' } ) .done( function ( data ) { assert.deepEqual( data, { example: { foo: 'quux' } } ); } ); - - this.server.requests[0].respond( 200, { 'Content-Type': 'application/json' }, - '{ "tokens": { "testsimpletokentoken": "a-bad-token" } }' - ); - - this.server.requests[1].respond( 200, { 'Content-Type': 'application/json' }, - '{ "example": { "foo": "quux" } }' - ); } ); QUnit.test( 'postWithToken( tokenType, params with assert )', function ( assert ) { QUnit.expect( 2 ); - var api = new mw.Api( { ajax: { url: '/postWithToken/api.php' } } ); - api.postWithToken( 'testasserttoken', { action: 'example', key: 'foo', assert: 'user' } ) + this.server.respondWith( /assert=user/, [ 200, { 'Content-Type': 'application/json' }, + '{ "error": { "code": "assertuserfailed", "info": "Assertion failed" } }' + ] ); + + api.postWithToken( 'testassertpost', { action: 'example', key: 'foo', assert: 'user' } ) .fail( function ( errorCode ) { assert.equal( errorCode, 'assertuserfailed', 'getToken fails assert' ); } ); - assert.equal( this.server.requests.length, 1, 'Request for token made' ); - this.server.respondWith( /assert=user/, function ( request ) { - request.respond( - 200, - { 'Content-Type': 'application/json' }, - '{ "error": { "code": "assertuserfailed", "info": "Assertion failed" } }' - ); - } ); - - this.server.respond(); + assert.equal( this.server.requests.length, 1, 'Requests made' ); } ); QUnit.test( 'postWithToken( tokenType, params, ajaxOptions )', function ( assert ) { QUnit.expect( 3 ); - var api = new mw.Api(); + this.server.respond( [ 200, { 'Content-Type': 'application/json' }, '{ "example": "quux" }' ] ); + api.postWithToken( 'edit', { @@ -223,86 +268,111 @@ } ); assert.equal( this.server.requests.length, 2, 'Request made' ); - assert.equal( this.server.requests[0].requestHeaders['X-Foo'], 'Bar', 'Header sent' ); - - this.server.respond( function ( request ) { - request.respond( 200, { 'Content-Type': 'application/json' }, '{ "example": "quux" }' ); - } ); + assert.equal( this.server.requests[ 0 ].requestHeaders[ 'X-Foo' ], 'Bar', 'Header sent' ); } ); QUnit.test( 'postWithToken() - badtoken', function ( assert ) { QUnit.expect( 1 ); - var api = new mw.Api(); - // - Request: token + this.server.respondWith( /type=testbadtoken/, sequenceBodies( 200, { 'Content-Type': 'application/json' }, + [ + '{ "tokens": { "testbadtokentoken": "bad" } }', + '{ "tokens": { "testbadtokentoken": "good" } }' + ] + ) ); + this.server.respondWith( 'POST', /api/, function ( request ) { + if ( request.requestBody.match( /token=bad/ ) ) { + request.respond( 200, { 'Content-Type': 'application/json' }, + '{ "error": { "code": "badtoken" } }' + ); + } + if ( request.requestBody.match( /token=good/ ) ) { + request.respond( 200, { 'Content-Type': 'application/json' }, + '{ "example": { "foo": "quux" } }' + ); + } + } ); + + // - Request: new token -> bad // - Request: action=example -> badtoken error - // - Request: new token - // - Request: action=example + // - Request: new token -> good + // - Request: action=example -> success api.postWithToken( 'testbadtoken', { action: 'example', key: 'foo' } ) .done( function ( data ) { assert.deepEqual( data, { example: { foo: 'quux' } } ); } ); - - this.server.requests[0].respond( 200, { 'Content-Type': 'application/json' }, - '{ "tokens": { "testbadtokentoken": "a-bad-token" } }' - ); - - this.server.requests[1].respond( 200, { 'Content-Type': 'application/json' }, - '{ "error": { "code": "badtoken" } }' - ); - - this.server.requests[2].respond( 200, { 'Content-Type': 'application/json' }, - '{ "tokens": { "testbadtokentoken": "a-good-token" } }' - ); - - this.server.requests[3].respond( 200, { 'Content-Type': 'application/json' }, - '{ "example": { "foo": "quux" } }' - ); - } ); QUnit.test( 'postWithToken() - badtoken-cached', function ( assert ) { QUnit.expect( 2 ); + var sequenceA, + api = new mw.Api(); - var api = new mw.Api(); + this.server.respondWith( /type=testonce/, sequenceBodies( 200, { 'Content-Type': 'application/json' }, + [ + '{ "tokens": { "testoncetoken": "good-A" } }', + '{ "tokens": { "testoncetoken": "good-B" } }' + ] + ) ); + sequenceA = sequenceBodies( 200, { 'Content-Type': 'application/json' }, + [ + '{ "example": { "value": "A" } }', + '{ "error": { "code": "badtoken" } }' + ] + ); + this.server.respondWith( 'POST', /api/, function ( request ) { + if ( request.requestBody.match( /token=good-A/ ) ) { + sequenceA( request ); + } else if ( request.requestBody.match( /token=good-B/ ) ) { + request.respond( 200, { 'Content-Type': 'application/json' }, + '{ "example": { "value": "B" } }' + ); + } + } ); - // - Request: token + // - Request: new token -> A // - Request: action=example - api.postWithToken( 'testbadtokencache', { action: 'example', key: 'foo' } ) + api.postWithToken( 'testonce', { action: 'example', key: 'foo' } ) .done( function ( data ) { - assert.deepEqual( data, { example: { foo: 'quux' } } ); + assert.deepEqual( data, { example: { value: 'A' } } ); } ); - // - Cache: Try previously cached token - // - Request: action=example -> badtoken error - // - Request: new token - // - Request: action=example - api.postWithToken( 'testbadtokencache', { action: 'example', key: 'bar' } ) + // - Request: action=example w/ token A -> badtoken error + // - Request: new token -> B + // - Request: action=example w/ token B -> success + api.postWithToken( 'testonce', { action: 'example', key: 'bar' } ) .done( function ( data ) { - assert.deepEqual( data, { example: { bar: 'quux' } } ); + assert.deepEqual( data, { example: { value: 'B' } } ); } ); + } ); - this.server.requests[0].respond( 200, { 'Content-Type': 'application/json' }, - '{ "tokens": { "testbadtokencachetoken": "a-good-token-once" } }' - ); - - this.server.requests[1].respond( 200, { 'Content-Type': 'application/json' }, - '{ "example": { "foo": "quux" } }' - ); - - this.server.requests[2].respond( 200, { 'Content-Type': 'application/json' }, - '{ "error": { "code": "badtoken" } }' - ); - - this.server.requests[3].respond( 200, { 'Content-Type': 'application/json' }, - '{ "tokens": { "testbadtokencachetoken": "a-good-new-token" } }' - ); - - this.server.requests[4].respond( 200, { 'Content-Type': 'application/json' }, - '{ "example": { "bar": "quux" } }' - ); - + QUnit.module( 'mediawiki.api (2)', { + setup: function () { + var self = this, + requests = this.requests = []; + this.api = new mw.Api(); + this.sandbox.stub( jQuery, 'ajax', function () { + var request = $.extend( { + abort: self.sandbox.spy() + }, $.Deferred() ); + requests.push( request ); + return request; + } ); + } } ); -}( mediaWiki ) ); + QUnit.test( '#abort', 3, function ( assert ) { + this.api.get( { + a: 1 + } ); + this.api.post( { + b: 2 + } ); + this.api.abort(); + assert.ok( this.requests.length === 2, 'Check both requests triggered' ); + $.each( this.requests, function ( i, request ) { + assert.ok( request.abort.calledOnce, 'abort request number ' + i ); + } ); + } ); +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js new file mode 100644 index 00000000..10fcd5da --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js @@ -0,0 +1,35 @@ +( function ( mw, $ ) { + QUnit.module( 'mediawiki.api.upload', QUnit.newMwEnvironment( {} ) ); + + QUnit.test( 'Basic functionality', function ( assert ) { + QUnit.expect( 2 ); + var api = new mw.Api(); + assert.ok( api.upload ); + assert.throws( function () { + api.upload(); + } ); + } ); + + QUnit.test( 'Set up iframe upload', function ( assert ) { + QUnit.expect( 5 ); + var $iframe, $form, $input, + api = new mw.Api(); + + this.sandbox.stub( api, 'getEditToken', function () { + return $.Deferred().promise(); + } ); + + api.uploadWithIframe( $( '<input>' )[ 0 ], { filename: 'Testing API upload.jpg' } ); + + $iframe = $( 'iframe' ); + $form = $( 'form.mw-api-upload-form' ); + $input = $form.find( 'input[name=filename]' ); + + assert.ok( $form.length > 0 ); + assert.ok( $input.length > 0 ); + assert.ok( $iframe.length > 0 ); + assert.strictEqual( $form.prop( 'target' ), $iframe.prop( 'id' ) ); + assert.strictEqual( $input.val(), 'Testing API upload.jpg' ); + } ); + +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.watch.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.watch.test.js index 5965ab7b..64a51847 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.watch.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.watch.test.js @@ -17,12 +17,12 @@ } ); api.watch( [ 'Foo' ] ).done( function ( items ) { - assert.equal( items[0].title, 'Foo' ); + assert.equal( items[ 0 ].title, 'Foo' ); } ); api.watch( [ 'Foo', 'Bar' ] ).done( function ( items ) { - assert.equal( items[0].title, 'Foo' ); - assert.equal( items[1].title, 'Bar' ); + assert.equal( items[ 0 ].title, 'Foo' ); + assert.equal( items[ 1 ].title, 'Bar' ); } ); // Requests are POST, match requestBody instead of url diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js new file mode 100644 index 00000000..2388497a --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js @@ -0,0 +1,38 @@ +( function ( mw, $ ) { + QUnit.module( 'mediawiki.RegExp' ); + + QUnit.test( 'escape', 16, function ( assert ) { + var specials, normal; + + specials = [ + '\\', + '{', + '}', + '(', + ')', + '[', + ']', + '|', + '.', + '?', + '*', + '+', + '-', + '^', + '$' + ]; + + normal = [ + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + 'abcdefghijklmnopqrstuvwxyz', + '0123456789' + ].join( '' ); + + $.each( specials, function ( i, str ) { + assert.propEqual( str.match( new RegExp( mw.RegExp.escape( str ) ) ), [ str ], 'Match ' + str ); + } ); + + assert.equal( mw.RegExp.escape( normal ), normal, 'Alphanumerals are left alone' ); + } ); + +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js index c0afe07c..641a5a5d 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js @@ -100,33 +100,35 @@ // testing custom / localized namespace 100: 'Penguins' }, + // jscs: disable requireCamelCaseOrUpperCaseIdentifiers wgNamespaceIds: { - 'media': -2, - 'special': -1, + media: -2, + special: -1, '': 0, - 'talk': 1, - 'user': 2, - 'user_talk': 3, - 'wikipedia': 4, - 'wikipedia_talk': 5, - 'file': 6, - 'file_talk': 7, - 'mediawiki': 8, - 'mediawiki_talk': 9, - 'template': 10, - 'template_talk': 11, - 'help': 12, - 'help_talk': 13, - 'category': 14, - 'category_talk': 15, - 'image': 6, - 'image_talk': 7, - 'project': 4, - 'project_talk': 5, + talk: 1, + user: 2, + user_talk: 3, + wikipedia: 4, + wikipedia_talk: 5, + file: 6, + file_talk: 7, + mediawiki: 8, + mediawiki_talk: 9, + template: 10, + template_talk: 11, + help: 12, + help_talk: 13, + category: 14, + category_talk: 15, + image: 6, + image_talk: 7, + project: 4, + project_talk: 5, // Testing custom namespaces and aliases - 'penguins': 100, - 'antarctic_waterfowl': 100 + penguins: 100, + antarctic_waterfowl: 100 }, + // jscs: enable requireCamelCaseOrUpperCaseIdentifiers wgCaseSensitiveNamespaces: [] } } ) ); @@ -134,14 +136,14 @@ QUnit.test( 'constructor', cases.invalid.length, function ( assert ) { var i, title; for ( i = 0; i < cases.valid.length; i++ ) { - title = new mw.Title( cases.valid[i] ); + title = new mw.Title( cases.valid[ i ] ); } for ( i = 0; i < cases.invalid.length; i++ ) { /*jshint loopfunc:true */ - title = cases.invalid[i]; + title = cases.invalid[ i ]; assert.throws( function () { return new mw.Title( title ); - }, cases.invalid[i] ); + }, cases.invalid[ i ] ); } } ); @@ -149,21 +151,21 @@ var i; for ( i = 0; i < cases.valid.length; i++ ) { assert.equal( - $.type( mw.Title.newFromText( cases.valid[i] ) ), + $.type( mw.Title.newFromText( cases.valid[ i ] ) ), 'object', - cases.valid[i] + cases.valid[ i ] ); } for ( i = 0; i < cases.invalid.length; i++ ) { assert.equal( - $.type( mw.Title.newFromText( cases.invalid[i] ) ), + $.type( mw.Title.newFromText( cases.invalid[ i ] ) ), 'null', - cases.invalid[i] + cases.invalid[ i ] ); } } ); - QUnit.test( 'Basic parsing', 12, function ( assert ) { + QUnit.test( 'Basic parsing', 21, function ( assert ) { var title; title = new mw.Title( 'File:Foo_bar.JPG' ); @@ -181,6 +183,17 @@ title = new mw.Title( 'Foo#bar' ); assert.equal( title.getPrefixedText(), 'Foo' ); assert.equal( title.getFragment(), 'bar' ); + + title = new mw.Title( '.foo' ); + assert.equal( title.getPrefixedText(), '.foo' ); + assert.equal( title.getName(), '' ); + assert.equal( title.getNameText(), '' ); + assert.equal( title.getExtension(), 'foo' ); + assert.equal( title.getDotExtension(), '.foo' ); + assert.equal( title.getMain(), '.foo' ); + assert.equal( title.getMainText(), '.foo' ); + assert.equal( title.getPrefixedDb(), '.foo' ); + assert.equal( title.getPrefixedText(), '.foo' ); } ); QUnit.test( 'Transformation', 11, function ( assert ) { @@ -266,7 +279,7 @@ assert.equal( title.toString(), 'Article', 'Default config: No sensitive namespaces by default. First-letter becomes uppercase' ); // $wgCapitalLinks = false; - mw.config.set( 'wgCaseSensitiveNamespaces', [0, -2, 1, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15] ); + mw.config.set( 'wgCaseSensitiveNamespaces', [ 0, -2, 1, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15 ] ); title = new mw.Title( 'article' ); assert.equal( title.toString(), 'article', '$wgCapitalLinks=false: Article namespace is sensitive, first-letter case stays lowercase' ); @@ -309,8 +322,8 @@ assert.strictEqual( title.exists(), null, 'Return null with empty existance registry' ); // Basic registry, checks default to boolean - mw.Title.exist.set( ['Does_exist', 'User_talk:NeilK', 'Wikipedia:Sandbox_rules'], true ); - mw.Title.exist.set( ['Does_not_exist', 'User:John', 'Foobar'], false ); + mw.Title.exist.set( [ 'Does_exist', 'User_talk:NeilK', 'Wikipedia:Sandbox_rules' ], true ); + mw.Title.exist.set( [ 'Does_not_exist', 'User:John', 'Foobar' ], false ); title = new mw.Title( 'Project:Sandbox rules' ); assert.assertTrue( title.exists(), 'Return true for page titles marked as existing' ); @@ -327,7 +340,7 @@ title = new mw.Title( 'Foobar' ); assert.equal( title.getUrl(), '/wiki/Foobar', 'Basic functionality, getUrl uses mw.util.getUrl' ); - assert.equal( title.getUrl({ action: 'edit' }), '/wiki/Foobar?action=edit', 'Basic functionality, \'params\' parameter' ); + assert.equal( title.getUrl( { action: 'edit' } ), '/wiki/Foobar?action=edit', 'Basic functionality, \'params\' parameter' ); title = new mw.Title( 'John Doe', 3 ); assert.equal( title.getUrl(), '/wiki/User_talk:John_Doe', 'Escaping in title and namespace for urls' ); @@ -420,7 +433,7 @@ ]; for ( i = 0; i < cases.length; i++ ) { - thisCase = cases[i]; + thisCase = cases[ i ]; title = mw.Title.newFromImg( { src: thisCase.url } ); if ( thisCase.nameText !== undefined ) { @@ -467,28 +480,62 @@ ]; for ( i = 0; i < cases.length; i++ ) { - thisCase = cases[i]; + thisCase = cases[ i ]; title = mw.Title.newFromText( thisCase.text ); assert.equal( title.getRelativeText( thisCase.relativeTo ), thisCase.expectedResult ); } } ); - QUnit.test( 'newFromUserInput', 8, function ( assert ) { + QUnit.test( 'normalizeExtension', 5, function ( assert ) { + var extension, i, thisCase, prefix, + cases = [ + { + extension: 'png', + expected: 'png', + description: 'Extension already in canonical form' + }, + { + extension: 'PNG', + expected: 'png', + description: 'Extension lowercased in canonical form' + }, + { + extension: 'jpeg', + expected: 'jpg', + description: 'Extension changed in canonical form' + }, + { + extension: 'JPEG', + expected: 'jpg', + description: 'Extension lowercased and changed in canonical form' + }, + { + extension: '~~~', + expected: '', + description: 'Extension invalid and discarded' + } + ]; + + for ( i = 0; i < cases.length; i++ ) { + thisCase = cases[ i ]; + extension = mw.Title.normalizeExtension( thisCase.extension ); + + prefix = '[' + thisCase.description + '] '; + assert.equal( extension, thisCase.expected, prefix + 'Extension as expected' ); + } + } ); + + QUnit.test( 'newFromUserInput', 12, function ( assert ) { var title, i, thisCase, prefix, cases = [ { title: 'DCS0001557854455.JPG', - defaultNamespace: 0, - options: { - fileExtension: 'PNG' - }, expected: 'DCS0001557854455.JPG', description: 'Title in normal namespace without anything invalid but with "file extension"' }, { title: 'MediaWiki:Msg-awesome', - defaultNamespace: undefined, expected: 'MediaWiki:Msg-awesome', description: 'Full title (page in MediaWiki namespace) supplied as string' }, @@ -506,11 +553,21 @@ }, expected: 'File:The/Mw/Sound.kml', description: 'Page in File-namespace without explicit options' + }, + { + title: 'File:Foo.JPEG', + expected: 'File:Foo.JPEG', + description: 'Page in File-namespace with non-canonical extension' + }, + { + title: 'File:Foo.JPEG ', + expected: 'File:Foo.JPEG', + description: 'Page in File-namespace with trailing whitespace' } ]; for ( i = 0; i < cases.length; i++ ) { - thisCase = cases[i]; + thisCase = cases[ i ]; title = mw.Title.newFromUserInput( thisCase.title, thisCase.defaultNamespace, thisCase.options ); if ( thisCase.expected !== undefined ) { @@ -524,15 +581,14 @@ } } ); - QUnit.test( 'newFromFileName', 62, function ( assert ) { + QUnit.test( 'newFromFileName', 54, function ( assert ) { var title, i, thisCase, prefix, cases = [ { fileName: 'DCS0001557854455.JPG', typeOfName: 'Standard camera output', nameText: 'DCS0001557854455', - prefixedText: 'File:DCS0001557854455.JPG', - extensionDesired: 'jpg' + prefixedText: 'File:DCS0001557854455.JPG' }, { fileName: 'File:Sample.png', @@ -544,8 +600,7 @@ fileName: 'Treppe 2222 Test upload.jpg', typeOfName: 'File name with spaces in it and lower case file extension', nameText: 'Treppe 2222 Test upload', - prefixedText: 'File:Treppe 2222 Test upload.jpg', - extensionDesired: 'JPG' + prefixedText: 'File:Treppe 2222 Test upload.jpg' }, { fileName: 'I contain a \ttab.jpg', @@ -602,20 +657,6 @@ prefixedText: 'File:Dot. dot. dot' }, { - fileName: 'dot. dot ._dot', - typeOfName: 'File name with different file extension desired', - nameText: 'Dot. dot . dot', - prefixedText: 'File:Dot. dot . dot.png', - extensionDesired: 'png' - }, - { - fileName: 'fileWOExt', - typeOfName: 'File W/O extension with extension desired', - nameText: 'FileWOExt', - prefixedText: 'File:FileWOExt.png', - extensionDesired: 'png' - }, - { fileName: '𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎𠸏𠹷𠺝𠺢𠻗𠻹𠻺𠼭𠼮𠽌𠾴𠾼𠿪𡁜𡁯𡁵𡁶𡁻𡃁𡃉𡇙𢃇𢞵𢫕𢭃𢯊𢱑𢱕𢳂𠻹𠻺𠼭𠼮𠽌𠾴𠾼𠿪𡁜𡁯𡁵𡁶𡁻𡃁𡃉𡇙𢃇𢞵𢫕𢭃𢯊𢱑𢱕𢳂.png', typeOfName: 'File name longer than 240 bytes', nameText: '𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎𠸏𠹷𠺝𠺢𠻗𠻹𠻺𠼭𠼮𠽌𠾴𠾼𠿪𡁜𡁯𡁵𡁶𡁻𡃁𡃉𡇙𢃇𢞵𢫕𢭃𢯊𢱑𢱕𢳂𠻹𠻺𠼭𠼮𠽌𠾴𠾼𠿪𡁜𡁯𡁵𡁶𡁻𡃁𡃉𡇙𢃇𢞵', @@ -632,8 +673,8 @@ ]; for ( i = 0; i < cases.length; i++ ) { - thisCase = cases[i]; - title = mw.Title.newFromFileName( thisCase.fileName, thisCase.extensionDesired ); + thisCase = cases[ i ]; + title = mw.Title.newFromFileName( thisCase.fileName ); if ( thisCase.nameText !== undefined ) { prefix = '[' + thisCase.typeOfName + '] '; diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js index ba366553..51374bd8 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js @@ -11,7 +11,7 @@ } } ) ); - $.each( [true, false], function ( i, strictMode ) { + $.each( [ true, false ], function ( i, strictMode ) { QUnit.test( 'Basic construction and properties (' + ( strictMode ? '' : 'non-' ) + 'strict mode)', 2, function ( assert ) { var uriString, uri; uriString = 'http://www.ietf.org/rfc/rfc2396.txt'; @@ -76,8 +76,8 @@ } ); assert.strictEqual( uri.query.n, '1', 'Simple parameter with overrideKeys:false' ); - assert.strictEqual( uri.query.m[0], 'foo', 'Order of multi-value parameters with overrideKeys:true' ); - assert.strictEqual( uri.query.m[1], 'bar', 'Order of multi-value parameters with overrideKeys:true' ); + assert.strictEqual( uri.query.m[ 0 ], 'foo', 'Order of multi-value parameters with overrideKeys:true' ); + assert.strictEqual( uri.query.m[ 1 ], 'bar', 'Order of multi-value parameters with overrideKeys:true' ); assert.strictEqual( uri.query.m.length, 2, 'Number of mult-value field is correct' ); uri = new mw.Uri( 'ftp://usr:pwd@192.0.2.16/' ); @@ -270,7 +270,7 @@ assert.deepEqual( original.query, - { 'one': '1', 'two': '2' }, + { one: '1', two: '2' }, 'Properties is deep cloned (bug 37708)' ); } ); @@ -367,7 +367,7 @@ host: 'example.com', port: undefined, path: '/wiki/Foo', - query: { 'v': '2' }, + query: { v: '2' }, fragment: undefined }, 'basic object properties' diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js index 779a0ed4..399db914 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js @@ -65,9 +65,9 @@ QUnit.test( 'Plural Test for ' + langCode, tests.length, function ( assert ) { for ( var i = 0; i < tests.length; i++ ) { assert.equal( - mw.language.convertPlural( tests[i][0], tests[i][1] ), - tests[i][2], - tests[i][3] + mw.language.convertPlural( tests[ i ][ 0 ], tests[ i ][ 1 ] ), + tests[ i ][ 2 ], + tests[ i ][ 3 ] ); } } ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js index f5f199ea..7a13f0f7 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js @@ -102,7 +102,7 @@ } ); call = $.cookie.lastCall.args; - assert.strictEqual( call[0], 'myPrefixfoo' ); + assert.strictEqual( call[ 0 ], 'myPrefixfoo' ); assert.deepEqual( call[ 2 ], { expires: expiryDate, domain: 'myDomain', @@ -122,7 +122,7 @@ } ); call = $.cookie.lastCall.args; - assert.strictEqual( call[0], 'myPrefixfoo' ); + assert.strictEqual( call[ 0 ], 'myPrefixfoo' ); assert.deepEqual( call[ 2 ], { expires: date, domain: 'myDomain', diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js index 7c3f1ecb..587c8931 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js @@ -7,7 +7,7 @@ errorUrl = 'http://example.com', errorLine = '123', errorColumn = '45', - errorObject = new Error( 'Foo'), + errorObject = new Error( 'Foo' ), oldHandler = this.sandbox.stub(); this.sandbox.stub( mw, 'track' ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.experiments.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.experiments.test.js new file mode 100644 index 00000000..774b2053 --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.experiments.test.js @@ -0,0 +1,63 @@ +( function ( mw ) { + + var getBucket = mw.experiments.getBucket; + + function createExperiment() { + return { + name: 'experiment', + enabled: true, + buckets: { + control: 0.25, + A: 0.25, + B: 0.25, + C: 0.25 + } + }; + } + + QUnit.module( 'mediawiki.experiments' ); + + QUnit.test( 'getBucket( experiment, token )', 4, function ( assert ) { + var experiment = createExperiment(), + token = '123457890'; + + assert.equal( + getBucket( experiment, token ), + getBucket( experiment, token ), + 'It returns the same bucket for the same experiment-token pair.' + ); + + // -------- + experiment = createExperiment(); + experiment.buckets = { + A: 0.314159265359 + }; + + assert.equal( + 'A', + getBucket( experiment, token ), + 'It returns the bucket if only one is defined.' + ); + + // -------- + experiment = createExperiment(); + experiment.enabled = false; + + assert.equal( + 'control', + getBucket( experiment, token ), + 'It returns "control" if the experiment is disabled.' + ); + + // -------- + experiment = createExperiment(); + experiment.buckets = {}; + + assert.equal( + 'control', + getBucket( experiment, token ), + 'It returns "control" if the experiment doesn\'t have any buckets.' + ); + } ); + +}( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js index 7e23e2ff..c088554a 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js @@ -1,5 +1,6 @@ ( function ( mw, $ ) { - var formatText, formatParse, formatnumTests, specialCharactersPageName, expectedListUsers, expectedEntrypoints, + var formatText, formatParse, formatnumTests, specialCharactersPageName, expectedListUsers, + expectedListUsersSitename, expectedEntrypoints, mwLanguageCache = {}, hasOwn = Object.hasOwnProperty; @@ -16,6 +17,8 @@ specialCharactersPageName = '"Who" wants to be a millionaire & live on \'Exotic Island\'?'; expectedListUsers = '注册<a title="Special:ListUsers" href="/wiki/Special:ListUsers">用户</a>'; + expectedListUsersSitename = '注册<a title="Special:ListUsers" href="/wiki/Special:ListUsers">用户' + + mw.config.get( 'wgSiteName' ) + '</a>'; expectedEntrypoints = '<a href="https://www.mediawiki.org/wiki/Manual:index.php">index.php</a>'; @@ -52,6 +55,7 @@ 'see-portal-url': '{{Int:portal-url}} is an important community page.', 'jquerymsg-test-statistics-users': '注册[[Special:ListUsers|用户]]', + 'jquerymsg-test-statistics-users-sitename': '注册[[Special:ListUsers|用户{{SITENAME}}]]', 'jquerymsg-test-version-entrypoints-index-php': '[https://www.mediawiki.org/wiki/Manual:index.php index.php]', @@ -70,7 +74,7 @@ */ function getMwLanguage( langCode ) { if ( !hasOwn.call( mwLanguageCache, langCode ) ) { - mwLanguageCache[langCode] = $.ajax( { + mwLanguageCache[ langCode ] = $.ajax( { url: mw.util.wikiScript( 'load' ), data: { skin: mw.config.get( 'skin' ), @@ -88,25 +92,37 @@ return mw.language; } ); } - return mwLanguageCache[langCode]; + return mwLanguageCache[ langCode ]; } /** * @param {Function[]} tasks List of functions that perform tasks * that may be asynchronous. Invoke the callback parameter when done. - * @param {Function} done When all tasks are done. - * @return + * @param {Function} complete Called when all tasks are done, or when the sequence is aborted. */ - function process( tasks, done ) { - function run() { + function process( tasks, complete ) { + /*jshint latedef:false */ + function abort() { + tasks.splice( 0, tasks.length ); + next(); + } + function next() { + if ( !tasks ) { + // This happens if after the process is completed, one of our callbacks is + // invoked. This can happen if a test timed out but the process was still + // running. In that case, ignore it. Don't invoke complete() a second time. + return; + } var task = tasks.shift(); if ( task ) { - task( run ); + task( next, abort ); } else { - done(); + // Remove tasks list to indicate the process is final. + tasks = null; + complete(); } } - run(); + next(); } QUnit.test( 'Replace', 16, function ( assert ) { @@ -306,9 +322,9 @@ QUnit.test( 'Match PHP parser', mw.libs.phpParserData.tests.length, function ( assert ) { mw.messages.set( mw.libs.phpParserData.messages ); var tasks = $.map( mw.libs.phpParserData.tests, function ( test ) { - return function ( next ) { + return function ( next, abort ) { getMwLanguage( test.lang ) - .done( function ( langClass ) { + .then( function ( langClass ) { mw.config.set( 'wgUserLanguage', test.lang ); var parser = new mw.jqueryMsg.parser( { language: langClass } ); assert.equal( @@ -316,11 +332,10 @@ test.result, test.name ); - } ) - .fail( function () { + }, function () { assert.ok( false, 'Language "' + test.lang + '" failed to load.' ); } ) - .always( next ); + .then( next, abort ); }; } ); @@ -328,8 +343,9 @@ process( tasks, QUnit.start ); } ); - QUnit.test( 'Links', 6, function ( assert ) { - var expectedDisambiguationsText, + QUnit.test( 'Links', 14, function ( assert ) { + var testCases, + expectedDisambiguationsText, expectedMultipleBars, expectedSpecialCharacters; @@ -360,12 +376,24 @@ // Pipe trick is not supported currently, but should not parse as text either. mw.messages.set( 'pipe-trick', '[[Tampa, Florida|]]' ); + mw.messages.set( 'reverse-pipe-trick', '[[|Tampa, Florida]]' ); + mw.messages.set( 'empty-link', '[[]]' ); this.suppressWarnings(); assert.equal( formatParse( 'pipe-trick' ), '[[Tampa, Florida|]]', 'Pipe trick should not be parsed.' ); + assert.equal( + formatParse( 'reverse-pipe-trick' ), + '[[|Tampa, Florida]]', + 'Reverse pipe trick should not be parsed.' + ); + assert.equal( + formatParse( 'empty-link' ), + '[[]]', + 'Empty link should not be parsed.' + ); this.restoreWarnings(); expectedMultipleBars = '<a title="Main Page" href="/wiki/Main_Page">Main|Page</a>'; @@ -384,20 +412,159 @@ expectedSpecialCharacters, 'Special characters' ); + + mw.messages.set( 'leading-colon', '[[:File:Foo.jpg]]' ); + assert.htmlEqual( + formatParse( 'leading-colon' ), + '<a title="File:Foo.jpg" href="/wiki/File:Foo.jpg">File:Foo.jpg</a>', + 'Leading colon in links is stripped' + ); + + assert.htmlEqual( + formatParse( 'jquerymsg-test-statistics-users-sitename' ), + expectedListUsersSitename, + 'Piped wikilink with parser function in the text' + ); + + testCases = [ + [ + 'extlink-html-full', + 'asd [http://example.org <strong>Example</strong>] asd', + 'asd <a href="http://example.org"><strong>Example</strong></a> asd' + ], + [ + 'extlink-html-partial', + 'asd [http://example.org foo <strong>Example</strong> bar] asd', + 'asd <a href="http://example.org">foo <strong>Example</strong> bar</a> asd' + ], + [ + 'wikilink-html-full', + 'asd [[Example|<strong>Example</strong>]] asd', + 'asd <a title="Example" href="/wiki/Example"><strong>Example</strong></a> asd' + ], + [ + 'wikilink-html-partial', + 'asd [[Example|foo <strong>Example</strong> bar]] asd', + 'asd <a title="Example" href="/wiki/Example">foo <strong>Example</strong> bar</a> asd' + ] + ]; + + $.each( testCases, function () { + var + key = this[ 0 ], + input = this[ 1 ], + output = this[ 2 ]; + mw.messages.set( key, input ); + assert.htmlEqual( + formatParse( key ), + output, + 'HTML in links: ' + key + ); + } ); + } ); + + QUnit.test( 'Replacements in links', 14, function ( assert ) { + var testCases = [ + [ + 'extlink-param-href-full', + 'asd [$1 Example] asd', + 'asd <a href="http://example.com">Example</a> asd' + ], + [ + 'extlink-param-href-partial', + 'asd [$1/example Example] asd', + 'asd <a href="http://example.com/example">Example</a> asd' + ], + [ + 'extlink-param-text-full', + 'asd [http://example.org $2] asd', + 'asd <a href="http://example.org">Text</a> asd' + ], + [ + 'extlink-param-text-partial', + 'asd [http://example.org Example $2] asd', + 'asd <a href="http://example.org">Example Text</a> asd' + ], + [ + 'extlink-param-both-full', + 'asd [$1 $2] asd', + 'asd <a href="http://example.com">Text</a> asd' + ], + [ + 'extlink-param-both-partial', + 'asd [$1/example Example $2] asd', + 'asd <a href="http://example.com/example">Example Text</a> asd' + ], + [ + 'wikilink-param-href-full', + 'asd [[$1|Example]] asd', + 'asd <a title="Example" href="/wiki/Example">Example</a> asd' + ], + [ + 'wikilink-param-href-partial', + 'asd [[$1/Test|Example]] asd', + 'asd <a title="Example/Test" href="/wiki/Example/Test">Example</a> asd' + ], + [ + 'wikilink-param-text-full', + 'asd [[Example|$2]] asd', + 'asd <a title="Example" href="/wiki/Example">Text</a> asd' + ], + [ + 'wikilink-param-text-partial', + 'asd [[Example|Example $2]] asd', + 'asd <a title="Example" href="/wiki/Example">Example Text</a> asd' + ], + [ + 'wikilink-param-both-full', + 'asd [[$1|$2]] asd', + 'asd <a title="Example" href="/wiki/Example">Text</a> asd' + ], + [ + 'wikilink-param-both-partial', + 'asd [[$1/Test|Example $2]] asd', + 'asd <a title="Example/Test" href="/wiki/Example/Test">Example Text</a> asd' + ], + [ + 'wikilink-param-unpiped-full', + 'asd [[$1]] asd', + 'asd <a title="Example" href="/wiki/Example">Example</a> asd' + ], + [ + 'wikilink-param-unpiped-partial', + 'asd [[$1/Test]] asd', + 'asd <a title="Example/Test" href="/wiki/Example/Test">Example/Test</a> asd' + ] + ]; + + $.each( testCases, function () { + var + key = this[ 0 ], + input = this[ 1 ], + output = this[ 2 ], + paramHref = key.slice( 0, 8 ) === 'wikilink' ? 'Example' : 'http://example.com', + paramText = 'Text'; + mw.messages.set( key, input ); + assert.htmlEqual( + formatParse( key, paramHref, paramText ), + output, + 'Replacements in links: ' + key + ); + } ); } ); -// Tests that {{-transformation vs. general parsing are done as requested + // Tests that {{-transformation vs. general parsing are done as requested QUnit.test( 'Curly brace transformation', 16, function ( assert ) { var oldUserLang = mw.config.get( 'wgUserLanguage' ); - assertBothModes( assert, ['gender-msg', 'Bob', 'male'], 'Bob: blue', 'gender is resolved' ); + assertBothModes( assert, [ 'gender-msg', 'Bob', 'male' ], 'Bob: blue', 'gender is resolved' ); - assertBothModes( assert, ['plural-msg', 5], 'Found 5 items', 'plural is resolved' ); + assertBothModes( assert, [ 'plural-msg', 5 ], 'Found 5 items', 'plural is resolved' ); - assertBothModes( assert, ['grammar-msg'], 'Przeszukaj ' + mw.config.get( 'wgSiteName' ), 'grammar is resolved' ); + assertBothModes( assert, [ 'grammar-msg' ], 'Przeszukaj ' + mw.config.get( 'wgSiteName' ), 'grammar is resolved' ); mw.config.set( 'wgUserLanguage', 'en' ); - assertBothModes( assert, ['formatnum-msg', '987654321.654321'], '987,654,321.654', 'formatnum is resolved' ); + assertBothModes( assert, [ 'formatnum-msg', '987654321.654321' ], '987,654,321.654', 'formatnum is resolved' ); // Test non-{{ wikitext, where behavior differs @@ -501,7 +668,7 @@ 'curly-brace': '{{int:message}}', 'single-square-bracket': '[https://www.mediawiki.org/ MediaWiki]', 'double-square-bracket': '[[Some page]]', - 'regular': 'Other message' + regular: 'Other message' } ); oldGMF = mw.jqueryMsg.getMessageFunction; @@ -518,7 +685,7 @@ outerCalled = false; innerCalled = false; message = mw.message( key ); - message[format](); + message[ format ](); assert.strictEqual( outerCalled, shouldCall, 'Outer function called for ' + key ); assert.strictEqual( innerCalled, shouldCall, 'Inner function called for ' + key ); } @@ -645,9 +812,9 @@ mw.messages.set( 'formatnum-msg', '{{formatnum:$1}}' ); mw.messages.set( 'formatnum-msg-int', '{{formatnum:$1|R}}' ); var queue = $.map( formatnumTests, function ( test ) { - return function ( next ) { + return function ( next, abort ) { getMwLanguage( test.lang ) - .done( function ( langClass ) { + .then( function ( langClass ) { mw.config.set( 'wgUserLanguage', test.lang ); var parser = new mw.jqueryMsg.parser( { language: langClass } ); assert.equal( @@ -656,11 +823,10 @@ test.result, test.description ); - } ) - .fail( function () { + }, function () { assert.ok( false, 'Language "' + test.lang + '" failed to load' ); } ) - .always( next ); + .then( next, abort ); }; } ); QUnit.stop(); @@ -671,16 +837,16 @@ QUnit.test( 'HTML', 26, function ( assert ) { mw.messages.set( 'jquerymsg-italics-msg', '<i>Very</i> important' ); - assertBothModes( assert, ['jquerymsg-italics-msg'], mw.messages.get( 'jquerymsg-italics-msg' ), 'Simple italics unchanged' ); + assertBothModes( assert, [ 'jquerymsg-italics-msg' ], mw.messages.get( 'jquerymsg-italics-msg' ), 'Simple italics unchanged' ); mw.messages.set( 'jquerymsg-bold-msg', '<b>Strong</b> speaker' ); - assertBothModes( assert, ['jquerymsg-bold-msg'], mw.messages.get( 'jquerymsg-bold-msg' ), 'Simple bold unchanged' ); + assertBothModes( assert, [ 'jquerymsg-bold-msg' ], mw.messages.get( 'jquerymsg-bold-msg' ), 'Simple bold unchanged' ); mw.messages.set( 'jquerymsg-bold-italics-msg', 'It is <b><i>key</i></b>' ); - assertBothModes( assert, ['jquerymsg-bold-italics-msg'], mw.messages.get( 'jquerymsg-bold-italics-msg' ), 'Bold and italics nesting order preserved' ); + assertBothModes( assert, [ 'jquerymsg-bold-italics-msg' ], mw.messages.get( 'jquerymsg-bold-italics-msg' ), 'Bold and italics nesting order preserved' ); mw.messages.set( 'jquerymsg-italics-bold-msg', 'It is <i><b>vital</b></i>' ); - assertBothModes( assert, ['jquerymsg-italics-bold-msg'], mw.messages.get( 'jquerymsg-italics-bold-msg' ), 'Italics and bold nesting order preserved' ); + assertBothModes( assert, [ 'jquerymsg-italics-bold-msg' ], mw.messages.get( 'jquerymsg-italics-bold-msg' ), 'Italics and bold nesting order preserved' ); mw.messages.set( 'jquerymsg-italics-with-link', 'An <i>italicized [[link|wiki-link]]</i>' ); @@ -737,19 +903,17 @@ 'Mismatched HTML start and end tag treated as text' ); - // TODO (mattflaschen, 2013-03-18): It's not a security issue, but there's no real - // reason the htmlEmitter span needs to be here. It's an artifact of how emitting works. mw.messages.set( 'jquerymsg-script-and-external-link', '<script>alert( "jquerymsg-script-and-external-link test" );</script> [http://example.com <i>Foo</i> bar]' ); assert.htmlEqual( formatParse( 'jquerymsg-script-and-external-link' ), - '<script>alert( "jquerymsg-script-and-external-link test" );</script> <a href="http://example.com"><span class="mediaWiki_htmlEmitter"><i>Foo</i> bar</span></a>', + '<script>alert( "jquerymsg-script-and-external-link test" );</script> <a href="http://example.com"><i>Foo</i> bar</a>', 'HTML tags in external links not interfering with escaping of other tags' ); mw.messages.set( 'jquerymsg-link-script', '[http://example.com <script>alert( "jquerymsg-link-script test" );</script>]' ); assert.htmlEqual( formatParse( 'jquerymsg-link-script' ), - '<a href="http://example.com"><span class="mediaWiki_htmlEmitter"><script>alert( "jquerymsg-link-script test" );</script></span></a>', + '<a href="http://example.com"><script>alert( "jquerymsg-link-script test" );</script></a>', 'Non-whitelisted HTML tag in external link anchor treated as text' ); @@ -792,7 +956,7 @@ mw.messages.set( 'jquerymsg-wikitext-contents-script', '<i><script>Script inside</script></i>' ); assert.htmlEqual( formatParse( 'jquerymsg-wikitext-contents-script' ), - '<i><span class="mediaWiki_htmlEmitter"><script>Script inside</script></span></i>', + '<i><script>Script inside</script></i>', 'Contents of valid tag are treated as wikitext, so invalid HTML element is treated as text' ); @@ -832,4 +996,33 @@ assert.equal( logSpy.callCount, 2, 'mw.log.warn calls' ); } ); + QUnit.test( 'Integration', 4, function ( assert ) { + var expected, logSpy; + + expected = '<b><a title="Bold" href="/wiki/Bold">Bold</a>!</b>'; + mw.messages.set( 'integration-test', '<b>[[Bold]]!</b>' ); + + this.suppressWarnings(); + logSpy = this.sandbox.spy( mw.log, 'warn' ); + assert.equal( + window.gM( 'integration-test' ), + expected, + 'Global function gM() works correctly' + ); + assert.equal( logSpy.callCount, 1, 'mw.log.warn called' ); + this.restoreWarnings(); + + assert.equal( + mw.message( 'integration-test' ).parse(), + expected, + 'mw.message().parse() works correctly' + ); + + assert.equal( + $( '<span>' ).msg( 'integration-test' ).html(), + expected, + 'jQuery plugin $.fn.msg() works correctly' + ); + } ); + }( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js index 3328ce3f..3b5915ac 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js @@ -61,7 +61,7 @@ expected = repeat( '\n', n ) + 'some text'; $textarea = $( '<textarea>\n' + expected + '</textarea>' ); - assert.equal( $textarea.val(), expected, 'Expecting ' + n + ' newlines (HTML contained ' + (n + 1) + ')' ); + assert.equal( $textarea.val(), expected, 'Expecting ' + n + ' newlines (HTML contained ' + ( n + 1 ) + ')' ); $textarea = $( '<textarea>' ).val( expected ); assert.equal( $textarea.val(), expected, 'Expecting ' + n + ' newlines (from DOM set with ' + n + ')' ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js index 670914eb..399290ca 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js @@ -11,7 +11,7 @@ }, messages: { // mw.language.listToText test - 'and': ' and', + and: ' and', 'comma-separator': ', ', 'word-separator': ' ' } @@ -51,9 +51,9 @@ for ( var i = 0; i < test.length; i++ ) { assert.equal( - mw.language.convertGrammar( test[i].word, test[i].grammarForm ), - test[i].expected, - test[i].description + mw.language.convertGrammar( test[ i ].word, test[ i ].grammarForm ), + test[ i ].expected, + test[ i ].description ); } } ); @@ -481,8 +481,8 @@ QUnit.test( 'List to text test', 4, function ( assert ) { assert.equal( mw.language.listToText( [] ), '', 'Blank list' ); - assert.equal( mw.language.listToText( ['a'] ), 'a', 'Single item' ); - assert.equal( mw.language.listToText( ['a', 'b'] ), 'a and b', 'Two items' ); - assert.equal( mw.language.listToText( ['a', 'b', 'c'] ), 'a, b and c', 'More than two items' ); + assert.equal( mw.language.listToText( [ 'a' ] ), 'a', 'Single item' ); + assert.equal( mw.language.listToText( [ 'a', 'b' ] ), 'a and b', 'Two items' ); + assert.equal( mw.language.listToText( [ 'a', 'b', 'c' ] ), 'a, b and c', 'More than two items' ); } ); }( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js index 61bab03f..288b527b 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js @@ -12,7 +12,7 @@ mw.messagePoster.factory.register( TEST_MODEL, testMessagePosterConstructor ); assert.strictEqual( - mw.messagePoster.factory.contentModelToClass[TEST_MODEL], + mw.messagePoster.factory.contentModelToClass[ TEST_MODEL ], testMessagePosterConstructor, 'Constructor is registered' ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.storage.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.storage.test.js new file mode 100644 index 00000000..6cef4a7c --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.storage.test.js @@ -0,0 +1,36 @@ +( function ( mw ) { + QUnit.module( 'mediawiki.storage' ); + + QUnit.test( 'set/get with localStorage', 3, function ( assert ) { + this.sandbox.stub( mw.storage, 'localStorage', { + setItem: this.sandbox.spy(), + getItem: this.sandbox.stub() + } ); + + mw.storage.set( 'foo', 'test' ); + assert.ok( mw.storage.localStorage.setItem.calledOnce ); + + mw.storage.localStorage.getItem.withArgs( 'foo' ).returns( 'test' ); + mw.storage.localStorage.getItem.returns( null ); + assert.strictEqual( mw.storage.get( 'foo' ), 'test', 'Check value gets stored.' ); + assert.strictEqual( mw.storage.get( 'bar' ), null, 'Unset values are null.' ); + } ); + + QUnit.test( 'set/get without localStorage', 3, function ( assert ) { + this.sandbox.stub( mw.storage, 'localStorage', { + getItem: this.sandbox.stub(), + removeItem: this.sandbox.stub(), + setItem: this.sandbox.stub() + } ); + + mw.storage.localStorage.getItem.throws(); + assert.strictEqual( mw.storage.get( 'foo' ), false ); + + mw.storage.localStorage.setItem.throws(); + assert.strictEqual( mw.storage.set( 'foo', 'test' ), false ); + + mw.storage.localStorage.removeItem.throws(); + assert.strictEqual( mw.storage.remove( 'foo', 'test' ), false ); + } ); + +}( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js index cf36ea82..111d85bd 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js @@ -108,21 +108,21 @@ // Multiple values at once someValues = { - 'foo': 'bar', - 'lorem': 'ipsum', - 'MediaWiki': true + foo: 'bar', + lorem: 'ipsum', + MediaWiki: true }; assert.strictEqual( conf.set( someValues ), true, 'Map.set returns boolean true if multiple values were set by passing an object' ); - assert.deepEqual( conf.get( ['foo', 'lorem'] ), { - 'foo': 'bar', - 'lorem': 'ipsum' + assert.deepEqual( conf.get( [ 'foo', 'lorem' ] ), { + foo: 'bar', + lorem: 'ipsum' }, 'Map.get returns multiple values correctly as an object' ); assert.deepEqual( conf, new mw.Map( conf.values ), 'new mw.Map maps over existing values-bearing object' ); - assert.deepEqual( conf.get( ['foo', 'notExist'] ), { - 'foo': 'bar', - 'notExist': null + assert.deepEqual( conf.get( [ 'foo', 'notExist' ] ), { + foo: 'bar', + notExist: null }, 'Map.get return includes keys that were not found as null values' ); // Interacting with globals and accessing the values object @@ -143,7 +143,7 @@ this.restoreWarnings(); // Change value via global Map - globalConf.set('anotherGlobalMapChecker', 'Again'); + globalConf.set( 'anotherGlobalMapChecker', 'Again' ); assert.equal( globalConf.get( 'anotherGlobalMapChecker' ), 'Again', 'Change in global Map reflected via get()' ); this.suppressWarnings(); assert.equal( window.anotherGlobalMapChecker, 'Again', 'Change in global Map reflected window object' ); @@ -175,8 +175,8 @@ len = formats.length; for ( i = 0; i < len; i++ ) { - format = formats[i]; - assert.equal( mw.message.apply( null, messageArguments )[format](), expectedResult, assertMessage + ' when format is ' + format ); + format = formats[ i ]; + assert.equal( mw.message.apply( null, messageArguments )[ format ](), expectedResult, assertMessage + ' when format is ' + format ); } } @@ -203,21 +203,21 @@ assert.equal( hello.format, 'escaped', 'Message.escaped correctly updated the "format" property' ); assert.ok( mw.messages.set( 'multiple-curly-brace', '"{{SITENAME}}" is the home of {{int:other-message}}' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['multiple-curly-brace'], ['text', 'parse'], '"' + siteName + '" is the home of Other Message', 'Curly brace format works correctly' ); + assertMultipleFormats( [ 'multiple-curly-brace' ], [ 'text', 'parse' ], '"' + siteName + '" is the home of Other Message', 'Curly brace format works correctly' ); assert.equal( mw.message( 'multiple-curly-brace' ).plain(), mw.messages.get( 'multiple-curly-brace' ), 'Plain format works correctly for curly brace message' ); assert.equal( mw.message( 'multiple-curly-brace' ).escaped(), mw.html.escape( '"' + siteName + '" is the home of Other Message' ), 'Escaped format works correctly for curly brace message' ); assert.ok( mw.messages.set( 'multiple-square-brackets-and-ampersand', 'Visit the [[Project:Community portal|community portal]] & [[Project:Help desk|help desk]]' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['multiple-square-brackets-and-ampersand'], ['plain', 'text'], mw.messages.get( 'multiple-square-brackets-and-ampersand' ), 'Square bracket message is not processed' ); + assertMultipleFormats( [ 'multiple-square-brackets-and-ampersand' ], [ 'plain', 'text' ], mw.messages.get( 'multiple-square-brackets-and-ampersand' ), 'Square bracket message is not processed' ); assert.equal( mw.message( 'multiple-square-brackets-and-ampersand' ).escaped(), 'Visit the [[Project:Community portal|community portal]] & [[Project:Help desk|help desk]]', 'Escaped format works correctly for square bracket message' ); assert.htmlEqual( mw.message( 'multiple-square-brackets-and-ampersand' ).parse(), 'Visit the ' + '<a title="Project:Community portal" href="/wiki/Project:Community_portal">community portal</a>' + ' & <a title="Project:Help desk" href="/wiki/Project:Help_desk">help desk</a>', 'Internal links work with parse' ); - assertMultipleFormats( ['mediawiki-test-version-entrypoints-index-php'], ['plain', 'text', 'escaped'], mw.messages.get( 'mediawiki-test-version-entrypoints-index-php' ), 'External link markup is unprocessed' ); + assertMultipleFormats( [ 'mediawiki-test-version-entrypoints-index-php' ], [ 'plain', 'text', 'escaped' ], mw.messages.get( 'mediawiki-test-version-entrypoints-index-php' ), 'External link markup is unprocessed' ); assert.htmlEqual( mw.message( 'mediawiki-test-version-entrypoints-index-php' ).parse(), '<a href="https://www.mediawiki.org/wiki/Manual:index.php">index.php</a>', 'External link works correctly in parse mode' ); - assertMultipleFormats( ['external-link-replace', 'http://example.org/?x=y&z'], ['plain', 'text'], 'Foo [http://example.org/?x=y&z bar]', 'Parameters are substituted but external link is not processed' ); + assertMultipleFormats( [ 'external-link-replace', 'http://example.org/?x=y&z' ], [ 'plain', 'text' ], 'Foo [http://example.org/?x=y&z bar]', 'Parameters are substituted but external link is not processed' ); assert.equal( mw.message( 'external-link-replace', 'http://example.org/?x=y&z' ).escaped(), 'Foo [http://example.org/?x=y&z bar]', 'In escaped mode, parameters are substituted and ampersand is escaped, but external link is not processed' ); assert.htmlEqual( mw.message( 'external-link-replace', 'http://example.org/?x=y&z' ).parse(), 'Foo <a href="http://example.org/?x=y&z">bar</a>', 'External link with replacement works in parse mode without double-escaping' ); @@ -235,26 +235,26 @@ goodbye = mw.message( 'goodbye' ); assert.strictEqual( goodbye.exists(), false, 'Message.exists returns false for nonexistent messages' ); - assertMultipleFormats( ['goodbye'], ['plain', 'text'], '<goodbye>', 'Message.toString returns <key> if key does not exist' ); + assertMultipleFormats( [ 'goodbye' ], [ 'plain', 'text' ], '<goodbye>', 'Message.toString returns <key> if key does not exist' ); // bug 30684 - assertMultipleFormats( ['goodbye'], ['parse', 'escaped'], '<goodbye>', 'Message.toString returns properly escaped <key> if key does not exist' ); + assertMultipleFormats( [ 'goodbye' ], [ 'parse', 'escaped' ], '<goodbye>', 'Message.toString returns properly escaped <key> if key does not exist' ); assert.ok( mw.messages.set( 'plural-test-msg', 'There {{PLURAL:$1|is|are}} $1 {{PLURAL:$1|result|results}}' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['plural-test-msg', 6], ['text', 'parse', 'escaped'], 'There are 6 results', 'plural get resolved' ); + assertMultipleFormats( [ 'plural-test-msg', 6 ], [ 'text', 'parse', 'escaped' ], 'There are 6 results', 'plural get resolved' ); assert.equal( mw.message( 'plural-test-msg', 6 ).plain(), 'There {{PLURAL:6|is|are}} 6 {{PLURAL:6|result|results}}', 'Parameter is substituted but plural is not resolved in plain' ); assert.ok( mw.messages.set( 'plural-test-msg-explicit', 'There {{plural:$1|is one car|are $1 cars|0=are no cars|12=are a dozen cars}}' ), 'mw.messages.set: Register message with explicit plural forms' ); - assertMultipleFormats( ['plural-test-msg-explicit', 12], ['text', 'parse', 'escaped'], 'There are a dozen cars', 'explicit plural get resolved' ); + assertMultipleFormats( [ 'plural-test-msg-explicit', 12 ], [ 'text', 'parse', 'escaped' ], 'There are a dozen cars', 'explicit plural get resolved' ); assert.ok( mw.messages.set( 'plural-test-msg-explicit-beginning', 'Basket has {{plural:$1|0=no eggs|12=a dozen eggs|6=half a dozen eggs|one egg|$1 eggs}}' ), 'mw.messages.set: Register message with explicit plural forms' ); - assertMultipleFormats( ['plural-test-msg-explicit-beginning', 1], ['text', 'parse', 'escaped'], 'Basket has one egg', 'explicit plural given at beginning get resolved for singular' ); - assertMultipleFormats( ['plural-test-msg-explicit-beginning', 4], ['text', 'parse', 'escaped'], 'Basket has 4 eggs', 'explicit plural given at beginning get resolved for plural' ); - assertMultipleFormats( ['plural-test-msg-explicit-beginning', 6], ['text', 'parse', 'escaped'], 'Basket has half a dozen eggs', 'explicit plural given at beginning get resolved for 6' ); - assertMultipleFormats( ['plural-test-msg-explicit-beginning', 0], ['text', 'parse', 'escaped'], 'Basket has no eggs', 'explicit plural given at beginning get resolved for 0' ); + assertMultipleFormats( [ 'plural-test-msg-explicit-beginning', 1 ], [ 'text', 'parse', 'escaped' ], 'Basket has one egg', 'explicit plural given at beginning get resolved for singular' ); + assertMultipleFormats( [ 'plural-test-msg-explicit-beginning', 4 ], [ 'text', 'parse', 'escaped' ], 'Basket has 4 eggs', 'explicit plural given at beginning get resolved for plural' ); + assertMultipleFormats( [ 'plural-test-msg-explicit-beginning', 6 ], [ 'text', 'parse', 'escaped' ], 'Basket has half a dozen eggs', 'explicit plural given at beginning get resolved for 6' ); + assertMultipleFormats( [ 'plural-test-msg-explicit-beginning', 0 ], [ 'text', 'parse', 'escaped' ], 'Basket has no eggs', 'explicit plural given at beginning get resolved for 0' ); - assertMultipleFormats( ['mediawiki-test-pagetriage-del-talk-page-notify-summary'], ['plain', 'text'], mw.messages.get( 'mediawiki-test-pagetriage-del-talk-page-notify-summary' ), 'Double square brackets with no parameters unchanged' ); + assertMultipleFormats( [ 'mediawiki-test-pagetriage-del-talk-page-notify-summary' ], [ 'plain', 'text' ], mw.messages.get( 'mediawiki-test-pagetriage-del-talk-page-notify-summary' ), 'Double square brackets with no parameters unchanged' ); - assertMultipleFormats( ['mediawiki-test-pagetriage-del-talk-page-notify-summary', specialCharactersPageName], ['plain', 'text'], 'Notifying author of deletion nomination for [[' + specialCharactersPageName + ']]', 'Double square brackets with one parameter' ); + assertMultipleFormats( [ 'mediawiki-test-pagetriage-del-talk-page-notify-summary', specialCharactersPageName ], [ 'plain', 'text' ], 'Notifying author of deletion nomination for [[' + specialCharactersPageName + ']]', 'Double square brackets with one parameter' ); assert.equal( mw.message( 'mediawiki-test-pagetriage-del-talk-page-notify-summary', specialCharactersPageName ).escaped(), 'Notifying author of deletion nomination for [[' + mw.html.escape( specialCharactersPageName ) + ']]', 'Double square brackets with one parameter, when escaped' ); @@ -264,21 +264,21 @@ assert.ok( mw.messages.set( 'mediawiki-test-wikieditor-toolbar-help-content-signature-result', '<a href=\'#\' title=\'{{#special:mypage}}\'>Username</a> (<a href=\'#\' title=\'{{#special:mytalk}}\'>talk</a>)' ), 'mw.messages.set: Register' ); assert.equal( mw.message( 'mediawiki-test-wikieditor-toolbar-help-content-signature-result' ).plain(), mw.messages.get( 'mediawiki-test-wikieditor-toolbar-help-content-signature-result' ), 'HTML message with curly braces is not changed in plain mode' ); - assertMultipleFormats( ['gender-plural-msg', 'male', 1], ['text', 'parse', 'escaped'], 'he is awesome', 'Gender and plural are resolved' ); + assertMultipleFormats( [ 'gender-plural-msg', 'male', 1 ], [ 'text', 'parse', 'escaped' ], 'he is awesome', 'Gender and plural are resolved' ); assert.equal( mw.message( 'gender-plural-msg', 'male', 1 ).plain(), '{{GENDER:male|he|she|they}} {{PLURAL:1|is|are}} awesome', 'Parameters are substituted, but gender and plural are not resolved in plain mode' ); assert.equal( mw.message( 'grammar-msg' ).plain(), mw.messages.get( 'grammar-msg' ), 'Grammar is not resolved in plain mode' ); - assertMultipleFormats( ['grammar-msg'], ['text', 'parse'], 'Przeszukaj ' + siteName, 'Grammar is resolved' ); + assertMultipleFormats( [ 'grammar-msg' ], [ 'text', 'parse' ], 'Przeszukaj ' + siteName, 'Grammar is resolved' ); assert.equal( mw.message( 'grammar-msg' ).escaped(), 'Przeszukaj ' + siteName, 'Grammar is resolved in escaped mode' ); - assertMultipleFormats( ['formatnum-msg', '987654321.654321'], ['text', 'parse', 'escaped'], '987,654,321.654', 'formatnum is resolved' ); + assertMultipleFormats( [ 'formatnum-msg', '987654321.654321' ], [ 'text', 'parse', 'escaped' ], '987,654,321.654', 'formatnum is resolved' ); assert.equal( mw.message( 'formatnum-msg' ).plain(), mw.messages.get( 'formatnum-msg' ), 'formatnum is not resolved in plain mode' ); - assertMultipleFormats( ['int-msg'], ['text', 'parse', 'escaped'], 'Some Other Message', 'int is resolved' ); + assertMultipleFormats( [ 'int-msg' ], [ 'text', 'parse', 'escaped' ], 'Some Other Message', 'int is resolved' ); assert.equal( mw.message( 'int-msg' ).plain(), mw.messages.get( 'int-msg' ), 'int is not resolved in plain mode' ); assert.ok( mw.messages.set( 'mediawiki-italics-msg', '<i>Very</i> important' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['mediawiki-italics-msg'], ['plain', 'text', 'parse'], mw.messages.get( 'mediawiki-italics-msg' ), 'Simple italics unchanged' ); + assertMultipleFormats( [ 'mediawiki-italics-msg' ], [ 'plain', 'text', 'parse' ], mw.messages.get( 'mediawiki-italics-msg' ), 'Simple italics unchanged' ); assert.htmlEqual( mw.message( 'mediawiki-italics-msg' ).escaped(), '<i>Very</i> important', @@ -286,7 +286,7 @@ ); assert.ok( mw.messages.set( 'mediawiki-italics-with-link', 'An <i>italicized [[link|wiki-link]]</i>' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['mediawiki-italics-with-link'], ['plain', 'text'], mw.messages.get( 'mediawiki-italics-with-link' ), 'Italics with link unchanged' ); + assertMultipleFormats( [ 'mediawiki-italics-with-link' ], [ 'plain', 'text' ], mw.messages.get( 'mediawiki-italics-with-link' ), 'Italics with link unchanged' ); assert.htmlEqual( mw.message( 'mediawiki-italics-with-link' ).escaped(), 'An <i>italicized [[link|wiki-link]]</i>', @@ -299,7 +299,7 @@ ); assert.ok( mw.messages.set( 'mediawiki-script-msg', '<script >alert( "Who put this script here?" );</script>' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['mediawiki-script-msg'], ['plain', 'text'], mw.messages.get( 'mediawiki-script-msg' ), 'Script unchanged' ); + assertMultipleFormats( [ 'mediawiki-script-msg' ], [ 'plain', 'text' ], mw.messages.get( 'mediawiki-script-msg' ), 'Script unchanged' ); assert.htmlEqual( mw.message( 'mediawiki-script-msg' ).escaped(), '<script >alert( "Who put this script here?" );</script>', @@ -340,8 +340,9 @@ * The sync style load test (for @import). This is, in a way, also an open bug for * ResourceLoader ("execute js after styles are loaded"), but browsers don't offer a * way to get a callback from when a stylesheet is loaded (that is, including any - * @import rules inside). To work around this, we'll have a little time loop to check + * `@import` rules inside). To work around this, we'll have a little time loop to check * if the styles apply. + * * Note: This test originally used new Image() and onerror to get a callback * when the url is loaded, but that is fragile since it doesn't monitor the * same request as the css @import, and Safari 4 has issues with @@ -406,7 +407,7 @@ isAwesomeDone = true; }; - mw.loader.implement( 'test.callback', [QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' )], {}, {} ); + mw.loader.implement( 'test.callback', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] ); mw.loader.using( 'test.callback', function () { @@ -430,7 +431,7 @@ isAwesomeDone = true; }; - mw.loader.implement( 'hasOwnProperty', [QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' )], {}, {} ); + mw.loader.implement( 'hasOwnProperty', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ], {}, {} ); mw.loader.using( 'hasOwnProperty', function () { @@ -454,7 +455,7 @@ isAwesomeDone = true; }; - mw.loader.implement( 'test.promise', [QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' )], {}, {} ); + mw.loader.implement( 'test.promise', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] ); mw.loader.using( 'test.promise' ) .done( function () { @@ -491,9 +492,8 @@ QUnit.start(); }, { - 'all': '.mw-test-implement-a { float: right; }' - }, - {} + all: '.mw-test-implement-a { float: right; }' + } ); mw.loader.load( [ @@ -547,16 +547,15 @@ } ); }, { - 'url': { - 'print': [urlStyleTest( '.mw-test-implement-b1', 'text-align', 'center' )], - 'screen': [ + url: { + print: [ urlStyleTest( '.mw-test-implement-b1', 'text-align', 'center' ) ], + screen: [ // bug 40834: Make sure it actually works with more than 1 stylesheet reference urlStyleTest( '.mw-test-implement-b2', 'float', 'left' ), urlStyleTest( '.mw-test-implement-b3', 'float', 'right' ) ] } - }, - {} + } ); mw.loader.load( [ @@ -585,9 +584,8 @@ QUnit.start(); }, { - 'all': '.mw-test-implement-c { float: right; }' - }, - {} + all: '.mw-test-implement-c { float: right; }' + } ); mw.loader.load( [ @@ -622,10 +620,9 @@ } ); }, { - 'all': [urlStyleTest( '.mw-test-implement-d', 'float', 'right' )], - 'print': [urlStyleTest( '.mw-test-implement-d2', 'text-align', 'center' )] - }, - {} + all: [ urlStyleTest( '.mw-test-implement-d', 'float', 'right' ) ], + print: [ urlStyleTest( '.mw-test-implement-d2', 'text-align', 'center' ) ] + } ); mw.loader.load( [ @@ -634,20 +631,20 @@ } ); // @import (bug 31676) - QUnit.asyncTest( 'mw.loader.implement( styles has @import)', 5, function ( assert ) { + QUnit.asyncTest( 'mw.loader.implement( styles has @import)', 7, function ( assert ) { var isJsExecuted, $element; mw.loader.implement( 'test.implement.import', function () { - assert.strictEqual( isJsExecuted, undefined, 'javascript not executed multiple times' ); + assert.strictEqual( isJsExecuted, undefined, 'script not executed multiple times' ); isJsExecuted = true; - assert.equal( mw.loader.getState( 'test.implement.import' ), 'ready', 'module state is "ready" while implement() is executing javascript' ); + assert.equal( mw.loader.getState( 'test.implement.import' ), 'executing', 'module state during implement() script execution' ); $element = $( '<div class="mw-test-implement-import">Foo bar</div>' ).appendTo( '#qunit-fixture' ); - assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'Messages are loaded before javascript execution' ); + assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'messages load before script execution' ); assertStyleAsync( assert, $element, 'float', 'right', function () { assert.equal( $element.css( 'text-align' ), 'center', @@ -658,7 +655,7 @@ } ); }, { - 'css': [ + css: [ '@import url(\'' + urlStyleTest( '.mw-test-implement-import', 'float', 'right' ) + '\');\n' @@ -670,8 +667,64 @@ } ); - mw.loader.load( 'test.implement' ); + mw.loader.using( 'test.implement.import' ).always( function () { + assert.strictEqual( isJsExecuted, true, 'script executed' ); + assert.equal( mw.loader.getState( 'test.implement.import' ), 'ready', 'module state after script execution' ); + } ); + } ); + + QUnit.asyncTest( 'mw.loader.implement( dependency with styles )', 4, function ( assert ) { + var $element = $( '<div class="mw-test-implement-e"></div>' ).appendTo( '#qunit-fixture' ), + $element2 = $( '<div class="mw-test-implement-e2"></div>' ).appendTo( '#qunit-fixture' ); + + assert.notEqual( + $element.css( 'float' ), + 'right', + 'style is clear' + ); + assert.notEqual( + $element2.css( 'float' ), + 'left', + 'style is clear' + ); + + mw.loader.register( [ + [ 'test.implement.e', '0', [ 'test.implement.e2' ] ], + [ 'test.implement.e2', '0' ] + ] ); + + mw.loader.implement( + 'test.implement.e', + function () { + assert.equal( + $element.css( 'float' ), + 'right', + 'Depending module\'s style is applied' + ); + QUnit.start(); + }, + { + all: '.mw-test-implement-e { float: right; }' + } + ); + + mw.loader.implement( + 'test.implement.e2', + function () { + assert.equal( + $element2.css( 'float' ), + 'left', + 'Dependency\'s style is applied' + ); + }, + { + all: '.mw-test-implement-e2 { float: left; }' + } + ); + mw.loader.load( [ + 'test.implement.e' + ] ); } ); QUnit.test( 'mw.loader.implement( only scripts )', 1, function ( assert ) { @@ -682,7 +735,9 @@ QUnit.asyncTest( 'mw.loader.implement( only messages )', 2, function ( assert ) { assert.assertFalse( mw.messages.exists( 'bug_29107' ), 'Verify that the test message doesn\'t exist yet' ); - mw.loader.implement( 'test.implement.msgs', [], {}, { 'bug_29107': 'loaded' } ); + // jscs: disable requireCamelCaseOrUpperCaseIdentifiers + mw.loader.implement( 'test.implement.msgs', [], {}, { bug_29107: 'loaded' } ); + // jscs: enable requireCamelCaseOrUpperCaseIdentifiers mw.loader.using( 'test.implement.msgs', function () { QUnit.start(); assert.ok( mw.messages.exists( 'bug_29107' ), 'Bug 29107: messages-only module should implement ok' ); @@ -697,9 +752,9 @@ this.sandbox.stub( mw, 'track' ); mw.loader.register( [ - ['test.module1', '0'], - ['test.module2', '0', ['test.module1']], - ['test.module3', '0', ['test.module2']] + [ 'test.module1', '0' ], + [ 'test.module2', '0', [ 'test.module1' ] ], + [ 'test.module3', '0', [ 'test.module2' ] ] ] ); mw.loader.implement( 'test.module1', function () { throw new Error( 'expected' ); @@ -713,22 +768,19 @@ QUnit.test( 'mw.loader out-of-order implementation', 9, function ( assert ) { mw.loader.register( [ - ['test.module4', '0'], - ['test.module5', '0', ['test.module4']], - ['test.module6', '0', ['test.module5']] + [ 'test.module4', '0' ], + [ 'test.module5', '0', [ 'test.module4' ] ], + [ 'test.module6', '0', [ 'test.module5' ] ] ] ); - mw.loader.implement( 'test.module4', function () { - }, {}, {} ); + mw.loader.implement( 'test.module4', function () {} ); assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' ); assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' ); assert.strictEqual( mw.loader.getState( 'test.module6' ), 'registered', 'Expected "registered" state for test.module6' ); - mw.loader.implement( 'test.module6', function () { - }, {}, {} ); + mw.loader.implement( 'test.module6', function () {} ); assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' ); assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' ); assert.strictEqual( mw.loader.getState( 'test.module6' ), 'loaded', 'Expected "loaded" state for test.module6' ); - mw.loader.implement( 'test.module5', function () { - }, {}, {} ); + mw.loader.implement( 'test.module5', function () {} ); assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' ); assert.strictEqual( mw.loader.getState( 'test.module5' ), 'ready', 'Expected "ready" state for test.module5' ); assert.strictEqual( mw.loader.getState( 'test.module6' ), 'ready', 'Expected "ready" state for test.module6' ); @@ -736,12 +788,11 @@ QUnit.test( 'mw.loader missing dependency', 13, function ( assert ) { mw.loader.register( [ - ['test.module7', '0'], - ['test.module8', '0', ['test.module7']], - ['test.module9', '0', ['test.module8']] + [ 'test.module7', '0' ], + [ 'test.module8', '0', [ 'test.module7' ] ], + [ 'test.module9', '0', [ 'test.module8' ] ] ] ); - mw.loader.implement( 'test.module8', function () { - }, {}, {} ); + mw.loader.implement( 'test.module8', function () {} ); assert.strictEqual( mw.loader.getState( 'test.module7' ), 'registered', 'Expected "registered" state for test.module7' ); assert.strictEqual( mw.loader.getState( 'test.module8' ), 'loaded', 'Expected "loaded" state for test.module8' ); assert.strictEqual( mw.loader.getState( 'test.module9' ), 'registered', 'Expected "registered" state for test.module9' ); @@ -749,24 +800,23 @@ assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' ); assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' ); assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' ); - mw.loader.implement( 'test.module9', function () { - }, {}, {} ); + mw.loader.implement( 'test.module9', function () {} ); assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' ); assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' ); assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' ); mw.loader.using( - ['test.module7'], + [ 'test.module7' ], function () { assert.ok( false, 'Success fired despite missing dependency' ); assert.ok( true, 'QUnit expected() count dummy' ); }, function ( e, dependencies ) { assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' ); - assert.deepEqual( dependencies, ['test.module7'], 'Error callback called with module test.module7' ); + assert.deepEqual( dependencies, [ 'test.module7' ], 'Error callback called with module test.module7' ); } ); mw.loader.using( - ['test.module9'], + [ 'test.module9' ], function () { assert.ok( false, 'Success fired despite missing dependency' ); assert.ok( true, 'QUnit expected() count dummy' ); @@ -776,7 +826,7 @@ dependencies.sort(); assert.deepEqual( dependencies, - ['test.module7', 'test.module8', 'test.module9'], + [ 'test.module7', 'test.module8', 'test.module9' ], 'Error callback called with all three modules as dependencies' ); } @@ -786,9 +836,9 @@ QUnit.asyncTest( 'mw.loader dependency handling', 5, function ( assert ) { mw.loader.register( [ // [module, version, dependencies, group, source] - ['testMissing', '1', [], null, 'testloader'], - ['testUsesMissing', '1', ['testMissing'], null, 'testloader'], - ['testUsesNestedMissing', '1', ['testUsesMissing'], null, 'testloader'] + [ 'testMissing', '1', [], null, 'testloader' ], + [ 'testUsesMissing', '1', [ 'testMissing' ], null, 'testloader' ], + [ 'testUsesNestedMissing', '1', [ 'testUsesMissing' ], null, 'testloader' ] ] ); function verifyModuleStates() { @@ -797,7 +847,7 @@ assert.equal( mw.loader.getState( 'testUsesNestedMissing' ), 'error', 'Module with indirect missing dependency must have state "error"' ); } - mw.loader.using( ['testUsesNestedMissing'], + mw.loader.using( [ 'testUsesNestedMissing' ], function () { assert.ok( false, 'Error handler should be invoked.' ); assert.ok( true ); // Dummy to reach QUnit expect() @@ -811,7 +861,7 @@ // As soon as server spits out state('testMissing', 'missing'); // it will bubble up and trigger the error callback. // Therefor the badmodules array is not testUsesMissing or testUsesNestedMissing. - assert.deepEqual( badmodules, ['testMissing'], 'Bad modules as expected.' ); + assert.deepEqual( badmodules, [ 'testMissing' ], 'Bad modules as expected.' ); verifyModuleStates(); @@ -823,9 +873,9 @@ QUnit.asyncTest( 'mw.loader skin-function handling', 5, function ( assert ) { mw.loader.register( [ // [module, version, dependencies, group, source, skip] - ['testSkipped', '1', [], null, 'testloader', 'return true;'], - ['testNotSkipped', '1', [], null, 'testloader', 'return false;'], - ['testUsesSkippable', '1', ['testSkipped', 'testNotSkipped'], null, 'testloader'] + [ 'testSkipped', '1', [], null, 'testloader', 'return true;' ], + [ 'testNotSkipped', '1', [], null, 'testloader', 'return false;' ], + [ 'testUsesSkippable', '1', [ 'testSkipped', 'testNotSkipped' ], null, 'testloader' ] ] ); function verifyModuleStates() { @@ -834,7 +884,7 @@ assert.equal( mw.loader.getState( 'testUsesSkippable' ), 'ready', 'Module is ready when skippable dependencies are ready' ); } - mw.loader.using( ['testUsesSkippable'], + mw.loader.using( [ 'testUsesSkippable' ], function () { assert.ok( true, 'Success handler should be invoked.' ); assert.ok( true ); // Dummy to match error handler and reach QUnit expect() @@ -858,7 +908,7 @@ // This bug was actually already fixed in 1.18 and later when discovered in 1.17. // Test is for regressions! - // Forge an URL to the test callback script + // Forge a URL to the test callback script var target = QUnit.fixurl( mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js' ); @@ -875,6 +925,45 @@ mw.loader.load( target ); } ); + QUnit.asyncTest( 'mw.loader( "/absolute-path" )', 2, function ( assert ) { + // Forge a URL to the test callback script + var target = QUnit.fixurl( + mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js' + ); + + // Confirm that mw.loader.load() works with absolute-paths (relative to current hostname) + assert.equal( target.slice( 0, 1 ), '/', 'URL is relative to document root' ); + + // Async! + // The target calls QUnit.start + mw.loader.load( target ); + } ); + + QUnit.asyncTest( 'mw.loader() executing race (T112232)', 2, function ( assert ) { + var done = false; + + // The red herring schedules its CSS buffer first. In T112232, a bug in the + // state machine would cause the job for testRaceLoadMe to run with an earlier job. + mw.loader.implement( + 'testRaceRedHerring', + function () {}, + { css: [ '.mw-testRaceRedHerring {}' ] } + ); + mw.loader.implement( + 'testRaceLoadMe', + function () { + done = true; + }, + { css: [ '.mw-testRaceLoadMe { float: left; }' ] } + ); + + mw.loader.load( [ 'testRaceRedHerring', 'testRaceLoadMe' ] ); + mw.loader.using( 'testRaceLoadMe', function () { + assert.strictEqual( done, true, 'script ran' ); + assert.strictEqual( mw.loader.getState( 'testRaceLoadMe' ), 'ready', 'state' ); + } ).always( QUnit.start ); + } ); + QUnit.test( 'mw.html', 13, function ( assert ) { assert.throws( function () { mw.html.escape(); @@ -968,9 +1057,9 @@ mw.hook( 'test.hook.data' ).add( function ( data1, data2 ) { assert.equal( data1, 'example', 'Fire with data (string param)' ); - assert.deepEqual( data2, ['two'], 'Fire with data (array param)' ); + assert.deepEqual( data2, [ 'two' ], 'Fire with data (array param)' ); } ); - mw.hook( 'test.hook.data' ).fire( 'example', ['two'] ); + mw.hook( 'test.hook.data' ).fire( 'example', [ 'two' ] ); hook = mw.hook( 'test.hook.chainable' ); assert.strictEqual( hook.add(), hook, 'hook.add is chainable' ); @@ -981,7 +1070,7 @@ add = hook.add; fire = hook.fire; add( function ( x, y ) { - assert.deepEqual( [x, y], ['x', 'y'], 'Detached (contextless) with data' ); + assert.deepEqual( [ x, y ], [ 'x', 'y' ], 'Detached (contextless) with data' ); } ); fire( 'x', 'y' ); @@ -1004,7 +1093,7 @@ assert.equal( chr, 'z', 'Adding callback later invokes right away with last data' ); } ); - assert.deepEqual( chars, ['x', 'y', 'z'], 'Multiple callbacks with multiple fires' ); + assert.deepEqual( chars, [ 'x', 'y', 'z' ], 'Multiple callbacks with multiple fires' ); chars = []; callback = function ( chr ) { @@ -1033,7 +1122,7 @@ assert.deepEqual( chars, - ['x', 'x', 'x', 'x', 'y', 'z'], + [ 'x', 'x', 'x', 'x', 'y', 'z' ], '"add" and "remove" support variadic arguments. ' + '"add" does not filter unique. ' + '"remove" removes all equal by reference. ' + diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js index e43516b0..89eb45f2 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js @@ -1,7 +1,7 @@ ( function ( mw, $ ) { QUnit.module( 'mediawiki.toc', QUnit.newMwEnvironment( { setup: function () { - // Prevent live cookies like mw_hidetoc=1 from interferring with the test + // Prevent live cookies from interferring with the test this.stub( $, 'cookie' ).returns( null ); } } ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.track.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.track.test.js index cdb26244..5329be6a 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.track.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.track.test.js @@ -39,4 +39,22 @@ assert.assertTrue( this.timeStamp >= now, 'thisValue has sane timestamp' ); } ); } ); + + QUnit.test( 'trackUnsubscribe', 1, function ( assert ) { + var sequence = []; + function unsubber( topic, data ) { + sequence.push( [ topic, data ] ); + } + + mw.track( 'unsub', { key: 1 } ); + mw.trackSubscribe( 'unsub', unsubber ); + mw.track( 'unsub', { key: 2 } ); + mw.trackUnsubscribe( unsubber ); + mw.track( 'unsub', { key: 3 } ); + + assert.deepEqual( sequence, [ + [ 'unsub', { key: 1 } ], + [ 'unsub', { key: 2 } ] + ], 'Stop when unsubscribing' ); + } ); }( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js index 0b42af4b..c1f1484c 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js @@ -2,54 +2,54 @@ var // Based on IPTest.php > testisIPv4 IPV4_CASES = [ - [false, false, 'Boolean false is not an IP'], - [false, true, 'Boolean true is not an IP'], - [false, '', 'Empty string is not an IP'], - [false, 'abc', '"abc" is not an IP'], - [false, ':', 'Colon is not an IP'], - [false, '124.24.52', 'IPv4 not enough quads'], - [false, '24.324.52.13', 'IPv4 out of range'], - [false, '.24.52.13', 'IPv4 starts with period'], - - [true, '124.24.52.13', '124.24.52.134 is a valid IP'], - [true, '1.24.52.13', '1.24.52.13 is a valid IP'], - [false, '74.24.52.13/20', 'IPv4 ranges are not recognized as valid IPs'] + [ false, false, 'Boolean false is not an IP' ], + [ false, true, 'Boolean true is not an IP' ], + [ false, '', 'Empty string is not an IP' ], + [ false, 'abc', '"abc" is not an IP' ], + [ false, ':', 'Colon is not an IP' ], + [ false, '124.24.52', 'IPv4 not enough quads' ], + [ false, '24.324.52.13', 'IPv4 out of range' ], + [ false, '.24.52.13', 'IPv4 starts with period' ], + + [ true, '124.24.52.13', '124.24.52.134 is a valid IP' ], + [ true, '1.24.52.13', '1.24.52.13 is a valid IP' ], + [ false, '74.24.52.13/20', 'IPv4 ranges are not recognized as valid IPs' ] ], // Based on IPTest.php > testisIPv6 IPV6_CASES = [ - [false, ':fc:100::', 'IPv6 starting with lone ":"'], - [false, 'fc:100:::', 'IPv6 ending with a ":::"'], - [false, 'fc:300', 'IPv6 with only 2 words'], - [false, 'fc:100:300', 'IPv6 with only 3 words'], - - [false, 'fc:100:a:d:1:e:ac:0::', 'IPv6 with 8 words ending with "::"'], - [false, 'fc:100:a:d:1:e:ac:0:1::', 'IPv6 with 9 words ending with "::"'], - - [false, ':::'], - [false, '::0:', 'IPv6 ending in a lone ":"'], - - [true, '::', 'IPv6 zero address'], - - [false, '::fc:100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words'], - [false, '::fc:100:a:d:1:e:ac:0:1', 'IPv6 with 9 words'], - - [false, ':fc::100', 'IPv6 starting with lone ":"'], - [false, 'fc::100:', 'IPv6 ending with lone ":"'], - [false, 'fc:::100', 'IPv6 with ":::" in the middle'], - - [true, 'fc::100', 'IPv6 with "::" and 2 words'], - [true, 'fc::100:a', 'IPv6 with "::" and 3 words'], - [true, 'fc::100:a:d', 'IPv6 with "::" and 4 words'], - [true, 'fc::100:a:d:1', 'IPv6 with "::" and 5 words'], - [true, 'fc::100:a:d:1:e', 'IPv6 with "::" and 6 words'], - [true, 'fc::100:a:d:1:e:ac', 'IPv6 with "::" and 7 words'], - [true, '2001::df', 'IPv6 with "::" and 2 words'], - [true, '2001:5c0:1400:a::df', 'IPv6 with "::" and 5 words'], - [true, '2001:5c0:1400:a::df:2', 'IPv6 with "::" and 6 words'], - - [false, 'fc::100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words'], - [false, 'fc::100:a:d:1:e:ac:0:1', 'IPv6 with 9 words'] + [ false, ':fc:100::', 'IPv6 starting with lone ":"' ], + [ false, 'fc:100:::', 'IPv6 ending with a ":::"' ], + [ false, 'fc:300', 'IPv6 with only 2 words' ], + [ false, 'fc:100:300', 'IPv6 with only 3 words' ], + + [ false, 'fc:100:a:d:1:e:ac:0::', 'IPv6 with 8 words ending with "::"' ], + [ false, 'fc:100:a:d:1:e:ac:0:1::', 'IPv6 with 9 words ending with "::"' ], + + [ false, ':::' ], + [ false, '::0:', 'IPv6 ending in a lone ":"' ], + + [ true, '::', 'IPv6 zero address' ], + + [ false, '::fc:100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words' ], + [ false, '::fc:100:a:d:1:e:ac:0:1', 'IPv6 with 9 words' ], + + [ false, ':fc::100', 'IPv6 starting with lone ":"' ], + [ false, 'fc::100:', 'IPv6 ending with lone ":"' ], + [ false, 'fc:::100', 'IPv6 with ":::" in the middle' ], + + [ true, 'fc::100', 'IPv6 with "::" and 2 words' ], + [ true, 'fc::100:a', 'IPv6 with "::" and 3 words' ], + [ true, 'fc::100:a:d', 'IPv6 with "::" and 4 words' ], + [ true, 'fc::100:a:d:1', 'IPv6 with "::" and 5 words' ], + [ true, 'fc::100:a:d:1:e', 'IPv6 with "::" and 6 words' ], + [ true, 'fc::100:a:d:1:e:ac', 'IPv6 with "::" and 7 words' ], + [ true, '2001::df', 'IPv6 with "::" and 2 words' ], + [ true, '2001:5c0:1400:a::df', 'IPv6 with "::" and 5 words' ], + [ true, '2001:5c0:1400:a::df:2', 'IPv6 with "::" and 6 words' ], + + [ false, 'fc::100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words' ], + [ false, 'fc::100:a:d:1:e:ac:0:1', 'IPv6 with 9 words' ] ]; Array.prototype.push.apply( IPV6_CASES, @@ -70,7 +70,7 @@ '::fc:100:a:d:1:e:ac', 'fc:100:a:d:1:e:ac:0' ], function ( el ) { - return [[ true, el, el + ' is a valid IP' ]]; + return [ [ true, el, el + ' is a valid IP' ] ]; } ) ); @@ -83,7 +83,7 @@ }, messages: { // Used by accessKeyLabel in test for addPortletLink - 'brackets': '[$1]', + brackets: '[$1]', 'word-separator': ' ' } } ) ); @@ -92,7 +92,7 @@ assert.equal( mw.util.rawurlencode( 'Test:A & B/Here' ), 'Test%3AA%20%26%20B%2FHere' ); } ); - QUnit.test( 'wikiUrlencode', 10, function ( assert ) { + QUnit.test( 'wikiUrlencode', 11, function ( assert ) { assert.equal( mw.util.wikiUrlencode( 'Test:A & B/Here' ), 'Test:A_%26_B/Here' ); // See also wfUrlencodeTest.php#provideURLS $.each( { @@ -102,6 +102,7 @@ ':': ':', ';@$-_.!*': ';@$-_.!*', '/': '/', + '~': '~', '[]': '%5B%5D', '<>': '%3C%3E', '\'': '%27' @@ -133,10 +134,10 @@ QUnit.test( 'wikiScript', 4, function ( assert ) { mw.config.set( { - 'wgScript': '/w/i.php', // customized wgScript for bug 39103 - 'wgLoadScript': '/w/l.php', // customized wgLoadScript for bug 39103 - 'wgScriptPath': '/w', - 'wgScriptExtension': '.php' + wgScript: '/w/i.php', // customized wgScript for bug 39103 + wgLoadScript: '/w/l.php', // customized wgLoadScript for bug 39103 + wgScriptPath: '/w', + wgScriptExtension: '.php' } ); assert.equal( mw.util.wikiScript(), mw.config.get( 'wgScript' ), @@ -175,10 +176,10 @@ url = 'http://example.org/#&foo=bad'; assert.strictEqual( mw.util.getParamValue( 'foo', url ), null, 'Ignore hash if param is not in querystring but in hash (bug 27427)' ); - url = 'example.org?' + $.param( { 'TEST': 'a b+c' } ); + url = 'example.org?' + $.param( { TEST: 'a b+c' } ); assert.strictEqual( mw.util.getParamValue( 'TEST', url ), 'a b+c', 'Bug 30441: getParamValue must understand "+" encoding of space' ); - url = 'example.org?' + $.param( { 'TEST': 'a b+c d' } ); // check for sloppy code from r95332 :) + url = 'example.org?' + $.param( { TEST: 'a b+c d' } ); // check for sloppy code from r95332 :) assert.strictEqual( mw.util.getParamValue( 'TEST', url ), 'a b+c d', 'Bug 30441: getParamValue must understand "+" encoding of space (multiple spaces)' ); } ); @@ -240,7 +241,7 @@ 'ResourceLoader', 't-rl', 'More info about ResourceLoader on MediaWiki.org ', 'l' ); - assert.ok( $.isDomElement( tbRL ), 'addPortletLink returns a valid DOM Element according to $.isDomElement' ); + assert.ok( tbRL && tbRL.nodeType, 'addPortletLink returns a DOM Node' ); tbMW = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/', 'MediaWiki.org', 't-mworg', 'Go to MediaWiki.org', 'm', tbRL ); @@ -265,7 +266,7 @@ ); assert.equal( $tbMW.closest( '.portlet' ).attr( 'id' ), 'p-test-tb', 'Link was inserted within correct portlet' ); - assert.strictEqual( $tbMW.next()[0], tbRL, 'Link is in the correct position (nextnode as Node object)' ); + assert.strictEqual( $tbMW.next()[ 0 ], tbRL, 'Link is in the correct position (nextnode as Node object)' ); cuQuux = mw.util.addPortletLink( 'p-test-custom', '#', 'Quux', null, 'Example [shift-x]', 'q' ); $cuQuux = $( cuQuux ); @@ -281,7 +282,7 @@ tbRLDM = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM', 'Default modules', 't-rldm', 'List of all default modules ', 'd', '#t-rl' ); - assert.strictEqual( $( tbRLDM ).next()[0], tbRL, 'Link is in the correct position (CSS selector as nextnode)' ); + assert.strictEqual( $( tbRLDM ).next()[ 0 ], tbRL, 'Link is in the correct position (CSS selector as nextnode)' ); caFoo = mw.util.addPortletLink( 'p-test-views', '#', 'Foo' ); @@ -289,19 +290,19 @@ assert.strictEqual( $( caFoo ).find( 'span' ).length, 1, 'A <span> element should be added for porlets with vectorTabs class.' ); addedAfter = mw.util.addPortletLink( 'p-test-tb', '#', 'After foo', 'post-foo', 'After foo', null, $( tbRL ) ); - assert.strictEqual( $( addedAfter ).next()[0], tbRL, 'Link is in the correct position (jQuery object as nextnode)' ); + assert.strictEqual( $( addedAfter ).next()[ 0 ], tbRL, 'Link is in the correct position (jQuery object as nextnode)' ); // test case - nonexistent id as next node tbRLDMnonexistentid = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM', 'Default modules', 't-rldm-nonexistent', 'List of all default modules ', 'd', '#t-rl-nonexistent' ); - assert.equal( tbRLDMnonexistentid, $( '#p-test-tb li:last' )[0], 'Fallback to adding at the end (nextnode non-matching CSS selector)' ); + assert.equal( tbRLDMnonexistentid, $( '#p-test-tb li:last' )[ 0 ], 'Fallback to adding at the end (nextnode non-matching CSS selector)' ); // test case - empty jquery object as next node tbRLDMemptyjquery = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM', 'Default modules', 't-rldm-empty-jquery', 'List of all default modules ', 'd', $( '#t-rl-nonexistent' ) ); - assert.equal( tbRLDMemptyjquery, $( '#p-test-tb li:last' )[0], 'Fallback to adding at the end (nextnode as empty jQuery object)' ); + assert.equal( tbRLDMemptyjquery, $( '#p-test-tb li:last' )[ 0 ], 'Fallback to adding at the end (nextnode as empty jQuery object)' ); } ); QUnit.test( 'validateEmail', 6, function ( assert ) { @@ -319,23 +320,23 @@ QUnit.test( 'isIPv6Address', 40, function ( assert ) { $.each( IPV6_CASES, function ( i, ipCase ) { - assert.strictEqual( mw.util.isIPv6Address( ipCase[1] ), ipCase[0], ipCase[2] ); + assert.strictEqual( mw.util.isIPv6Address( ipCase[ 1 ] ), ipCase[ 0 ], ipCase[ 2 ] ); } ); } ); QUnit.test( 'isIPv4Address', 11, function ( assert ) { $.each( IPV4_CASES, function ( i, ipCase ) { - assert.strictEqual( mw.util.isIPv4Address( ipCase[1] ), ipCase[0], ipCase[2] ); + assert.strictEqual( mw.util.isIPv4Address( ipCase[ 1 ] ), ipCase[ 0 ], ipCase[ 2 ] ); } ); } ); QUnit.test( 'isIPAddress', 51, function ( assert ) { $.each( IPV4_CASES, function ( i, ipCase ) { - assert.strictEqual( mw.util.isIPv4Address( ipCase[1] ), ipCase[0], ipCase[2] ); + assert.strictEqual( mw.util.isIPv4Address( ipCase[ 1 ] ), ipCase[ 0 ], ipCase[ 2 ] ); } ); $.each( IPV6_CASES, function ( i, ipCase ) { - assert.strictEqual( mw.util.isIPv6Address( ipCase[1] ), ipCase[0], ipCase[2] ); + assert.strictEqual( mw.util.isIPv6Address( ipCase[ 1 ] ), ipCase[ 0 ], ipCase[ 2 ] ); } ); } ); }( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/startup.test.js b/tests/qunit/suites/resources/startup.test.js index 6011961a..5ea7a816 100644 --- a/tests/qunit/suites/resources/startup.test.js +++ b/tests/qunit/suites/resources/startup.test.js @@ -86,7 +86,9 @@ 'Mozilla/5.0 (Series40; NokiaX3-02/05.60; Profile/MIDP-2.1 Configuration/CLDC-1.1) Gecko/20100401 S40OviBrowser/3.2.0.0.6', 'Mozilla/5.0 (Series40; Nokia305/05.92; Profile/MIDP-2.1 Configuration/CLDC-1.1) Gecko/20100401 S40OviBrowser/3.7.0.0.11', // Google Glass - 'Mozilla/5.0 (Linux; U; Android 4.0.4; en-us; Glass 1 Build/IMM76L; XE11) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' + 'Mozilla/5.0 (Linux; U; Android 4.0.4; en-us; Glass 1 Build/IMM76L; XE11) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', + // MeeGo + 'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13' ], // No explicit support for or against these browsers, they're given a shot at Grade A. gradeX: [ diff --git a/tests/testHelpers.inc b/tests/testHelpers.inc index 6d3ac2f5..16f458bf 100644 --- a/tests/testHelpers.inc +++ b/tests/testHelpers.inc @@ -695,7 +695,7 @@ class DelayedParserTest { * @throws MWException */ public function unleash( &$parserTest ) { - if ( !( $parserTest instanceof ParserTest || $parserTest instanceof NewParserTest ) ) { + if ( !( $parserTest instanceof ParserTest || $parserTest instanceof NewParserTest ) ) { throw new MWException( __METHOD__ . " must be passed an instance of ParserTest or " . "NewParserTest classes\n" ); } |