From: jenkins-bot Date: Wed, 6 Jan 2016 23:24:34 +0000 (+0000) Subject: Merge "jquery.accessKeyLabel: make modifier info public" X-Git-Tag: 1.31.0-rc.0~8421 X-Git-Url: http://git.cyclocoop.org/%7B%24admin_url%7Dcompta/comptes/journal.php?a=commitdiff_plain;h=b24a0048185fe7c4d86f8b55872ad749c6ab52e6;hp=5c8c989e3334478f2724baa9788410e2994c2b3a;p=lhc%2Fweb%2Fwiklou.git Merge "jquery.accessKeyLabel: make modifier info public" --- diff --git a/Gemfile b/Gemfile index 392558ce8f..ee09906560 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,4 @@ -# ruby=ruby-2.1.2 -# ruby-gemset=core - source 'https://rubygems.org' -gem 'mediawiki_selenium', '~> 1.2.1' -gem 'rubocop', require: false +gem 'mediawiki_selenium', '~> 1.6.3' +gem 'rubocop', '~> 0.32.1', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 3a695ef75a..4d0203a430 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,7 +5,7 @@ GEM astrolabe (1.3.0) parser (>= 2.2.0.pre.3, < 3.0) builder (3.2.2) - childprocess (0.5.6) + childprocess (0.5.8) ffi (~> 1.0, >= 1.0.11) cucumber (1.3.20) builder (>= 2.1.2) @@ -13,15 +13,15 @@ GEM gherkin (~> 2.12) multi_json (>= 1.7.5, < 2.0) multi_test (>= 0.1.2) - data_magic (0.21) + data_magic (0.22) faker (>= 1.1.2) - yml_reader (>= 0.4) + yml_reader (>= 0.6) diff-lcs (1.2.5) - domain_name (0.5.24) + domain_name (0.5.25) unf (>= 0.0.5, < 1.0.0) - faker (1.4.3) + faker (1.6.1) i18n (~> 0.5) - faraday (0.9.1) + faraday (0.9.2) multipart-post (>= 1.2, < 3) faraday-cookie_jar (0.0.6) faraday (>= 0.7.4) @@ -29,29 +29,30 @@ GEM ffi (1.9.10) gherkin (2.12.2) multi_json (~> 1.3) - headless (1.0.2) + headless (2.2.0) http-cookie (1.0.2) domain_name (~> 0.5) i18n (0.7.0) json (1.8.3) - mediawiki_api (0.4.1) + mediawiki_api (0.5.0) faraday (~> 0.9, >= 0.9.0) faraday-cookie_jar (~> 0.0, >= 0.0.6) - mediawiki_selenium (1.2.1) - cucumber (~> 1.3, >= 1.3.10) - headless (~> 1.0, >= 1.0.1) + mediawiki_selenium (1.6.3) + cucumber (~> 1.3, >= 1.3.20) + headless (~> 2.0, >= 2.1.0) json (~> 1.8, >= 1.8.1) - mediawiki_api (~> 0.2, >= 0.2.1) + mediawiki_api (~> 0.5, >= 0.5.0) page-object (~> 1.0) rest-client (~> 1.6, >= 1.6.7) + rspec-core (~> 2.14, >= 2.14.4) rspec-expectations (~> 2.14, >= 2.14.4) syntax (~> 1.2, >= 1.2.0) thor (~> 0.19, >= 0.19.1) - mime-types (2.6.1) + mime-types (2.99) multi_json (1.11.2) multi_test (0.1.2) multipart-post (2.0.0) - netrc (0.10.3) + netrc (0.11.0) page-object (1.1.0) page_navigation (>= 0.9) selenium-webdriver (>= 2.44.0) @@ -66,6 +67,7 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 3.0) netrc (~> 0.7) + rspec-core (2.99.2) rspec-expectations (2.99.2) diff-lcs (>= 1.1.3, < 2.0) rubocop (0.32.1) @@ -76,7 +78,7 @@ GEM ruby-progressbar (~> 1.4) ruby-progressbar (1.7.5) rubyzip (1.1.7) - selenium-webdriver (2.46.2) + selenium-webdriver (2.48.1) childprocess (~> 0.5) multi_json (~> 1.0) rubyzip (~> 1.0) @@ -86,17 +88,14 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.1) - watir-webdriver (0.8.0) + watir-webdriver (0.9.1) selenium-webdriver (>= 2.46.2) websocket (1.2.2) - yml_reader (0.5) + yml_reader (0.6) PLATFORMS ruby DEPENDENCIES - mediawiki_selenium (~> 1.2.1) - rubocop - -BUNDLED WITH - 1.10.5 + mediawiki_selenium (~> 1.6.3) + rubocop (~> 0.32.1) diff --git a/HISTORY b/HISTORY index e9de01ab2c..e57d346316 100644 --- a/HISTORY +++ b/HISTORY @@ -1,6 +1,40 @@ Change notes from older releases. For current info see RELEASE-NOTES-1.27. -== MediaWiki 1.26 == += MediaWiki 1.26 = + +== MediaWiki 1.26.2 == + +This is a maintenance release of the MediaWiki 1.26 branch. + +=== Changes since 1.26.1 === +* (T121892) Fix fatal error on some Special pages, introduced in 1.26.1. + +== MediaWiki 1.26.1 == + +This is a maintenance release of the MediaWiki 1.26 branch. + +=== Changes since 1.26.0 === +* (T117899) SECURITY: $wgArticlePath can no longer be set to relative paths + that do not begin with a slash. This enabled trivial XSS attacks. + Configuration values such as "http://my.wiki.com/wiki/$1" are fine, as are + "/wiki/$1". A value such as "$1" or "wiki/$1" is not and will now throw an + error. +* (T119309) SECURITY: Use hash_compare() for edit token comparison +* (T118032) SECURITY: Don't allow cURL to interpret POST parameters starting + with '@' as file uploads +* (T115522) SECURITY: Passwords generated by User::randomPassword() can no + longer be shorter than $wgMinimalPasswordLength +* (T97897) SECURITY: Improve IP parsing and trimming. Previous behavior could + result in improper blocks being issued +* (T109724) SECURITY: Special:MyPage, Special:MyTalk, Special:MyContributions + and related pages no longer use HTTP redirects and are now redirected by + MediaWiki +* Fixed ConfigException in ExpandTemplates due to AlwaysUseTidy. +* Fixed stray literal \n in Special:Search. +* Fix issue that breaks HHVM Repo Authorative mode. +* (T120267) Work around APCu memory corruption bug + +== MediaWiki 1.26.0 == === Configuration changes in 1.26 === * $wgPasswordResetRoutes['email'] = true by default. @@ -91,7 +125,7 @@ Change notes from older releases. For current info see RELEASE-NOTES-1.27. documentation for mw.Upload.Dialog, mw.Upload.BookletLayout and its subclasses for more information. -== extension.json changes in 1.26 == +=== extension.json changes in 1.26 === * (T99344) The extension.json schema is now versioned. All extensions and skins should set a "manifest_version" property corresponding to the schema version they were written for. The only supported version @@ -244,7 +278,94 @@ changes to languages because of Phabricator reports. * $wgDeferredUpdateList was removed. * DeferredUpdates::addHTMLCacheUpdate() was removed. -== MediaWiki 1.25 == += MediaWiki 1.25 = + +== MediaWiki 1.25.5 == + +This is a maintenance release of the MediaWiki 1.25 branch. + +=== Changes since 1.25.4 === +* (T121892) Fix fatal error on some Special pages, introduced in 1.25.4. + +== MediaWiki 1.25.4 == + +This is a security and maintenance release of the MediaWiki 1.25 branch. + +=== Changes since 1.25.3 === +* (T117899) SECURITY: $wgArticlePath can no longer be set to relative paths + that do not begin with a slash. This enabled trivial XSS attacks. + Configuration values such as "http://my.wiki.com/wiki/$1" are fine, as are + "/wiki/$1". A value such as "$1" or "wiki/$1" is not and will now throw an + error. +* (T119309) SECURITY: Use hash_compare() for edit token comparison +* (T118032) SECURITY: Don't allow cURL to interpret POST parameters starting + with '@' as file uploads +* (T115522) SECURITY: Passwords generated by User::randomPassword() can no + longer be shorter than $wgMinimalPasswordLength +* (T97897) SECURITY: Improve IP parsing and trimming. Previous behavior could + result in improper blocks being issued +* (T109724) SECURITY: Special:MyPage, Special:MyTalk, Special:MyContributions + and related pages no longer use HTTP redirects and are now redirected by + MediaWiki +* (T103237) $wgUseGzip had no effect when using file cache. +* (T114606) mw.notify was not correctly fixed to the page if + initialized while not at the top of the page. +* Fix issue that breaks HHVM Repo Authorative mode. + +== MediaWiki 1.25.3 == + +This is a security and maintenance release of the MediaWiki 1.25 branch. + +=== Changes since 1.25.2 === + +* (T98975) Fix having multiple callbacks for a single hook. +* (T107632) maintenance/refreshLinks.php did not always remove all links + pointing to nonexistent pages. +* (T104142) $wgEmergencyContact and $wgPasswordSender now use their default + value if set to an empty string. +* (T62174) Provide fallbacks for use of mb_convert_encoding() in + HtmlFormatter. It was causing an error when accessing the api help page + if the mbstring PHP extension was not installed. +* (T105896) Confirmation emails would sometimes contain invalid codes. +* (T105597) Fixed edit stash inclusion queries. +* (T91850) SECURITY: Add throttle check in ApiUpload and SpecialUpload +* (T91203, T91205) SECURITY: API: Improve validation in chunked uploading +* (T95589) SECURITY: RevDel: Check all revisions for suppression, not just the + first +* (T108616) SECURITY: Avoid exposure of local path in PNG thumbnails + +== MediaWiki 1.25.2 == + +This is a security and maintenance release of the MediaWiki 1.25 branch. + +=== Changes since 1.25.1 === + +* (T94116) SECURITY: Compare API watchlist token in constant time +* (T97391) SECURITY: Escape error message strings in thumb.php +* (T106893) SECURITY: Don't leak autoblocked IP addresses on + Special:DeletedContributions +* (T102562) Fix InstantCommons parameters to handle the new HTTPS-only + policy of Wikimedia Commons. +* (T100767) Setting a configuration setting for skin or extension to + false in LocalSettings.php was not working. +* (T100635) API action=opensearch json output no longer breaks when + $wgDebugToolbar is enabled. +* (T102522) Using an extension.json or skin.json file which has + a "manifest_version" property for 1.26 compatability will no longer + trigger warnings. +* (T86156) Running updateSearchIndex.php will not throw an error as + page_restrictions has been added to the locked table list. +* Special:Version would throw notices if using SVN due to an incorrectly + named variable. Add an additional check that an index is defined. + +== MediaWiki 1.25.1 == + +This is a bug fix release of the MediaWiki 1.25 branch. + +=== Changes since 1.25 === +* (T100351) Fix syntax errors in extension.json of ConfirmEdit extension + +== MediaWiki 1.25.0 == === Configuration changes in 1.25 === * $wgPageShowWatchingUsers was removed. @@ -748,49 +869,108 @@ changes to languages because of Bugzilla reports. loadedScripts object, from wikibits.js (deprecated since 1.17) now emit warnings through mw.log.warn when accessed. += MediaWiki 1.24 = -== Compatibility == +== MediaWiki 1.24.6 == -MediaWiki 1.25 requires PHP 5.3.3 or later. There is experimental support for -HHVM 3.3.0. +This is a maintenance release of the MediaWiki 1.24 branch. -MySQL is the recommended DBMS. PostgreSQL or SQLite can also be used, but -support for them is somewhat less mature. There is experimental support for -Oracle and Microsoft SQL Server. +=== Changes since 1.24.5 === +* (T121892) Fix fatal error on some Special pages, introduced in 1.24.5. -The supported versions are: +== MediaWiki 1.24.5 == -* MySQL 5.0.3 or later -* PostgreSQL 8.3 or later -* SQLite 3.3.7 or later -* Oracle 9.0.1 or later -* Microsoft SQL Server 2005 (9.00.1399) +This is a security and maintenance release of the MediaWiki 1.23 branch. -== Upgrading == +=== Changes since 1.24.4 === +* (T117899) SECURITY: $wgArticlePath can no longer be set to relative paths + that do not begin with a slash. This enabled trivial XSS attacks. + Configuration values such as "http://my.wiki.com/wiki/$1" are fine, as are + "/wiki/$1". A value such as "$1" or "wiki/$1" is not and will now throw an + error. +* (T119309) SECURITY: Use hash_compare() for edit token comparison +* (T118032) SECURITY: Don't allow cURL to interpret POST parameters starting + with '@' as file uploads +* (T115522) SECURITY: Passwords generated by User::randomPassword() can no + longer be shorter than $wgMinimalPasswordLength +* (T97897) SECURITY: Improve IP parsing and trimming. Previous behavior could + result in improper blocks being issued +* (T109724) SECURITY: Special:MyPage, Special:MyTalk, Special:MyContributions + and related pages no longer use HTTP redirects and are now redirected by + MediaWiki +* (T103237) $wgUseGzip had no effect when using file cache. -1.25 has several database changes since 1.24, and will not work without schema -updates. Note that due to changes to some very large tables like the revision -table, the schema update may take quite long (minutes on a medium sized site, -many hours on a large site). +== MediaWiki 1.24.4 == -If upgrading from before 1.11, and you are using a wiki as a commons -repository, make sure that it is updated as well. Otherwise, errors may arise -due to database schema changes. +This is a security and maintenance release of the MediaWiki 1.24 branch. -If upgrading from before 1.7, you may want to run refreshLinks.php to ensure -new database fields are filled with data. +=== Changes since 1.24.3 === -If you are upgrading from MediaWiki 1.4.x or earlier, you should upgrade to -1.5 first. The upgrade script maintenance/upgrade1_5.php has been removed -with MediaWiki 1.21. +* (T91653) Minimal PSR-3 debug logger to support backports from 1.25+. +* (T68650) Fix indexing of moved pages with PostgreSQL. Requires running + update.php to fix. +* (T91850) SECURITY: Add throttle check in ApiUpload and SpecialUpload +* (T91203, T91205) SECURITY: API: Improve validation in chunked uploading +* (T95589) SECURITY: RevDel: Check all revisions for suppression, not just the + first +* (T108616) SECURITY: Avoid exposure of local path in PNG thumbnails -Don't forget to always back up your database before upgrading! +== MediaWiki 1.24.3 == -See the file UPGRADE for more detailed upgrade instructions. +This is a security and maintenance release of the MediaWiki 1.24 branch. -For notes on 1.24.x and older releases, see HISTORY. +=== Changes since 1.24.2 === -== MediaWiki 1.24 == +* (T94116) SECURITY: Compare API watchlist token in constant time +* (T97391) SECURITY: Escape error message strings in thumb.php +* (T106893) SECURITY: Don't leak autoblocked IP addresses on + Special:DeletedContributions +* Update jQuery from v1.11.2 to v1.11.3. +* (T102562) Fix InstantCommons parameters to handle the new HTTPS-only + policy of Wikimedia Commons. + +== MediaWiki 1.24.2 == + +This is a security and maintenance release of the MediaWiki 1.24 branch. + +=== Changes since 1.24.1 === + +* (T85848, T71210) SECURITY: Don't parse XMP blocks that contain XML entities, + to prevent various DoS attacks. +* (T85848) SECURITY: Don't allow directly calling Xml::isWellFormed, to reduce + likelihood of DoS. +* (T88310) SECURITY: Always expand xml entities when checking SVG's. +* (T73394) SECURITY: Escape > in Html::expandAttributes to prevent XSS. +* (T85855) SECURITY: Don't execute another user's CSS or JS on preview. +* (T64685) SECURITY: Allow setting maximal password length to prevent DoS when + using PBKDF2. +* (T85349, T85850, T86711) SECURITY: Multiple issues fixed in SVG filtering to + prevent XSS and protect viewer's privacy. +* Fix case of SpecialAllPages/SpecialAllMessages in SpecialPageFactory to fix + loading these special pages when $wgAutoloadAttemptLowercase is false. +* (bug T70087) Fix Special:ActiveUsers page for installations using + PostgreSQL. +* (bug T76254) Fix deleting of pages with PostgreSQL. Requires a schema change + and running update.php to fix. + +== MediaWiki 1.24.1 == + +This is a security and maintenance release of the MediaWiki 1.24 branch. + +=== Changes since 1.24.0 === + +* (bug T76686) [SECURITY] thumb.php outputs wikitext message as raw HTML, which + could lead to xss. Permission to edit MediaWiki namespace is required to + exploit this. +* (bug T77028) [SECURITY] Malicious site can bypass CORS restrictions in + $wgCrossSiteAJAXdomains in API calls if it only included an allowed domain as + part of its name. +* (bug T74222) The original patch for T74222 was reverted as unnecessary. +* Fixed a couple of entries in RELEASE-NOTES-1.24. +* (bug T76168) OutputPage: Add accessors for some protected properties. +* (bug T74834) Make 1.24 branch directly installable under PostgreSQL. + +== MediaWiki 1.24.0 == === Configuration changes in 1.24 === * MediaWiki will no longer run if register_globals is enabled. It has been @@ -1484,8 +1664,204 @@ of files that are no longer available follows. * skins/common/images/icons/fileicon.png * skins/common/images/ksh/button_S_italic.png += MediaWiki 1.23 = + +== MediaWiki 1.23.13 == + +This is a maintenance release of the MediaWiki 1.23 branch. + +=== Changes since 1.23.12 === +* (T121892) Fix fatal errors on some Special pages, introduced in 1.23.12. + +== MediaWiki 1.23.12 == + +This is a security and maintenance release of the MediaWiki 1.23 branch. + +=== Changes since 1.23.11 === +* (T117899) SECURITY: $wgArticlePath can no longer be set to relative paths + that do not begin with a slash. This enabled trivial XSS attacks. + Configuration values such as "http://my.wiki.com/wiki/$1" are fine, as are + "/wiki/$1". A value such as "$1" or "wiki/$1" is not and will now throw an + error. +* (T119309) SECURITY: Use hash_compare() for edit token comparison +* (T118032) SECURITY: Don't allow cURL to interpret POST parameters starting + with '@' as file uploads +* (T115522) SECURITY: Passwords generated by User::randomPassword() can no + longer be shorter than $wgMinimalPasswordLength +* (T97897) SECURITY: Improve IP parsing and trimming. Previous behavior could + result in improper blocks being issued +* (T109724) SECURITY: Special:MyPage, Special:MyTalk, Special:MyContributions + and related pages no longer use HTTP redirects and are now redirected by + MediaWiki + +== MediaWiki 1.23.11 == + +This is a security and maintenance release of the MediaWiki 1.23 branch. + +=== Changes since 1.23.10 === + +* (T91850) SECURITY: Add throttle check in ApiUpload and SpecialUpload +* (T91203, T91205) SECURITY: API: Improve validation in chunked uploading +* (T108616) SECURITY: Avoid exposure of local path in PNG thumbnails + +== MediaWiki 1.23.10 == + +This is a security and maintenance release of the MediaWiki 1.23 branch. + +=== Changes since 1.23.9 === + +* (T94116) SECURITY: Compare API watchlist token in constant time +* (T97391) SECURITY: Escape error message strings in thumb.php +* (T106893) SECURITY: Don't leak autoblocked IP addresses on + Special:DeletedContributions +* (bug 67644) Make AutoLoaderTest handle namespaces +* (T91653) Minimal PSR-3 debug logger to support backports from 1.25+. +* (T102562) Fix InstantCommons parameters to handle the new HTTPS-only + policy of Wikimedia Commons. + +== MediaWiki 1.23.9 == + +This is a security and maintenance release of the MediaWiki 1.23 branch. + +=== Changes since 1.23.8 === + +* (T85848, T71210) SECURITY: Don't parse XMP blocks that contain XML entities, + to prevent various DoS attacks. +* (T85848) SECURITY: Don't allow directly calling Xml::isWellFormed, to reduce + likelihood of DoS. +* (T88310) SECURITY: Always expand xml entities when checking SVG's. +* (T73394) SECURITY: Escape > in Html::expandAttributes to prevent XSS. +* (T85855) SECURITY: Don't execute another user's CSS or JS on preview. +* (T85349, T85850, T86711) SECURITY: Multiple issues fixed in SVG filtering to + prevent XSS and protect viewer's privacy. +* (bug T68650) Fix indexing of moved pages with PostgreSQL. Requires running + update.php to fix. +* (bug T70087) Fix Special:ActiveUsers page for installations using + PostgreSQL. + +== MediaWiki 1.23.8 == + +This is a security and maintenance release of the MediaWiki 1.23 branch. + +=== Changes since 1.23.7 === + +* (bug T76686) [SECURITY] thumb.php outputs wikitext message as raw HTML, which + could lead to xss. Permission to edit MediaWiki namespace is required to + exploit this. +* (bug T77028) [SECURITY] Malicious site can bypass CORS restrictions in + $wgCrossSiteAJAXdomains in API calls if it only included an allowed domain as + part of its name. +* (bug T74222) The original patch for T74222 was reverted as unnecessary. + +== MediaWiki 1.23.7 == + +This is a security and maintenance release of the MediaWiki 1.23 branch. + +=== Changes since 1.23.6 === + +* (bugs 66776, 71478) SECURITY: User PleaseStand reported a way to inject code + into API clients that used format=php to process pages that underwent flash + policy mangling. This was fixed along with improving how the mangling was done + for format=json, and allowing sites to disable the mangling using + $wgMangleFlashPolicy. +* (bug 70901) SECURITY: User Jackmcbarn reported that the ability to update + the content model for a page could allow an unprivileged attacker to edit + another user's common.js under certain circumstances. The user right + "editcontentmodel" was added, and is needed to change a revision's content + model. +* (bug 71111) SECURITY: User PleaseStand reported that on wikis that allow raw + HTML, it is not safe to preview wikitext coming from an untrusted source such + as a cross-site request. Thus add an edit token to the form, and when raw HTML + is allowed, ensure the token is provided before showing the preview. This + check is not performed on wikis that both allow raw HTML and anonymous + editing, since there are easier ways to exploit that scenario. +* (bug 72222) SECURITY: Do not show log action when the entry is revdeleted with + DELETED_ACTION. NOTICE: this may be reverted in a future release pending a + public RFC about the desired functionality. This issue was reported by user + Bawolff. +* (bug 71621) Make allowing site-wide styles on restricted special pages a + config option. +* (bug 42723) Added updated version history from 1.19.2 to 1.22.13 +* $wgMangleFlashPolicy was added to make MediaWiki's mangling of anything that + might be a flash policy directive configurable. + +== MediaWiki 1.23.6 == -== MediaWiki 1.23 == +This is a maintenance release of the MediaWiki 1.23 branch. + +=== Changes since 1.23.5 === +* (Bug 72274) Job queue not running (HTTP 411) due to missing + Content-Length: header +* (Bug 67440) Allow classes to be registered properly from installer + +== MediaWiki 1.23.5 == + +This is a security release of the MediaWiki 1.23 branch. + +=== Changes since 1.23.4 === +* (bug 70672) SECURITY: OutputPage: Remove separation of css and js module + allowance. + +== MediaWiki 1.23.4 == + +This is a security and maintenance release of the MediaWiki 1.23 branch. + +=== Changes since 1.23.3 === + +* (bug 69008) SECURITY: Enhance CSS filtering in SVG files. Filter + + + + + + + + + + + + + + + + + + + + diff --git a/resources/src/mediawiki/bookletlayout/option2/noderiv.png b/resources/src/mediawiki/bookletlayout/option2/noderiv.png new file mode 100644 index 0000000000..afac81f4b5 Binary files /dev/null and b/resources/src/mediawiki/bookletlayout/option2/noderiv.png differ diff --git a/resources/src/mediawiki/bookletlayout/option2/noderiv.svg b/resources/src/mediawiki/bookletlayout/option2/noderiv.svg new file mode 100644 index 0000000000..96d80845ed --- /dev/null +++ b/resources/src/mediawiki/bookletlayout/option2/noderiv.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/src/mediawiki/bookletlayout/option2/ownwork.png b/resources/src/mediawiki/bookletlayout/option2/ownwork.png new file mode 100644 index 0000000000..69df378887 Binary files /dev/null and b/resources/src/mediawiki/bookletlayout/option2/ownwork.png differ diff --git a/resources/src/mediawiki/bookletlayout/option2/ownwork.svg b/resources/src/mediawiki/bookletlayout/option2/ownwork.svg new file mode 100644 index 0000000000..dc660c83ff --- /dev/null +++ b/resources/src/mediawiki/bookletlayout/option2/ownwork.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/src/mediawiki/bookletlayout/option2/useful.png b/resources/src/mediawiki/bookletlayout/option2/useful.png new file mode 100644 index 0000000000..d5b9429d05 Binary files /dev/null and b/resources/src/mediawiki/bookletlayout/option2/useful.png differ diff --git a/resources/src/mediawiki/bookletlayout/option2/useful.svg b/resources/src/mediawiki/bookletlayout/option2/useful.svg new file mode 100644 index 0000000000..2af3f938fa --- /dev/null +++ b/resources/src/mediawiki/bookletlayout/option2/useful.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + diff --git a/resources/src/mediawiki/bookletlayout/option4/camera.png b/resources/src/mediawiki/bookletlayout/option4/camera.png new file mode 100644 index 0000000000..a48f9fbf4a Binary files /dev/null and b/resources/src/mediawiki/bookletlayout/option4/camera.png differ diff --git a/resources/src/mediawiki/bookletlayout/option4/camera.svg b/resources/src/mediawiki/bookletlayout/option4/camera.svg new file mode 100644 index 0000000000..b1103968eb --- /dev/null +++ b/resources/src/mediawiki/bookletlayout/option4/camera.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/src/mediawiki/bookletlayout/option4/graphics.png b/resources/src/mediawiki/bookletlayout/option4/graphics.png new file mode 100644 index 0000000000..5613f8800c Binary files /dev/null and b/resources/src/mediawiki/bookletlayout/option4/graphics.png differ diff --git a/resources/src/mediawiki/bookletlayout/option4/graphics.svg b/resources/src/mediawiki/bookletlayout/option4/graphics.svg new file mode 100644 index 0000000000..c32f79fb17 --- /dev/null +++ b/resources/src/mediawiki/bookletlayout/option4/graphics.svg @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/src/mediawiki/bookletlayout/option4/guide.html b/resources/src/mediawiki/bookletlayout/option4/guide.html new file mode 100644 index 0000000000..c747851b1a --- /dev/null +++ b/resources/src/mediawiki/bookletlayout/option4/guide.html @@ -0,0 +1,12 @@ +
+
+ +
+
+ +
+
+
+
+ +
diff --git a/resources/src/mediawiki/bookletlayout/option4/search-ltr.png b/resources/src/mediawiki/bookletlayout/option4/search-ltr.png new file mode 100644 index 0000000000..26d6beb965 Binary files /dev/null and b/resources/src/mediawiki/bookletlayout/option4/search-ltr.png differ diff --git a/resources/src/mediawiki/bookletlayout/option4/search-ltr.svg b/resources/src/mediawiki/bookletlayout/option4/search-ltr.svg new file mode 100644 index 0000000000..67c4ef0890 --- /dev/null +++ b/resources/src/mediawiki/bookletlayout/option4/search-ltr.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/src/mediawiki/bookletlayout/option4/search-rtl.png b/resources/src/mediawiki/bookletlayout/option4/search-rtl.png new file mode 100644 index 0000000000..3305928cde Binary files /dev/null and b/resources/src/mediawiki/bookletlayout/option4/search-rtl.png differ diff --git a/resources/src/mediawiki/bookletlayout/option4/search-rtl.svg b/resources/src/mediawiki/bookletlayout/option4/search-rtl.svg new file mode 100644 index 0000000000..17c54d2f63 --- /dev/null +++ b/resources/src/mediawiki/bookletlayout/option4/search-rtl.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/src/mediawiki/bookletlayout/option4/website-ltr.png b/resources/src/mediawiki/bookletlayout/option4/website-ltr.png new file mode 100644 index 0000000000..7e007514ad Binary files /dev/null and b/resources/src/mediawiki/bookletlayout/option4/website-ltr.png differ diff --git a/resources/src/mediawiki/bookletlayout/option4/website-ltr.svg b/resources/src/mediawiki/bookletlayout/option4/website-ltr.svg new file mode 100644 index 0000000000..ed07c6135c --- /dev/null +++ b/resources/src/mediawiki/bookletlayout/option4/website-ltr.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/src/mediawiki/bookletlayout/option4/website-rtl.png b/resources/src/mediawiki/bookletlayout/option4/website-rtl.png new file mode 100644 index 0000000000..83ba6d3f3e Binary files /dev/null and b/resources/src/mediawiki/bookletlayout/option4/website-rtl.png differ diff --git a/resources/src/mediawiki/bookletlayout/option4/website-rtl.svg b/resources/src/mediawiki/bookletlayout/option4/website-rtl.svg new file mode 100644 index 0000000000..dd8b0f0719 --- /dev/null +++ b/resources/src/mediawiki/bookletlayout/option4/website-rtl.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.css b/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.css deleted file mode 100644 index 4143520801..0000000000 --- a/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.css +++ /dev/null @@ -1,5 +0,0 @@ -.mw-foreignStructuredUpload-bookletLayout-license { - font-size: 90%; - line-height: 1.4em; - color: #555; -} diff --git a/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js b/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js index 859538615c..48060973bc 100644 --- a/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js +++ b/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js @@ -43,7 +43,7 @@ mw.ForeignStructuredUpload.BookletLayout.parent.prototype.initialize.call( this ) .done( function () { // Point the CategorySelector to the right wiki - this.upload.apiPromise.done( function ( api ) { + this.upload.getApi().done( function ( api ) { // If this is a ForeignApi, it will have a apiUrl, otherwise we don't need to do anything if ( api.apiUrl ) { // Can't reuse the same object, CategorySelector calls #abort on its mw.Api instance @@ -72,12 +72,45 @@ * @inheritdoc */ mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm = function () { + var + query = /[?&]uploadbucket=(\d)/.exec( location.search ), + isTestEnabled = !!mw.config.get( 'wgForeignUploadTestEnabled' ), + defaultBucket = mw.config.get( 'wgForeignUploadTestDefault' ) || 1, + userId = mw.config.get( 'wgUserId' ); + + if ( query && query[ 1 ] ) { + // Testing and debugging + this.shouldRecordBucket = false; + this.bucket = Number( query[ 1 ] ); + } else if ( !userId || !isTestEnabled ) { + // a) Anonymous user. This can actually happen, because our software sucks. + // b) Test is not enabled on this wiki. + // In either case, display the old interface and don't record bucket on uploads. + this.shouldRecordBucket = false; + this.bucket = defaultBucket; + } else { + // Regular logged in user on a wiki where the test is running + this.shouldRecordBucket = true; + this.bucket = ( userId % 4 ) + 1; // 1, 2, 3, 4 + } + + return this[ 'renderUploadForm' + this.bucket ](); + }; + + /** + * Test option 1, the original one. See T120867. + */ + mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm1 = function () { var fieldset, $ownWorkMessage, $notOwnWorkMessage, + onUploadFormChange, ownWorkMessage, notOwnWorkMessage, notOwnWorkLocal, validTargets = mw.config.get( 'wgForeignUploadTargets' ), target = this.target || validTargets[ 0 ] || 'local', layout = this; + // Temporary override to make my life easier during A/B test + target = 'shared'; + // foreign-structured-upload-form-label-own-work-message-local // foreign-structured-upload-form-label-own-work-message-shared ownWorkMessage = mw.message( 'foreign-structured-upload-form-label-own-work-message-' + target ); @@ -104,7 +137,13 @@ $( '

' ).html( notOwnWorkMessage.parse() ), $( '

' ).html( notOwnWorkLocal.parse() ) ); - $ownWorkMessage.add( $notOwnWorkMessage ).find( 'a' ).attr( 'target', '_blank' ); + $ownWorkMessage.add( $notOwnWorkMessage ).find( 'a' ) + .attr( 'target', '_blank' ) + .on( 'click', function ( e ) { + // Some stupid code is trying to prevent default on all clicks, which causes the links to + // not be openable, don't let it + e.stopPropagation(); + } ); this.selectFileWidget = new OO.ui.SelectFileWidget(); this.messageLabel = new OO.ui.LabelWidget( { @@ -133,23 +172,299 @@ ] ); this.uploadForm = new OO.ui.FormLayout( { items: [ fieldset ] } ); + onUploadFormChange = function () { + var file = this.selectFileWidget.getValue(), + ownWork = this.ownWorkCheckbox.isSelected(), + valid = !!file && ownWork; + this.emit( 'uploadValid', valid ); + }; + // Validation - this.selectFileWidget.on( 'change', this.onUploadFormChange.bind( this ) ); - this.ownWorkCheckbox.on( 'change', this.onUploadFormChange.bind( this ) ); + this.selectFileWidget.on( 'change', onUploadFormChange.bind( this ) ); + this.ownWorkCheckbox.on( 'change', onUploadFormChange.bind( this ) ); + + this.selectFileWidget.on( 'change', function () { + var file = layout.getFile(); + + // Set the date to lastModified once we have the file + if ( layout.getDateFromLastModified( file ) !== undefined ) { + layout.dateWidget.setValue( layout.getDateFromLastModified( file ) ); + } + + // Check if we have EXIF data and set to that where available + layout.getDateFromExif( file ).done( function ( date ) { + layout.dateWidget.setValue( date ); + } ); + } ); return this.uploadForm; }; /** - * @inheritdoc + * Test option 2, idea A from T121021. See T120867. + */ + mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm2 = function () { + var fieldset, checkboxes, fields, onUploadFormChange; + + this.selectFileWidget = new OO.ui.SelectFileWidget(); + this.licenseCheckboxes = checkboxes = [ + new OO.ui.CheckboxInputWidget(), + new OO.ui.CheckboxInputWidget(), + new OO.ui.CheckboxInputWidget(), + new OO.ui.CheckboxInputWidget() + ]; + + fields = [ + new OO.ui.FieldLayout( this.selectFileWidget, { + align: 'top', + label: mw.msg( 'upload-form-label-select-file' ) + } ), + new OO.ui.FieldLayout( new OO.ui.LabelWidget( { + label: mw.message( 'foreign-structured-upload-form-2-label-intro' ).parseDom() + } ), { + align: 'top' + } ), + new OO.ui.FieldLayout( checkboxes[ 0 ], { + align: 'inline', + classes: [ + 'mw-foreignStructuredUpload-bookletLayout-withicon', + 'mw-foreignStructuredUpload-bookletLayout-ownwork' + ], + label: mw.message( 'foreign-structured-upload-form-2-label-ownwork' ).parseDom() + } ), + new OO.ui.FieldLayout( checkboxes[ 1 ], { + align: 'inline', + classes: [ + 'mw-foreignStructuredUpload-bookletLayout-withicon', + 'mw-foreignStructuredUpload-bookletLayout-noderiv' + ], + label: mw.message( 'foreign-structured-upload-form-2-label-noderiv' ).parseDom() + } ), + new OO.ui.FieldLayout( checkboxes[ 2 ], { + align: 'inline', + classes: [ + 'mw-foreignStructuredUpload-bookletLayout-withicon', + 'mw-foreignStructuredUpload-bookletLayout-useful' + ], + label: mw.message( 'foreign-structured-upload-form-2-label-useful' ).parseDom() + } ), + new OO.ui.FieldLayout( checkboxes[ 3 ], { + align: 'inline', + classes: [ + 'mw-foreignStructuredUpload-bookletLayout-withicon', + 'mw-foreignStructuredUpload-bookletLayout-ccbysa' + ], + label: mw.message( 'foreign-structured-upload-form-2-label-ccbysa' ).parseDom() + } ), + new OO.ui.FieldLayout( new OO.ui.LabelWidget( { + label: $() + .add( $( '

' ).msg( 'foreign-structured-upload-form-2-label-alternative' ) ) + .add( $( '

' ).msg( 'foreign-structured-upload-form-2-label-termsofuse' ) + .addClass( 'mw-foreignStructuredUpload-bookletLayout-license' ) ) + } ), { + align: 'top' + } ) + ]; + + fieldset = new OO.ui.FieldsetLayout( { items: fields } ); + this.uploadForm = new OO.ui.FormLayout( { items: [ fieldset ] } ); + + this.uploadForm.$element.find( 'a' ) + .attr( 'target', '_blank' ) + .on( 'click', function ( e ) { + // Some stupid code is trying to prevent default on all clicks, which causes the links to + // not be openable, don't let it + e.stopPropagation(); + } ); + + onUploadFormChange = function () { + var file = this.selectFileWidget.getValue(), + checks = checkboxes.every( function ( checkbox ) { + return checkbox.isSelected(); + } ), + valid = !!file && checks; + this.emit( 'uploadValid', valid ); + }; + + // Validation + this.selectFileWidget.on( 'change', onUploadFormChange.bind( this ) ); + checkboxes[ 0 ].on( 'change', onUploadFormChange.bind( this ) ); + checkboxes[ 1 ].on( 'change', onUploadFormChange.bind( this ) ); + checkboxes[ 2 ].on( 'change', onUploadFormChange.bind( this ) ); + checkboxes[ 3 ].on( 'change', onUploadFormChange.bind( this ) ); + + return this.uploadForm; + }; + + /** + * Test option 3, idea D from T121021. See T120867. + */ + mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm3 = function () { + var ownWorkCheckbox, fieldset, yesMsg, noMsg, selects, selectFields, + alternativeField, fields, onUploadFormChange; + + this.selectFileWidget = new OO.ui.SelectFileWidget(); + this.ownWorkCheckbox = ownWorkCheckbox = new OO.ui.CheckboxInputWidget(); + + yesMsg = mw.message( 'foreign-structured-upload-form-3-label-yes' ).text(); + noMsg = mw.message( 'foreign-structured-upload-form-3-label-no' ).text(); + selects = [ + new OO.ui.RadioSelectWidget( { + items: [ + new OO.ui.RadioOptionWidget( { data: false, label: yesMsg } ), + new OO.ui.RadioOptionWidget( { data: true, label: noMsg } ) + ] + } ), + new OO.ui.RadioSelectWidget( { + items: [ + new OO.ui.RadioOptionWidget( { data: true, label: yesMsg } ), + new OO.ui.RadioOptionWidget( { data: false, label: noMsg } ) + ] + } ), + new OO.ui.RadioSelectWidget( { + items: [ + new OO.ui.RadioOptionWidget( { data: false, label: yesMsg } ), + new OO.ui.RadioOptionWidget( { data: true, label: noMsg } ) + ] + } ) + ]; + + this.licenseSelectFields = selectFields = [ + new OO.ui.FieldLayout( selects[ 0 ], { + align: 'top', + classes: [ 'mw-foreignStructuredUpload-bookletLayout-question' ], + label: mw.message( 'foreign-structured-upload-form-3-label-question-website' ).parseDom() + } ), + new OO.ui.FieldLayout( selects[ 1 ], { + align: 'top', + classes: [ 'mw-foreignStructuredUpload-bookletLayout-question' ], + label: mw.message( 'foreign-structured-upload-form-3-label-question-ownwork' ).parseDom() + } ).toggle( false ), + new OO.ui.FieldLayout( selects[ 2 ], { + align: 'top', + classes: [ 'mw-foreignStructuredUpload-bookletLayout-question' ], + label: mw.message( 'foreign-structured-upload-form-3-label-question-noderiv' ).parseDom() + } ).toggle( false ) + ]; + + alternativeField = new OO.ui.FieldLayout( new OO.ui.LabelWidget( { + label: mw.message( 'foreign-structured-upload-form-3-label-alternative' ).parseDom() + } ), { + align: 'top' + } ).toggle( false ); + + // Choosing the right answer to each question shows the next question. + // Switching to wrong answer hides all subsequent questions. + selects.forEach( function ( select, i ) { + select.on( 'choose', function ( selectedOption ) { + var isRightAnswer = !!selectedOption.getData(); + alternativeField.toggle( !isRightAnswer ); + if ( i + 1 === selectFields.length ) { + // Last question + return; + } + if ( isRightAnswer ) { + selectFields[ i + 1 ].toggle( true ); + } else { + selectFields.slice( i + 1 ).forEach( function ( field ) { + field.fieldWidget.selectItem( null ); + field.toggle( false ); + } ); + } + } ); + } ); + + fields = [ + new OO.ui.FieldLayout( this.selectFileWidget, { + align: 'top', + label: mw.msg( 'upload-form-label-select-file' ) + } ), + selectFields[ 0 ], + selectFields[ 1 ], + selectFields[ 2 ], + alternativeField, + new OO.ui.FieldLayout( ownWorkCheckbox, { + classes: [ 'mw-foreignStructuredUpload-bookletLayout-checkbox' ], + align: 'inline', + label: mw.message( 'foreign-structured-upload-form-label-own-work-message-shared' ).parseDom() + } ) + ]; + + // Must be done late, after it's been associated with the FieldLayout + ownWorkCheckbox.setDisabled( true ); + + fieldset = new OO.ui.FieldsetLayout( { items: fields } ); + this.uploadForm = new OO.ui.FormLayout( { items: [ fieldset ] } ); + + this.uploadForm.$element.find( 'a' ) + .attr( 'target', '_blank' ) + .on( 'click', function ( e ) { + // Some stupid code is trying to prevent default on all clicks, which causes the links to + // not be openable, don't let it + e.stopPropagation(); + } ); + + onUploadFormChange = function () { + var file = this.selectFileWidget.getValue(), + checkbox = ownWorkCheckbox.isSelected(), + rightAnswers = selects.every( function ( select ) { + return select.getSelectedItem() && !!select.getSelectedItem().getData(); + } ), + valid = !!file && checkbox && rightAnswers; + ownWorkCheckbox.setDisabled( !rightAnswers ); + if ( !rightAnswers ) { + ownWorkCheckbox.setSelected( false ); + } + this.emit( 'uploadValid', valid ); + }; + + // Validation + this.selectFileWidget.on( 'change', onUploadFormChange.bind( this ) ); + this.ownWorkCheckbox.on( 'change', onUploadFormChange.bind( this ) ); + selects[ 0 ].on( 'choose', onUploadFormChange.bind( this ) ); + selects[ 1 ].on( 'choose', onUploadFormChange.bind( this ) ); + selects[ 2 ].on( 'choose', onUploadFormChange.bind( this ) ); + + return this.uploadForm; + }; + + /** + * Test option 4, idea E from T121021. See T120867. */ - mw.ForeignStructuredUpload.BookletLayout.prototype.onUploadFormChange = function () { - var file = this.selectFileWidget.getValue(), - ownWork = this.ownWorkCheckbox.isSelected(), - valid = !!file && ownWork; - this.emit( 'uploadValid', valid ); + mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm4 = function () { + var fieldset, $guide; + this.renderUploadForm1(); + fieldset = this.uploadForm.getItems()[ 0 ]; + + $guide = mw.template.get( 'mediawiki.ForeignStructuredUpload.BookletLayout', 'guide.html' ).render(); + $guide.find( '.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-good span' ) + .msg( 'foreign-structured-upload-form-4-label-good' ); + $guide.find( '.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-bad span' ) + .msg( 'foreign-structured-upload-form-4-label-bad' ); + + // Note the index, we insert after the SelectFileWidget field + fieldset.addItems( [ + new OO.ui.FieldLayout( new OO.ui.Widget( { + $content: $guide + } ), { + align: 'top' + } ) + ], 1 ); + + // Hook for custom styles + fieldset.getItems()[ 2 ].$element.addClass( 'mw-foreignStructuredUpload-bookletLayout-guide-checkbox' ); + + // Streamline: remove mention of local Special:Upload + fieldset.getItems()[ 3 ].$element.find( 'p' ).last().remove(); + + return this.uploadForm; }; + /** + * @inheritdoc + */ + mw.ForeignStructuredUpload.BookletLayout.prototype.onUploadFormChange = function () {}; + /** * @inheritdoc */ @@ -162,20 +477,20 @@ } ); this.descriptionWidget = new OO.ui.TextInputWidget( { required: true, - validate: /.+/, + validate: /\S+/, multiline: true, autosize: true } ); - this.dateWidget = new mw.widgets.DateInputWidget( { - $overlay: this.$overlay, - required: true, - mustBeBefore: moment().add( 1, 'day' ).locale( 'en' ).format( 'YYYY-MM-DD' ) // Tomorrow - } ); this.categoriesWidget = new mw.widgets.CategorySelector( { // Can't be done here because we don't know the target wiki yet... done in #initialize. // api: new mw.ForeignApi( ... ), $overlay: this.$overlay } ); + this.dateWidget = new mw.widgets.DateInputWidget( { + $overlay: this.$overlay, + required: true, + mustBeBefore: moment().add( 1, 'day' ).locale( 'en' ).format( 'YYYY-MM-DD' ) // Tomorrow + } ); fieldset = new OO.ui.FieldsetLayout( { label: mw.msg( 'upload-form-label-infoform-title' ) @@ -183,11 +498,13 @@ fieldset.addItems( [ new OO.ui.FieldLayout( this.filenameWidget, { label: mw.msg( 'upload-form-label-infoform-name' ), - align: 'top' + align: 'top', + help: mw.msg( 'upload-form-label-infoform-name-tooltip' ) } ), new OO.ui.FieldLayout( this.descriptionWidget, { label: mw.msg( 'upload-form-label-infoform-description' ), - align: 'top' + align: 'top', + help: mw.msg( 'upload-form-label-infoform-description-tooltip' ) } ), new OO.ui.FieldLayout( this.categoriesWidget, { label: mw.msg( 'foreign-structured-upload-form-label-infoform-categories' ), @@ -239,6 +556,71 @@ return this.upload.getText(); }; + /** + * Get original date from EXIF data + * + * @param {Object} file + * @return {jQuery.Promise} Promise resolved with the EXIF date + */ + mw.ForeignStructuredUpload.BookletLayout.prototype.getDateFromExif = function ( file ) { + var fileReader, + deferred = $.Deferred(); + + if ( file && file.type === 'image/jpeg' ) { + fileReader = new FileReader(); + fileReader.onload = function () { + var fileStr, arr, i, metadata; + + if ( typeof fileReader.result === 'string' ) { + fileStr = fileReader.result; + } else { + // Array buffer; convert to binary string for the library. + arr = new Uint8Array( fileReader.result ); + fileStr = ''; + for ( i = 0; i < arr.byteLength; i++ ) { + fileStr += String.fromCharCode( arr[ i ] ); + } + } + + try { + metadata = mw.libs.jpegmeta( this.result, file.name ); + } catch ( e ) { + metadata = null; + } + + if ( metadata !== null && metadata.exif !== undefined && metadata.exif.DateTimeOriginal ) { + deferred.resolve( moment( metadata.exif.DateTimeOriginal, 'YYYY:MM:DD' ).format( 'YYYY-MM-DD' ) ); + } else { + deferred.reject(); + } + }; + + if ( 'readAsBinaryString' in fileReader ) { + fileReader.readAsBinaryString( file ); + } else if ( 'readAsArrayBuffer' in fileReader ) { + fileReader.readAsArrayBuffer( file ); + } else { + // We should never get here + deferred.reject(); + throw new Error( 'Cannot read thumbnail as binary string or array buffer.' ); + } + } + + return deferred.promise(); + }; + + /** + * Get last modified date from file + * + * @param {Object} file + * @return {Object} Last modified date from file + */ + mw.ForeignStructuredUpload.BookletLayout.prototype.getDateFromLastModified = function ( file ) { + if ( file && file.lastModified ) { + return moment( file.lastModified ).format( 'YYYY-MM-DD' ); + } + }; + /* Setters */ /** @@ -247,7 +629,23 @@ mw.ForeignStructuredUpload.BookletLayout.prototype.clear = function () { mw.ForeignStructuredUpload.BookletLayout.parent.prototype.clear.call( this ); - this.ownWorkCheckbox.setSelected( false ); + if ( this.ownWorkCheckbox ) { + this.ownWorkCheckbox.setSelected( false ); + } + if ( this.licenseCheckboxes ) { + this.licenseCheckboxes.forEach( function ( checkbox ) { + checkbox.setSelected( false ); + } ); + } + if ( this.licenseSelectFields ) { + this.licenseSelectFields.forEach( function ( field, i ) { + field.fieldWidget.selectItem( null ); + if ( i !== 0 ) { + field.toggle( false ); + } + } ); + } + this.categoriesWidget.setItemsFromData( [] ); this.dateWidget.setValue( '' ).setValidityFlag( true ); }; diff --git a/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.less b/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.less new file mode 100644 index 0000000000..fa15a2ecae --- /dev/null +++ b/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.less @@ -0,0 +1,136 @@ +@import "mediawiki.mixins"; + +/* All */ + +.mw-foreignStructuredUpload-bookletLayout-license { + font-size: 90%; + line-height: 1.4em; + color: #555; +} + +/* Option 2 */ + +.mw-foreignStructuredUpload-bookletLayout-withicon.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline { + background-repeat: no-repeat; + background-size: 3.5em; + min-height: 4em; + margin-bottom: 0.25em; + display: table; + vertical-align: middle; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 100%; +} + +.mw-foreignStructuredUpload-bookletLayout-withicon.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body { + /* Together with 'display: table' above, and FieldLayout styles, this aligns the text */ + /* vertically to the middle. Don't try this at home, kids. */ + display: table-row; +} + +.mw-foreignStructuredUpload-bookletLayout-ownwork { + .background-image-svg('bookletlayout/option2/ownwork.svg', 'bookletlayout/option2/ownwork.png'); + background-position: right center; + padding-right: 4.5em; +} + +.mw-foreignStructuredUpload-bookletLayout-noderiv { + .background-image-svg('bookletlayout/option2/noderiv.svg', 'bookletlayout/option2/noderiv.png'); + background-position: right center; + padding-right: 4.5em; +} + +.mw-foreignStructuredUpload-bookletLayout-useful { + .background-image-svg('bookletlayout/option2/useful.svg', 'bookletlayout/option2/useful.png'); + background-position: right center; + padding-right: 4.5em; +} + +.mw-foreignStructuredUpload-bookletLayout-ccbysa { + .background-image-svg('bookletlayout/option2/ccbysa.svg', 'bookletlayout/option2/ccbysa.png'); + background-position: right center; + padding-right: 4.5em; +} + +/* Option 3 */ + +.mw-foreignStructuredUpload-bookletLayout-question .oo-ui-radioOptionWidget { + display: inline-block; + margin-right: 2em; +} + +.mw-foreignStructuredUpload-bookletLayout-checkbox.oo-ui-fieldLayout-disabled > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label { + /* No unreadable greys, please. This is the lightest WCAG AA compliant grey. */ + color: #707070; +} + +/* Option 4 */ + +.mw-foreignStructuredUpload-bookletLayout-guide { + position: relative; + height: 315px; +} + +.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-good, +.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-bad { + display: table; + width: 150px; + height: 140px; + position: absolute; +} + +.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-good span, +.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-bad span { + display: table-cell; + vertical-align: middle; +} + +.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-good { + top: 0; + left: 0; +} + +.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-bad { + bottom: 0; + right: 0; +} + +.mw-foreignStructuredUpload-bookletLayout-guide-image { + position: absolute; + width: 200px; + height: 122px; + background-color: #fff; + background-size: 200px; + background-repeat: no-repeat; + border: 1px solid #e5e5e5; +} + +.mw-foreignStructuredUpload-bookletLayout-guide-image-camera { + .background-image-svg('bookletlayout/option4/camera.svg','bookletlayout/option4/camera.png'); + top: 0; + right: 80px; +} + +.mw-foreignStructuredUpload-bookletLayout-guide-image-graphics { + .background-image-svg('bookletlayout/option4/graphics.svg','bookletlayout/option4/graphics.png'); + top: 50px; + right: 0; +} + +.mw-foreignStructuredUpload-bookletLayout-guide-image-website { + .background-image-svg('bookletlayout/option4/website-ltr.svg','bookletlayout/option4/website-ltr.png'); + left: 0; + bottom: 50px; +} + +.mw-foreignStructuredUpload-bookletLayout-guide-image-search { + .background-image-svg('bookletlayout/option4/search-ltr.svg','bookletlayout/option4/search-ltr.png'); + left: 80px; + bottom: 0; +} + +.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline.mw-foreignStructuredUpload-bookletLayout-guide-checkbox { + /* We're really tight on vertical space. */ + margin-bottom: 0; +} diff --git a/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.js b/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.js index 0117ceace2..c03c0d1939 100644 --- a/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.js +++ b/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.js @@ -156,6 +156,10 @@ ForeignStructuredUpload.prototype.getCategories = function () { var i, cat, categoryLinks = []; + if ( this.categories.length === 0 ) { + return '{{subst:unc}}'; + } + for ( i = 0; i < this.categories.length; i++ ) { cat = this.categories[ i ]; categoryLinks.push( '[[Category:' + cat + ']]' ); @@ -193,9 +197,18 @@ * @return {string} */ ForeignStructuredUpload.prototype.getUser = function () { - var username = mw.config.get( 'wgUserName' ); - // Do not localise 'User:', we don't know the language of target wiki - return '[[User:' + username + '|' + username + ']]'; + var username, namespace; + // Do not localise, we don't know the language of target wiki + namespace = 'User'; + username = mw.config.get( 'wgUserName' ); + if ( !username ) { + // The user is not logged in locally. However, they might be logged in on the foreign wiki. + // We should record their username there. (If they're not logged in there either, this will + // record the IP address.) It's also possible that the user opened this dialog, got an error + // about not being logged in, logged in in another browser tab, then continued uploading. + username = '{{subst:REVISIONUSER}}'; + } + return '[[' + namespace + ':' + username + '|' + username + ']]'; }; mw.ForeignStructuredUpload = ForeignStructuredUpload; diff --git a/resources/src/mediawiki/mediawiki.ForeignUpload.js b/resources/src/mediawiki/mediawiki.ForeignUpload.js index 61fb59f609..aa08b6cd7c 100644 --- a/resources/src/mediawiki/mediawiki.ForeignUpload.js +++ b/resources/src/mediawiki/mediawiki.ForeignUpload.js @@ -109,6 +109,13 @@ * or to local uploads if no foreign target is configured. */ + /** + * @inheritdoc + */ + ForeignUpload.prototype.getApi = function () { + return this.apiPromise; + }; + /** * Override from mw.Upload to make sure the API info is found and allowed */ diff --git a/resources/src/mediawiki/mediawiki.Title.js b/resources/src/mediawiki/mediawiki.Title.js index 47250eea0a..033636cfea 100644 --- a/resources/src/mediawiki/mediawiki.Title.js +++ b/resources/src/mediawiki/mediawiki.Title.js @@ -906,7 +906,12 @@ * @return {string} */ getUrl: function ( params ) { - return mw.util.getUrl( this.toString(), params ); + var fragment = this.getFragment(); + if ( fragment ) { + return mw.util.getUrl( this.toString() + '#' + this.getFragment(), params ); + } else { + return mw.util.getUrl( this.toString(), params ); + } }, /** diff --git a/resources/src/mediawiki/mediawiki.Upload.BookletLayout.js b/resources/src/mediawiki/mediawiki.Upload.BookletLayout.js index 740144502c..1cd9101511 100644 --- a/resources/src/mediawiki/mediawiki.Upload.BookletLayout.js +++ b/resources/src/mediawiki/mediawiki.Upload.BookletLayout.js @@ -52,7 +52,7 @@ * {@link #createUpload createUpload} method to * return the new model. The {@link #saveFile saveFile}, and * the {@link #uploadFile uploadFile} methods need to be - * overriden to use the new model and data returned from the forms. + * overridden to use the new model and data returned from the forms. * * @class * @extends OO.ui.BookletLayout @@ -151,10 +151,27 @@ * @return {jQuery.Promise} Promise resolved when everything is initialized */ mw.Upload.BookletLayout.prototype.initialize = function () { + var + booklet = this, + deferred = $.Deferred(); + this.clear(); this.upload = this.createUpload(); this.setPage( 'upload' ); - return $.Deferred().resolve().promise(); + + this.upload.getApi().done( function ( api ) { + // If the user can't upload anything, don't give them the option to. + api.getUserInfo().done( function ( userInfo ) { + if ( userInfo.rights.indexOf( 'upload' ) === -1 ) { + // TODO Use a better error message when not all logged-in users can upload + booklet.getPage( 'upload' ).$element.msg( 'api-error-mustbeloggedin' ); + } + } ).always( function () { + deferred.resolve(); + } ); + } ); + + return deferred.promise(); }; /** @@ -183,12 +200,17 @@ layout = this, file = this.getFile(); - this.filenameWidget.setValue( file.name ); + this.setFilename( file.name ); + this.setPage( 'info' ); + if ( this.shouldRecordBucket ) { + this.upload.bucket = this.bucket; + } + this.upload.setFile( file ); - // Explicitly set the filename so that the old filename isn't used in case of retry - this.upload.setFilenameFromFile(); + // The original file name might contain invalid characters, so use our sanitized one + this.upload.setFilename( this.getFilename() ); this.uploadPromise = this.upload.uploadToStash(); this.uploadPromise.then( function () { @@ -330,7 +352,7 @@ } else if ( warnings.badfilename !== undefined ) { // Change the name if the current name isn't acceptable // TODO This might not really be the best place to do this - this.filenameWidget.setValue( warnings.badfilename ); + this.setFilename( warnings.badfilename ); return new OO.ui.Error( $( '

' ).msg( 'badfilename', warnings.badfilename ) ); @@ -399,7 +421,7 @@ this.descriptionWidget = new OO.ui.TextInputWidget( { indicator: 'required', required: true, - validate: /.+/, + validate: /\S+/, multiline: true, autosize: true } ); @@ -410,11 +432,13 @@ fieldset.addItems( [ new OO.ui.FieldLayout( this.filenameWidget, { label: mw.msg( 'upload-form-label-infoform-name' ), - align: 'top' + align: 'top', + help: mw.msg( 'upload-form-label-infoform-name-tooltip' ) } ), new OO.ui.FieldLayout( this.descriptionWidget, { label: mw.msg( 'upload-form-label-infoform-description' ), - align: 'top' + align: 'top', + help: mw.msg( 'upload-form-label-infoform-description-tooltip' ) } ) ] ); this.infoForm = new OO.ui.FormLayout( { items: [ fieldset ] } ); @@ -489,7 +513,30 @@ * @return {string} */ mw.Upload.BookletLayout.prototype.getFilename = function () { - return this.filenameWidget.getValue(); + var filename = this.filenameWidget.getValue(); + if ( this.filenameExtension ) { + filename += '.' + this.filenameExtension; + } + return filename; + }; + + /** + * Prefills the {@link #infoForm information form} with the given filename. + * + * @protected + * @param {string} filename + */ + mw.Upload.BookletLayout.prototype.setFilename = function ( filename ) { + var title = mw.Title.newFromFileName( filename ); + + if ( title ) { + this.filenameWidget.setValue( title.getNameText() ); + this.filenameExtension = mw.Title.normalizeExtension( title.getExtension() ); + } else { + // Seems to happen for files with no extension, which should fail some checks anyway... + this.filenameWidget.setValue( filename ); + this.filenameExtension = null; + } }; /** diff --git a/resources/src/mediawiki/mediawiki.Upload.Dialog.js b/resources/src/mediawiki/mediawiki.Upload.Dialog.js index 5f2569c404..e8a85f1b51 100644 --- a/resources/src/mediawiki/mediawiki.Upload.Dialog.js +++ b/resources/src/mediawiki/mediawiki.Upload.Dialog.js @@ -128,7 +128,7 @@ * @inheritdoc */ mw.Upload.Dialog.prototype.getBodyHeight = function () { - return 300; + return 600; }; /** diff --git a/resources/src/mediawiki/mediawiki.Upload.js b/resources/src/mediawiki/mediawiki.Upload.js index 4f8789de7a..8a74ffc6ab 100644 --- a/resources/src/mediawiki/mediawiki.Upload.js +++ b/resources/src/mediawiki/mediawiki.Upload.js @@ -62,6 +62,17 @@ UP = Upload.prototype; + /** + * Get the mw.Api instance used by this Upload object. + * + * @return {jQuery.Promise} + * @return {Function} return.done + * @return {mw.Api} return.done.api + */ + UP.getApi = function () { + return $.Deferred().resolve( this.api ).promise(); + }; + /** * Set the text of the file page, to be created on file upload. * @@ -321,6 +332,7 @@ upload.setState( Upload.State.UPLOADING ); return finishStash( { + bucket: upload.bucket, // Automatically ignored if undefined watchlist: ( upload.getWatchlist() ) ? 1 : undefined, comment: upload.getComment(), filename: upload.getFilename(), diff --git a/resources/src/mediawiki/mediawiki.feedlink.css b/resources/src/mediawiki/mediawiki.feedlink.css index a07a4031af..4a2a367d76 100644 --- a/resources/src/mediawiki/mediawiki.feedlink.css +++ b/resources/src/mediawiki/mediawiki.feedlink.css @@ -6,8 +6,6 @@ a.feedlink { * http://pauginer.tumblr.com/post/36614680636/invisible-gradient-technique */ background-image: url(images/feed-icon.png); /* @embed */ - background-image: -webkit-linear-gradient(transparent, transparent), url(images/feed-icon.svg); - /* @embed */ background-image: linear-gradient(transparent, transparent), url(images/feed-icon.svg); background-position: center left; background-repeat: no-repeat; diff --git a/resources/src/mediawiki/mediawiki.htmlform.css b/resources/src/mediawiki/mediawiki.htmlform.css index 765e92fabe..c3341bb15a 100644 --- a/resources/src/mediawiki/mediawiki.htmlform.css +++ b/resources/src/mediawiki/mediawiki.htmlform.css @@ -31,8 +31,6 @@ tr.mw-htmlform-vertical-label td.mw-label { * http://pauginer.tumblr.com/post/36614680636/invisible-gradient-technique */ background-image: url(images/question.png); /* @embed */ - background-image: -webkit-linear-gradient(transparent, transparent), url(images/question.svg); - /* @embed */ background-image: linear-gradient(transparent, transparent), url(images/question.svg); background-repeat: no-repeat; background-size: 13px 13px; diff --git a/resources/src/mediawiki/mediawiki.inspect.js b/resources/src/mediawiki/mediawiki.inspect.js index 514a3dd15e..d4449231f8 100644 --- a/resources/src/mediawiki/mediawiki.inspect.js +++ b/resources/src/mediawiki/mediawiki.inspect.js @@ -20,7 +20,7 @@ function humanSize( bytes ) { if ( !$.isNumeric( bytes ) || bytes === 0 ) { return bytes; } var i = 0, - units = [ '', ' kB', ' MB', ' GB', ' TB', ' PB' ]; + units = [ '', ' KiB', ' MiB', ' GiB', ' TiB', ' PiB' ]; for ( ; bytes >= 1024; bytes /= 1024 ) { i++; } // Maintain one decimal for kB and above, but don't diff --git a/resources/src/mediawiki/mediawiki.jqueryMsg.js b/resources/src/mediawiki/mediawiki.jqueryMsg.js index 620dd5e5af..e905f69b71 100644 --- a/resources/src/mediawiki/mediawiki.jqueryMsg.js +++ b/resources/src/mediawiki/mediawiki.jqueryMsg.js @@ -1246,6 +1246,48 @@ number = nodes[ 0 ]; return this.language.convertNumber( number, isInteger ); + }, + + /** + * Lowercase text + * + * @param {Array} nodes List of nodes + * @return {string} The given text, all in lowercase + */ + lc: function ( nodes ) { + return textify( nodes[ 0 ] ).toLowerCase(); + }, + + /** + * Uppercase text + * + * @param {Array} nodes List of nodes + * @return {string} The given text, all in uppercase + */ + uc: function ( nodes ) { + return textify( nodes[ 0 ] ).toUpperCase(); + }, + + /** + * Lowercase first letter of input, leaving the rest unchanged + * + * @param {Array} nodes List of nodes + * @return {string} The given text, with the first character in lowercase + */ + lcfirst: function ( nodes ) { + var text = textify( nodes[ 0 ] ); + return text.charAt( 0 ).toLowerCase() + text.slice( 1 ); + }, + + /** + * Uppercase first letter of input, leaving the rest unchanged + * + * @param {Array} nodes List of nodes + * @return {string} The given text, with the first character in uppercase + */ + ucfirst: function ( nodes ) { + var text = textify( nodes[ 0 ] ); + return text.charAt( 0 ).toUpperCase() + text.slice( 1 ); } }; diff --git a/resources/src/mediawiki/mediawiki.js b/resources/src/mediawiki/mediawiki.js index 322c579428..c25e32767e 100644 --- a/resources/src/mediawiki/mediawiki.js +++ b/resources/src/mediawiki/mediawiki.js @@ -2446,9 +2446,19 @@ }; }() ), - // Skeleton user object. mediawiki.user.js extends this + // Skeleton user object, extended by the 'mediawiki.user' module. + /** + * @class mw.user + * @singleton + */ user: { + /** + * @property {mw.Map} + */ options: new Map(), + /** + * @property {mw.Map} + */ tokens: new Map() }, diff --git a/resources/src/mediawiki/mediawiki.searchSuggest.js b/resources/src/mediawiki/mediawiki.searchSuggest.js index c960d6570e..ada4924815 100644 --- a/resources/src/mediawiki/mediawiki.searchSuggest.js +++ b/resources/src/mediawiki/mediawiki.searchSuggest.js @@ -3,6 +3,7 @@ */ ( function ( mw, $ ) { mw.searchSuggest = { + // queries the wiki and calls response with the result request: function ( api, query, response, maxRows ) { return api.get( { action: 'opensearch', @@ -13,7 +14,9 @@ } ).done( function ( data ) { response( data[ 1 ] ); } ); - } + }, + // The name of the request api for event logging purposes + type: 'prefix' }; $( function () { @@ -98,9 +101,7 @@ mw.track( 'mediawiki.searchSuggest', { action: 'impression-results', numberOfResults: context.config.suggestions.length, - // FIXME: when other types of search become available change this value accordingly - // See the API call below (opensearch = prefix) - resultSetType: 'prefix' + resultSetType: mw.searchSuggest.type } ); } diff --git a/resources/src/mediawiki/mediawiki.template.js b/resources/src/mediawiki/mediawiki.template.js index 91f1aff7ce..5e0e343250 100644 --- a/resources/src/mediawiki/mediawiki.template.js +++ b/resources/src/mediawiki/mediawiki.template.js @@ -8,43 +8,47 @@ mw.template = { /** - * Register a new compiler and template. + * Register a new compiler. * - * @param {string} name of compiler. Should also match with any file extensions of templates that want to use it. - * @param {Function} compiler which must implement a compile function + * A compiler is any object that implements a compile() method. The compile() method must + * return a Template interface with a method render() that returns HTML. + * + * The compiler name must correspond with the name suffix of templates that use this compiler. + * + * @param {string} name Compiler name + * @param {Object} compiler */ registerCompiler: function ( name, compiler ) { if ( !compiler.compile ) { - throw new Error( 'Compiler must implement compile method.' ); + throw new Error( 'Compiler must implement a compile method' ); } compilers[ name ] = compiler; }, /** - * Get the name of the compiler associated with a template based on its name. + * Get the name of the associated compiler based on a template name. * - * @param {string} templateName Name of template (including file suffix) - * @return {string} Name of compiler + * @param {string} templateName Name of a template (including suffix) + * @return {string} Name of a compiler */ getCompilerName: function ( templateName ) { - var templateParts = templateName.split( '.' ); - - if ( templateParts.length < 2 ) { - throw new Error( 'Unable to identify compiler. Template name must have a suffix.' ); + var nameParts = templateName.split( '.' ); + if ( nameParts.length < 2 ) { + throw new Error( 'Template name must have a suffix' ); } - return templateParts[ templateParts.length - 1 ]; + return nameParts[ nameParts.length - 1 ]; }, /** - * Get the compiler for a given compiler name. + * Get a compiler via its name. * - * @param {string} compilerName Name of the compiler - * @return {Object} The compiler associated with that name + * @param {string} name Name of a compiler + * @return {Object} The compiler */ - getCompiler: function ( compilerName ) { - var compiler = compilers[ compilerName ]; + getCompiler: function ( name ) { + var compiler = compilers[ name ]; if ( !compiler ) { - throw new Error( 'Unknown compiler ' + compilerName ); + throw new Error( 'Unknown compiler ' + name ); } return compiler; }, @@ -52,57 +56,54 @@ /** * Register a template associated with a module. * - * Compiles the newly added template based on the suffix in its name. + * Precompiles the newly added template based on the suffix in its name. * - * @param {string} moduleName Name of ResourceLoader module to get the template from - * @param {string} templateName Name of template to add including file extension - * @param {string} templateBody Contents of a template (e.g. html markup) - * @return {Function} Compiled template + * @param {string} moduleName Name of the ResourceLoader module the template is associated with + * @param {string} templateName Name of the template (including suffix) + * @param {string} templateBody Contents of the template (e.g. html markup) + * @return {Object} Compiled template */ add: function ( moduleName, templateName, templateBody ) { - var compiledTemplate, - compilerName = this.getCompilerName( templateName ); - + // Precompile and add to cache + var compiled = this.compile( templateBody, this.getCompilerName( templateName ) ); if ( !compiledTemplates[ moduleName ] ) { compiledTemplates[ moduleName ] = {}; } + compiledTemplates[ moduleName ][ templateName ] = compiled; - compiledTemplate = this.compile( templateBody, compilerName ); - compiledTemplates[ moduleName ][ templateName ] = compiledTemplate; - return compiledTemplate; + return compiled; }, /** - * Retrieve a template by module and template name. + * Get a compiled template by module and template name. * * @param {string} moduleName Name of the module to retrieve the template from * @param {string} templateName Name of template to be retrieved * @return {Object} Compiled template */ get: function ( moduleName, templateName ) { - var moduleTemplates, compiledTemplate; + var moduleTemplates; - // Check if the template has already been compiled, compile it if not - if ( !compiledTemplates[ moduleName ] || !compiledTemplates[ moduleName ][ templateName ] ) { - moduleTemplates = mw.templates.get( moduleName ); - if ( !moduleTemplates || !moduleTemplates[ templateName ] ) { - throw new Error( 'Template ' + templateName + ' not found in module ' + moduleName ); - } + // Try cache first + if ( compiledTemplates[ moduleName ] && compiledTemplates[ moduleName ][ templateName ] ) { + return compiledTemplates[ moduleName ][ templateName ]; + } - // Add compiled version - compiledTemplate = this.add( moduleName, templateName, moduleTemplates[ templateName ] ); - } else { - compiledTemplate = compiledTemplates[ moduleName ][ templateName ]; + moduleTemplates = mw.templates.get( moduleName ); + if ( !moduleTemplates || !moduleTemplates[ templateName ] ) { + throw new Error( 'Template ' + templateName + ' not found in module ' + moduleName ); } - return compiledTemplate; + + // Compiled and add to cache + return this.add( moduleName, templateName, moduleTemplates[ templateName ] ); }, /** - * Wrap our template engine of choice. + * Compile a string of template markup with an engine of choice. * * @param {string} templateBody Template body * @param {string} compilerName The name of a registered compiler - * @return {Object} Template interface + * @return {Object} Compiled template */ compile: function ( templateBody, compilerName ) { return this.getCompiler( compilerName ).compile( templateBody ); diff --git a/resources/src/mediawiki/mediawiki.template.mustache.js b/resources/src/mediawiki/mediawiki.template.mustache.js index 624986a9bb..7f62256adf 100644 --- a/resources/src/mediawiki/mediawiki.template.mustache.js +++ b/resources/src/mediawiki/mediawiki.template.mustache.js @@ -4,8 +4,27 @@ mw.template.registerCompiler( 'mustache', { compile: function ( src ) { return { - render: function ( data ) { - return $( $.parseHTML( Mustache.render( src, data ) ) ); + /** + * @ignore + * @return {string} The raw source code of the template + */ + getSource: function () { + return src; + }, + /** + * @ignore + * @param {Object} data Data to render + * @param {Object} partialTemplates Map partial names to Mustache template objects + * returned by mw.template.get() + */ + render: function ( data, partialTemplates ) { + var partials = {}; + if ( partialTemplates ) { + $.each( partialTemplates, function ( name, template ) { + partials[ name ] = template.getSource(); + } ); + } + return $( $.parseHTML( Mustache.render( src, data, partials ) ) ); } }; } diff --git a/resources/src/mediawiki/mediawiki.user.js b/resources/src/mediawiki/mediawiki.user.js index b4baa66caa..d2f2abd60a 100644 --- a/resources/src/mediawiki/mediawiki.user.js +++ b/resources/src/mediawiki/mediawiki.user.js @@ -4,41 +4,20 @@ */ ( function ( mw, $ ) { var i, - deferreds = {}, + userInfoPromise, byteToHex = []; /** * Get the current user's groups or rights * * @private - * @param {string} info One of 'groups' or 'rights' * @return {jQuery.Promise} */ - function getUserInfo( info ) { - var api; - if ( !deferreds[ info ] ) { - - deferreds.rights = $.Deferred(); - deferreds.groups = $.Deferred(); - - api = new mw.Api(); - api.get( { - action: 'query', - meta: 'userinfo', - uiprop: 'rights|groups' - } ).always( function ( data ) { - var rights, groups; - if ( data.query && data.query.userinfo ) { - rights = data.query.userinfo.rights; - groups = data.query.userinfo.groups; - } - deferreds.rights.resolve( rights || [] ); - deferreds.groups.resolve( groups || [] ); - } ); - + function getUserInfo() { + if ( !userInfoPromise ) { + userInfoPromise = new mw.Api().getUserInfo(); } - - return deferreds[ info ].promise(); + return userInfoPromise; } // Map from numbers 0-255 to a hex string (with padding) @@ -262,7 +241,10 @@ * @return {jQuery.Promise} */ getGroups: function ( callback ) { - return getUserInfo( 'groups' ).done( callback ); + return getUserInfo().then( + function ( userInfo ) { return userInfo.groups; }, + function () { return []; } + ).done( callback ); }, /** @@ -272,7 +254,10 @@ * @return {jQuery.Promise} */ getRights: function ( callback ) { - return getUserInfo( 'rights' ).done( callback ); + return getUserInfo().then( + function ( userInfo ) { return userInfo.rights; }, + function () { return []; } + ).done( callback ); } } ); diff --git a/resources/src/mediawiki/mediawiki.util.js b/resources/src/mediawiki/mediawiki.util.js index 4cec813e5b..f9810f9aa8 100644 --- a/resources/src/mediawiki/mediawiki.util.js +++ b/resources/src/mediawiki/mediawiki.util.js @@ -58,6 +58,18 @@ .replace( /\)/g, '%29' ).replace( /\*/g, '%2A' ).replace( /~/g, '%7E' ); }, + /** + * Encode the string like Sanitizer::escapeId in PHP + * + * @param {string} str String to be encoded. + */ + escapeId: function ( str ) { + str = String( str ); + return util.rawurlencode( str.replace( / /g, '_' ) ) + .replace( /%3A/g, ':' ) + .replace( /%/g, '.' ); + }, + /** * Encode page titles for use in a URL * @@ -95,15 +107,33 @@ * @return {string} Url of the page with name of `str` */ getUrl: function ( str, params ) { - var url = mw.config.get( 'wgArticlePath' ).replace( - '$1', - util.wikiUrlencode( typeof str === 'string' ? str : mw.config.get( 'wgPageName' ) ) - ); + var titleFragmentStart, + url, + fragment = '', + pageName = typeof str === 'string' ? str : mw.config.get( 'wgPageName' ); + + // Find any fragment should one exist + if ( typeof str === 'string' ) { + titleFragmentStart = pageName.indexOf( '#' ); + if ( titleFragmentStart !== -1 ) { + fragment = pageName.slice( titleFragmentStart + 1 ); + // Exclude the fragment from the page name + pageName = pageName.slice( 0, titleFragmentStart ); + } + } + url = mw.config.get( 'wgArticlePath' ).replace( '$1', util.wikiUrlencode( pageName ) ); + + // Add query string if necessary if ( params && !$.isEmptyObject( params ) ) { url += ( url.indexOf( '?' ) !== -1 ? '&' : '?' ) + $.param( params ); } + // Append the encoded fragment + if ( fragment.length > 0 ) { + url += '#' + util.escapeId( fragment ); + } + return url; }, diff --git a/resources/src/mediawiki/page/patrol.ajax.js b/resources/src/mediawiki/page/patrol.ajax.js new file mode 100644 index 0000000000..f9b0d3564b --- /dev/null +++ b/resources/src/mediawiki/page/patrol.ajax.js @@ -0,0 +1,65 @@ +/*! + * Animate patrol links to use asynchronous API requests to + * patrol pages, rather than navigating to a different URI. + * + * @since 1.21 + * @author Marius Hoch + */ +( function ( mw, $ ) { + if ( !mw.user.tokens.exists( 'patrolToken' ) ) { + // Current user has no patrol right, or an old cached version of user.tokens + // that didn't have patrolToken yet. + return; + } + $( function () { + var $patrolLinks = $( '.patrollink a' ); + $patrolLinks.on( 'click', function ( e ) { + var $spinner, href, rcid, apiRequest; + + // Start preloading the notification module (normally loaded by mw.notify()) + mw.loader.load( 'mediawiki.notification' ); + + // Hide the link and create a spinner to show it inside the brackets. + $spinner = $.createSpinner( { + size: 'small', + type: 'inline' + } ); + $( this ).hide().after( $spinner ); + + href = $( this ).attr( 'href' ); + rcid = mw.util.getParamValue( 'rcid', href ); + apiRequest = new mw.Api(); + + apiRequest.postWithToken( 'patrol', { + action: 'patrol', + rcid: rcid + } ) + .done( function ( data ) { + // Remove all patrollinks from the page (including any spinners inside). + $patrolLinks.closest( '.patrollink' ).remove(); + if ( data.patrol !== undefined ) { + // Success + var title = new mw.Title( data.patrol.title ); + mw.notify( mw.msg( 'markedaspatrollednotify', title.toText() ) ); + } else { + // This should never happen as errors should trigger fail + mw.notify( mw.msg( 'markedaspatrollederrornotify' ), { type: 'error' } ); + } + } ) + .fail( function ( error ) { + $spinner.remove(); + // Restore the patrol link. This allows the user to try again + // (or open it in a new window, bypassing this ajax module). + $patrolLinks.show(); + if ( error === 'noautopatrol' ) { + // Can't patrol own + mw.notify( mw.msg( 'markedaspatrollederror-noautopatrol' ), { type: 'warn' } ); + } else { + mw.notify( mw.msg( 'markedaspatrollederrornotify' ), { type: 'error' } ); + } + } ); + + e.preventDefault(); + } ); + } ); +}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/page/patrol.js b/resources/src/mediawiki/page/patrol.js deleted file mode 100644 index f9b0d3564b..0000000000 --- a/resources/src/mediawiki/page/patrol.js +++ /dev/null @@ -1,65 +0,0 @@ -/*! - * Animate patrol links to use asynchronous API requests to - * patrol pages, rather than navigating to a different URI. - * - * @since 1.21 - * @author Marius Hoch - */ -( function ( mw, $ ) { - if ( !mw.user.tokens.exists( 'patrolToken' ) ) { - // Current user has no patrol right, or an old cached version of user.tokens - // that didn't have patrolToken yet. - return; - } - $( function () { - var $patrolLinks = $( '.patrollink a' ); - $patrolLinks.on( 'click', function ( e ) { - var $spinner, href, rcid, apiRequest; - - // Start preloading the notification module (normally loaded by mw.notify()) - mw.loader.load( 'mediawiki.notification' ); - - // Hide the link and create a spinner to show it inside the brackets. - $spinner = $.createSpinner( { - size: 'small', - type: 'inline' - } ); - $( this ).hide().after( $spinner ); - - href = $( this ).attr( 'href' ); - rcid = mw.util.getParamValue( 'rcid', href ); - apiRequest = new mw.Api(); - - apiRequest.postWithToken( 'patrol', { - action: 'patrol', - rcid: rcid - } ) - .done( function ( data ) { - // Remove all patrollinks from the page (including any spinners inside). - $patrolLinks.closest( '.patrollink' ).remove(); - if ( data.patrol !== undefined ) { - // Success - var title = new mw.Title( data.patrol.title ); - mw.notify( mw.msg( 'markedaspatrollednotify', title.toText() ) ); - } else { - // This should never happen as errors should trigger fail - mw.notify( mw.msg( 'markedaspatrollederrornotify' ), { type: 'error' } ); - } - } ) - .fail( function ( error ) { - $spinner.remove(); - // Restore the patrol link. This allows the user to try again - // (or open it in a new window, bypassing this ajax module). - $patrolLinks.show(); - if ( error === 'noautopatrol' ) { - // Can't patrol own - mw.notify( mw.msg( 'markedaspatrollederror-noautopatrol' ), { type: 'warn' } ); - } else { - mw.notify( mw.msg( 'markedaspatrollederrornotify' ), { type: 'error' } ); - } - } ); - - e.preventDefault(); - } ); - } ); -}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/page/ready.js b/resources/src/mediawiki/page/ready.js index 9505bdd1e0..4385a2e903 100644 --- a/resources/src/mediawiki/page/ready.js +++ b/resources/src/mediawiki/page/ready.js @@ -70,6 +70,25 @@ } ); } + $nodes = $( '.catlinks[data-mw="interface"]' ); + if ( $nodes.length ) { + /** + * Fired when categories are being added to the DOM + * + * It is encouraged to fire it before the main DOM is changed (when $content + * is still detached). However, this order is not defined either way, so you + * should only rely on $content itself. + * + * This includes the ready event on a page load (including post-edit loads) + * and when content has been previewed with LivePreview. + * + * @event wikipage_categories + * @member mw.hook + * @param {jQuery} $content The most appropriate element containing the content, + * such as .catlinks + */ + mw.hook( 'wikipage.categories' ).fire( $nodes ); + } } ); }( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/page/startup.js b/resources/src/mediawiki/page/startup.js index f2509e28fc..cd37e33080 100644 --- a/resources/src/mediawiki/page/startup.js +++ b/resources/src/mediawiki/page/startup.js @@ -14,7 +14,7 @@ * Fired when wiki content is being added to the DOM * * It is encouraged to fire it before the main DOM is changed (when $content - * is still detatched). However, this order is not defined either way, so you + * is still detached). However, this order is not defined either way, so you * should only rely on $content itself. * * This includes the ready event on a page load (including post-edit loads) @@ -28,7 +28,7 @@ */ mw.hook( 'wikipage.content' ).fire( $( '#mw-content-text' ) ); - var $diff = $( 'table.diff' ); + var $diff = $( 'table.diff[data-mw="interface"]' ); if ( $diff.length ) { /** * Fired when the diff is added to a page containing a diff diff --git a/resources/src/mediawiki/page/watch.js b/resources/src/mediawiki/page/watch.js index a3197da367..578c846cc5 100644 --- a/resources/src/mediawiki/page/watch.js +++ b/resources/src/mediawiki/page/watch.js @@ -105,11 +105,8 @@ }; $( function () { - var $links = $( '.mw-watchlink a, a.mw-watchlink, ' + - '#ca-watch a, #ca-unwatch a, #mw-unwatch-link1, ' + - '#mw-unwatch-link2, #mw-watch-link2, #mw-watch-link1' ); - - // Allowing people to add inline animated links is a little scary + var $links = $( '.mw-watchlink a, a.mw-watchlink' ); + // Restrict to core interfaces, ignore user-generated content $links = $links.filter( ':not( #bodyContent *, #content * )' ); $links.click( function ( e ) { diff --git a/tests/browser/environments.yml b/tests/browser/environments.yml index b2232e628f..35eb153f04 100644 --- a/tests/browser/environments.yml +++ b/tests/browser/environments.yml @@ -15,14 +15,12 @@ # bundle exec cucumber # mw-vagrant-host: &default + user_factory: true mediawiki_url: http://127.0.0.1:8080/wiki/ - mediawiki_user: Selenium_user - mediawiki_password: vagrant mw-vagrant-guest: + user_factory: true mediawiki_url: http://127.0.0.1/wiki/ - mediawiki_user: Selenium_user - mediawiki_password: vagrant beta: mediawiki_url: http://en.wikipedia.beta.wmflabs.org/wiki/ @@ -34,4 +32,8 @@ test2: mediawiki_user: Selenium_user # mediawiki_password: SET THIS IN THE ENVIRONMENT! +integration: + user_factory: true + # mediawiki_url: THIS WILL BE SET BY JENKINS + default: *default diff --git a/tests/browser/features/support/env.rb b/tests/browser/features/support/env.rb index 5eff4ce5cc..c1072b26a2 100644 --- a/tests/browser/features/support/env.rb +++ b/tests/browser/features/support/env.rb @@ -1,4 +1,3 @@ -require 'mediawiki_selenium' - -require 'mediawiki_selenium/support' +require 'mediawiki_selenium/cucumber' +require 'mediawiki_selenium/pages' require 'mediawiki_selenium/step_definitions' diff --git a/tests/parser/parserTest.inc b/tests/parser/parserTest.inc index f29e8b89f5..b91a5bc8b6 100644 --- a/tests/parser/parserTest.inc +++ b/tests/parser/parserTest.inc @@ -964,7 +964,7 @@ class ParserTest { 'site_stats', 'ipblocks', 'image', 'oldimage', 'recentchanges', 'watchlist', 'interwiki', 'logging', 'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo', - 'archive', 'user_groups', 'page_props', 'category', 'msg_resource', 'msg_resource_links' + 'archive', 'user_groups', 'page_props', 'category' ); if ( in_array( $this->db->getType(), array( 'mysql', 'sqlite', 'oracle' ) ) ) { diff --git a/tests/parser/parserTests.txt b/tests/parser/parserTests.txt index c456fcb9de..d0a3d08b62 100644 --- a/tests/parser/parserTests.txt +++ b/tests/parser/parserTests.txt @@ -14,6 +14,7 @@ # Plus any combination of these: # # cat add category links +# (ignored by Parsoid, since it emits s) # ill add inter-language links # (ignored by Parsoid, since it emits s) # subpage enable subpages (disabled by default) @@ -1335,6 +1336,8 @@ array ( ) +!! html/parsoid +


 !! end
 
 !! test
@@ -3329,14 +3332,18 @@ parsoid=wt2html,wt2wt
   
 !! end
 
+## We used to, but no longer wt2wt this test since the default serializer
+## will normalize all categories to serialize on their own line.
+## This wikitext usage is going to be fairly uncommon in production and
+## selser will take care of preserving formatting in those scenarios.
 !! test
 7b. Indent-pre and category links
 !! options
-parsoid=wt2html,wt2wt
+parsoid=wt2html
 !! wikitext
  [[Category:foo]] a
  [[Category:foo]] {{echo|b}}
-!! html
+!! html/parsoid
 
 a
   b
!! end @@ -7119,6 +7126,17 @@ Piped link with multiple pipe characters in link text

|The|Main|Page|

!! end +!! test +Piped link with no link text +!! wikitext +[[Thomas Bek (bishop of St David's)|]] +!! html/php +

[[Thomas Bek (bishop of St David's)|]] +

+!! html/parsoid +

[[Thomas Bek (bishop of St David's)|]]

+!! end + !! test Broken link !! wikitext @@ -10860,8 +10878,14 @@ Un-closed !! html !! end +## We used to, but no longer wt2wt this test since the default serializer +## will normalize the include directives to serialize on their own line. +## Selser will take care of preserving formatting in scenarios where they +## intermingled with other wikitext. !! test Includes and comments at SOL +!! options +parsoid=wt2html,html2html !! wikitext == hu == @@ -11042,10 +11066,14 @@ parsoid=wt2html,wt2wt !!end +## We used to, but no longer wt2wt this test since the default serializer +## will normalize the include directives to serialize on their own line. +## Selser will take care of preserving formatting in scenarios where they +## intermingled with other wikitext. !!test 2. Table tag in SOL posn. should get reparsed correctly with valid TSR !!options -parsoid=wt2html,wt2wt +parsoid=wt2html !!wikitext a{| {{{b}}} |c @@ -14245,7 +14273,7 @@ cat pst !! wikitext [[Category:MediaWiki User's Guide|]] -!! html +!! html/php [[Category:MediaWiki User's Guide|MediaWiki User's Guide]] !! end @@ -14256,19 +14284,26 @@ cat pst !! wikitext [[Category:Foo (bar)|]] -!! html +!! html/php [[Category:Foo (bar)|Foo]] !! end +## We used to, but no longer wt2wt this test since the default serializer +## will normalize all categories to serialize on their own line. +## This wikitext usage is going to be fairly uncommon in production and +## selser will take care of preserving formatting in those scenarios. !! test Category with link tail !! options cat pst +parsoid=wt2html !! wikitext 123[[Category:Foo]]456 -!! html +!! html/php 123[[Category:Foo]]456 +!! html/parsoid +

123456

!! end !! test @@ -14278,7 +14313,7 @@ cat pst !! wikitext [[Category:{{echo|Foo}}]] -!! html +!! html/php [[Category:{{echo|Foo}}]] !! end @@ -14289,7 +14324,7 @@ cat pst !! wikitext [[Category:Foo|{{echo|Bar}}]] -!! html +!! html/php [[Category:Foo|{{echo|Bar}}]] !! end @@ -14300,12 +14335,18 @@ cat pst !! wikitext [[Category:{{echo|Foo}}|{{echo|Bar}}]] -!! html +!! html/php [[Category:{{echo|Foo}}|{{echo|Bar}}]] !! end +## We used to, but no longer wt2wt this test since the default serializer +## will normalize all categories to serialize on their own line. +## This wikitext usage is going to be fairly uncommon in production and +## selser will take care of preserving formatting in those scenarios. !! test Category / paragraph interactions +!! options +parsoid=wt2html !! wikitext Foo [[Category:Baz]] Bar @@ -14332,7 +14373,7 @@ Bar [[Category:Baz]] {{echo|[[Category:Baz]]}} [[Category:Baz]] -!! html +!! html/php

Foo Bar

Foo Bar @@ -14342,20 +14383,32 @@ Bar

Foo Bar

+!! html/parsoid +

Foo Bar

+

Foo Bar

+

Foo Bar

+

Foo Bar

+

Foo Bar

+ !! end +## We used to, but no longer wt2wt this test since the default serializer +## will normalize all categories to serialize on their own line. +## This wikitext usage is going to be fairly uncommon in production and +## selser will take care of preserving formatting in those scenarios. +## ## 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 +parsoid=wt2html !! wikitext This [[Category:Foo]] and this should be part of same paragraph (not an indent-pre) {{echo|[[Category:Foo]] and so should this!}} -!! html +!! html/php

This and this should be part of same paragraph (not an indent-pre) and so should this!

!! html/parsoid @@ -14453,8 +14506,14 @@ parsoid=wt2html !! end +## We used to, but no longer wt2wt this test since the default serializer +## will normalize all categories to serialize on their own line. +## This wikitext usage is going to be fairly uncommon in production and +## selser will take care of preserving formatting in those scenarios. !! test 6. Categories and newlines: migrateTrailingCategories dom pass should not migrate categories not preceded by newlines +!! options +parsoid=wt2html !! wikitext * a [[Category:Foo]] !! html/parsoid @@ -14505,13 +14564,20 @@ parsoid

!! end -# html2wt localizes the "Category" namespace. -# XXX the element needs an empty data-parsoid attribute, or -# else the html2html test fails because spaces are inserted. +# We used to, but no longer wt2wt this test since the default serializer +# will normalize all categories to serialize on their own line. +# This wikitext usage is going to be fairly uncommon in production and +# selser will take care of preventing whitespace insertion if this +# occurs in an article. +# +# html2html disabled for the same reason (whitespace insertion between +# x and y). +# +# html2wt disabled because it localizes the "Category" namespace. !! test Link prefix/suffixes aren't applied to category links !! options -parsoid=wt2html,wt2wt,html2html +parsoid=wt2html language=is !! wikitext x[[Category:Foo]]y @@ -16152,10 +16218,15 @@ array ( ) +!! html/parsoid +

 !! end
 
+## Don't expect parsoid to rt this form.
 !! test
 Parser hook: empty input using terminated empty elements
+!! options
+parsoid=wt2html,html2html
 !! wikitext
 
 !! html/php
@@ -16165,6 +16236,8 @@ array (
 )
 
 
+!! html/parsoid
+

 !! end
 
 !! test
@@ -16178,6 +16251,8 @@ array (
 )
 
 
+!! html/parsoid
+

 !! end
 
 !! test
@@ -16191,11 +16266,15 @@ array (
 )
 
 
+!! html/parsoid
+

 !! end
 
-
+## Don't expect parsoid to rt this form.
 !! test
 Parser hook: case insensitive
+!! options
+parsoid=wt2html,html2html
 !! wikitext
 input
 !! html/php
@@ -16205,11 +16284,15 @@ array (
 )
 
 
+!! html/parsoid
+

 !! end
 
-
+## Don't expect parsoid to rt this form.
 !! test
 Parser hook: case insensitive, redux
+!! options
+parsoid=wt2html,html2html
 !! wikitext
 input
 !! html/php
@@ -16219,6 +16302,8 @@ array (
 )
 
 
+!! html/parsoid
+

 !! end
 
 !! test
@@ -16234,11 +16319,35 @@ array (
 )
 </tag>
 
+!! html/parsoid
+
</tag>
 !! end
 
 !! test
 Parser hook: basic arguments
 !! wikitext
+
+!! html/php
+
+''
+array (
+  'width' => '200',
+  'height' => '100',
+  'depth' => '50',
+  'square' => '',
+)
+
+ +!! html/parsoid +

+!! end
+
+## Don't expect parsoid to rt this form.
+!! test
+Parser hook: basic arguments, variations
+!! options
+parsoid=wt2html,html2html
+!! wikitext
 
 !! html/php
 
@@ -16251,12 +16360,14 @@ array (
 )
 
+!! html/parsoid +

 !! end
 
 !! test
 Parser hook: argument containing a forward slash (bug 5344)
 !! wikitext
-
+
 !! html/php
 
 ''
@@ -16265,10 +16376,15 @@ array (
 )
 
+!! html/parsoid +

 !! end
 
+## Don't expect parsoid to rt this form.
 !! test
 Parser hook: empty input using terminated empty elements (bug 2374)
+!! options
+parsoid=wt2html,html2html
 !! wikitext
 text
 !! html/php
@@ -16279,6 +16395,8 @@ array (
 )
 text
 
+!! html/parsoid
+
text
 !! end
 
 # 
should be output literally since there is no matching tag that begins it @@ -16311,21 +16429,28 @@ array ( Parser hook: static parser hook not inside a comment !! wikitext hello, world - + + !! html/php -

hello, world +


+hello, world

+!! html/parsoid +

+

hello, world

!! end - !! test Parser hook: static parser hook inside a comment !! wikitext - + !! html/php


+!! html/parsoid + +

!! end # Nested template calls; this case was broken by Parser.php rev 1.506, @@ -19211,17 +19336,25 @@ Category:分類 blah !! endarticle +## We used to, but no longer wt2wt this test since the default serializer +## will normalize all categories to serialize on their own line. +## This wikitext usage is going to be fairly uncommon in production and +## selser will take care of preserving formatting in those scenarios. !! test Don't convert blue categorylinks to another variant (bug 33210) !! options -language=zh cat +cat +language=zh +parsoid=wt2html !! wikitext [[A]][[Category:分类]] -!! html +!! html/php 分类 +!! html/parsoid +

A

+ !! end - !! test Stripping -{}- tags (language variants) !! options @@ -19756,7 +19889,7 @@ Tildes in comments pst !! wikitext -!! html +!! html/php !! end @@ -20078,7 +20211,7 @@ Edit comment with link comment !! wikitext I like the [[Main Page]] a lot -!! html +!! html/php I like the Main Page a lot !!end @@ -20088,7 +20221,7 @@ Edit comment with link and link text comment !! wikitext I like the [[Main Page|best pages]] a lot -!! html +!! html/php I like the best pages a lot !!end @@ -20098,7 +20231,7 @@ Edit comment with link and link text with suffix comment !! wikitext I like the [[Main Page|best page]]s a lot -!! html +!! html/php I like the best pages a lot !!end @@ -20108,7 +20241,7 @@ Edit comment with section link (non-local, eg in history list) comment title=[[Main Page]] !! wikitext /* External links */ removed bogus entries -!! html +!! html/php →‎External links: removed bogus entries !!end @@ -20118,7 +20251,7 @@ Edit comment with section link and text before it (non-local, eg in history list comment title=[[Main Page]] !! wikitext pre-comment text /* External links */ removed bogus entries -!! html +!! html/php pre-comment text →‎External links: removed bogus entries !!end @@ -20128,7 +20261,7 @@ Edit comment with section link (local, eg in diff view) comment local title=[[Main Page]] !! wikitext /* External links */ removed bogus entries -!! html +!! html/php →‎External links: removed bogus entries !!end @@ -20140,7 +20273,7 @@ subpage title=[[Subpage test]] !! wikitext Poked at a [[/subpage]] here... -!! html +!! html/php Poked at a /subpage here... !!end @@ -20152,7 +20285,7 @@ subpage title=[[Subpage test]] !! wikitext Poked at a [[/subpage|neat little page]] here... -!! html +!! html/php Poked at a neat little page here... !!end @@ -20163,7 +20296,7 @@ comment title=[[Subpage test]] !! wikitext Poked at a [[/subpage]] here... -!! html +!! html/php Poked at a /subpage here... !!end @@ -20175,7 +20308,7 @@ local title=[[Main Page]] !! wikitext [[#section]] -!! html +!! html/php #section !! end @@ -20186,24 +20319,28 @@ comment title=[[Main Page]] !! wikitext [[#section]] -!! html +!! html/php #section !! end !! test Anchor starting with underscore +!! options +title=[[Foo]] !! wikitext [[#_ref|One]] -!! html +!! html/php

One

+!! html/parsoid +

One

!! end !! test Id starting with underscore !! wikitext
-!! html +!! html/*
!! end @@ -20215,7 +20352,7 @@ comment title=[[Main Page]] !! wikitext /* __hello__world__ */ -!! html +!! html/php →‎__hello__world__ !! end @@ -20534,11 +20671,23 @@ HTML5 data attributes !! wikitext Baz

Quuz

-!! html +!! html/php

Baz

Quuz

+!! html/parsoid +

Baz

+

Quuz

+!! end + +!! test +Strip reserved data attributes +!! wikitext +
d
+!! html +
d
+ !! end !! test @@ -21377,14 +21526,12 @@ parsoid=wt2html,wt2wt !!test Ref: 1. ref-location should be replaced with an index span -!!options -parsoid !! wikitext A foo B foo C -!! html +!! html/parsoid

A [1] B [2] C [3]

@@ -21397,13 +21544,11 @@ C foo
B -!! html +!! html/parsoid

A [1] B [1]

    @@ -21413,14 +21558,12 @@ B foo B C -!! html +!! html/parsoid

    A [1] B [1] C [1]

    @@ -21432,12 +21575,10 @@ C foo
    -!! html +!! html/parsoid

    A [1]

    1. ↑ foo
    2. @@ -21446,15 +21587,13 @@ A foo !!test Ref: 5. body should accept generic wikitext -!!options -parsoid !! wikitext A This is a '''[[bolded link]]''' and this is a {{echo|transclusion}} -!! html +!! html/parsoid

      A [1]

        @@ -21465,8 +21604,6 @@ A !!test Ref: 6. indent-pres should not be output in ref-body -!!options -parsoid !! wikitext A foo @@ -21475,7 +21612,7 @@ A -!! html +!! html/parsoid

        A [1]

          @@ -21488,8 +21625,6 @@ A !!test Ref: 7. No p-wrapping in ref-body -!!options -parsoid !! wikitext A foo @@ -21505,7 +21640,7 @@ booz -!! html +!! html/parsoid

          A [1]

            @@ -21525,27 +21660,23 @@ booz !!test Ref: 8. transclusion wikitext has lower precedence -!!options -parsoid !! wikitext A foo {{echo| B C}} -!! html +!! html/parsoid

            A [1] B C}}

              -
            1. ↑ foo {{echo|
            2. +
            3. ↑ foo {{echo|
            !!end !!test Ref: 9. unclosed comments should not leak out of ref-body -!!options -parsoid !! wikitext A foo @@ -21554,13 +21685,11 @@ A foo [[Category:A3]] how goes it == +== [[Category:A3]] how goes it == -== it goes well [[Category:A4]] == +== it goes well [[Category:A4]] == -==howdy [[Category:A5]] == +==howdy [[Category:A5]]== == __TOC__ ok == !! end @@ -25611,7 +25754,7 @@ parsoid={ "modes": ["html2wt"], "suppressErrors": true } # 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 +Strip span tags added to mark misnested links !! options parsoid=html2wt !! html/parsoid @@ -25620,10 +25763,112 @@ parsoid=html2wt [http://test.com][[one]] two three !! end +!! test +Use data-parsoid.firstWikitextNode to compute newline constraints for template content +!! options +parsoid=html2wt +!! html/parsoid +a + +
            d +
            +!! wikitext +{{echo|a}} +{|{{echo|c +{{!}}d +}} +|} +!! end + +## This test verifies the presence and computation of this attribute indirectly +## by making an edit and ensuring that the serialization is correct (which it would be +## only if firstWikitextNode is properly set). +!! test +data-parsoid.firstWikitextNode should be computed properly in the presence of fostered content +!! options +parsoid= { + "modes": ["wt2wt"], + "changes": [ + [ "div#x", "remove" ], + [ "div", "before", "
            new
            " ] + ] +} +!! wikitext +
            foo
            +{| +{{echo|
            boo
            +{{!}}b}} +|c +|} +!! wikitext/edited + +
            new
            +{| +{{echo|
            boo
            +{{!}}b}} +|c +|} +!! end + # -------------------------------------------- # Tests spec'ing wikitext serialization norms | # -------------------------------------------- +!! test +1. Categories should always be serialized on their own line +!! options +parsoid=html2wt +!! html/parsoid +foobar +!! wikitext +foo +[[Category:Foo]] +bar +!! end + +!! test +2. Categories that are part of templates should not introduce a line break +!! wikitext +foo {{echo|bar [[Category:baz]]}} bar +!! html/parsoid +

            foo bar bar

            +!! end + +# Careful while editing these next 2 tests. There are \u200f characters +# before and after the tags in the HTML and following some +# of the categories in wikitext +# Do not remove these characters in edits. +# +# As part of the serialization, these bidi characters will get stripped. +!! test +RTL (\u200f) and LTR (\u200e) markers around category tags should be stripped +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": true +} +!! html/parsoid +

            ‏‏ +‏‏

            +!! wikitext +[[קטגוריה:טקסים]] +[[קטגוריה: שיטות משפט]] +!! end + +!! test +RTL (\u200f) and LTR (\u200e) markers should not be stripped if followed by a text node +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": true +} +!! html/parsoid +

            ‏‏y

            +!! wikitext +[[קטגוריה:טקסים]] +‏y +!! end + !! test Lists: Add space after bullets !! options @@ -25711,6 +25956,35 @@ parsoid={ !! wikitext/edited !! end +!! test +Headings: Replace
            with a single whitespace char (when scrubWikitext = true) +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": true +} +!! html/parsoid +

            foo
            bar

            +

            foo
            bar
            baz

            +!! wikitext +== foo bar == + +== foo bar baz == +!! end + +!! test +Headings: Replace
            with a single whitespace char (when scrubWikitext = false) +!! options +parsoid={ + "modes": ["html2wt"], + "scrubWikitext": false +} +!! html/parsoid +

            foo
            bar

            +!! wikitext +== foo
            bar == +!! end + !! test 1. WT Quote Tags: suppress newly created empty style tags !! options @@ -26216,10 +26490,61 @@ parsoid=html2wt <nowiki>''foo''</nowiki> !! end +# This is meant to be an interim fix while we go about figuring out +# how to not introduce these trailing s in the first place. +!! test +T115717: Strip trailing s (without affecting valid uses) +!! options +parsoid=html2wt +!! html/parsoid +

            x +y

            +

            +

            +!! wikitext +x +y + +{{echo| +1 = }} + +{{echo| +1 = +}} +!! end + # --------------------------------------------------- # End of tests spec'ing wikitext serialization norms | # --------------------------------------------------- +# T104032 +!! test +Bare inline nodes not wrapped inside p-tags should be treated as p-wrapped +!! options +parsoid=html2wt +!! html/parsoid +a

            b

            +c

            d

            + + + +
            a

            b

            c

            d

            +!! wikitext +a + +b + +'''c''' + +d +{| +|a +b +|'''c''' +d +|} +!! end + # ----------------------------------------------------------------- # End of section for Parsoid-only html2wt tests for serialization # of new content @@ -26339,12 +26664,3 @@ Empty LI (T49673)
          1. b
          2. !! end - -!! test -reserved data attributes stripped -!! wikitext -
            d
            -!! html -
            d
            - -!! end diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php index 9e4a984653..fc2f743e12 100644 --- a/tests/phpunit/MediaWikiTestCase.php +++ b/tests/phpunit/MediaWikiTestCase.php @@ -520,10 +520,17 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * @since 1.21 */ public static function teardownTestDB() { + global $wgJobClasses; + if ( !self::$dbSetup ) { return; } + foreach ( $wgJobClasses as $type => $class ) { + // Delete any jobs under the clone DB (or old prefix in other stores) + JobQueueGroup::singleton()->get( $type )->delete(); + } + CloneDatabase::changePrefix( self::$oldTablePrefix ); self::$oldTablePrefix = false; diff --git a/tests/phpunit/data/templates/conds.mustache b/tests/phpunit/data/templates/conds.mustache new file mode 100644 index 0000000000..5ebd2ea3d9 --- /dev/null +++ b/tests/phpunit/data/templates/conds.mustache @@ -0,0 +1 @@ +{{#list}}oh no{{/list}}{{#foo}}none of this should render{{/foo}} \ No newline at end of file diff --git a/tests/phpunit/includes/ExportTest.php b/tests/phpunit/includes/ExportTest.php new file mode 100644 index 0000000000..202603030b --- /dev/null +++ b/tests/phpunit/includes/ExportTest.php @@ -0,0 +1,71 @@ + + */ +class ExportTest extends MediaWikiLangTestCase { + + protected function setUp() { + parent::setUp(); + $this->setMwGlobals( array( + 'wgContLang' => Language::factory( 'en' ), + 'wgLanguageCode' => 'en', + 'wgCapitalLinks' => true, + ) ); + } + + /** + * @covers WikiExporter::pageByTitle + */ + public function testPageByTitle() { + global $wgContLang; + $pageTitle = 'UTPage'; + + $exporter = new WikiExporter( + $this->db, + WikiExporter::FULL + ); + + $title = Title::newFromText( $pageTitle ); + + ob_start(); + $exporter->openStream(); + $exporter->pageByTitle( $title ); + $exporter->closeStream(); + $xmlString = ob_get_clean(); + + // This throws error if invalid xml output + $xmlObject = simplexml_load_string( $xmlString ); + + /** + * Check namespaces match xml + * FIXME: PHP 5.3 support. When we don't support PHP 5.3, + * add ->namespace to object and remove from array + */ + $xmlNamespaces = (array) $xmlObject->siteinfo->namespaces; + $xmlNamespaces = str_replace( ' ', '_', $xmlNamespaces['namespace'] ); + unset ( $xmlNamespaces[ '@attributes' ] ); + foreach ( $xmlNamespaces as &$namespaceObject ) { + if ( is_object( $namespaceObject ) ) { + $namespaceObject = ''; + } + } + + $actualNamespaces = (array) $wgContLang->getNamespaces(); + $actualNamespaces = array_values( $actualNamespaces ); + $this->assertEquals( $actualNamespaces, $xmlNamespaces ); + + // Check xml page title correct + $xmlTitle = (array) $xmlObject->page->title; + $this->assertEquals( $pageTitle, $xmlTitle[0] ); + + // Check xml page text is not empty + $text = (array) $xmlObject->page->revision->text; + $this->assertNotEquals( '', $text[0] ); + } + +} diff --git a/tests/phpunit/includes/ImportLinkCacheIntegrationTest.php b/tests/phpunit/includes/ImportLinkCacheIntegrationTest.php deleted file mode 100644 index 1433b898d0..0000000000 --- a/tests/phpunit/includes/ImportLinkCacheIntegrationTest.php +++ /dev/null @@ -1,112 +0,0 @@ -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/ImportTest.php b/tests/phpunit/includes/ImportTest.php deleted file mode 100644 index 9c224309bb..0000000000 --- a/tests/phpunit/includes/ImportTest.php +++ /dev/null @@ -1,162 +0,0 @@ - - */ -class ImportTest extends MediaWikiLangTestCase { - - private function getDataSource( $xml ) { - return new ImportStringSource( $xml ); - } - - /** - * @covers WikiImporter::handlePage - * @dataProvider getRedirectXML - * @param string $xml - * @param string|null $redirectTitle - */ - public function testHandlePageContainsRedirect( $xml, $redirectTitle ) { - $source = $this->getDataSource( $xml ); - - $redirect = null; - $callback = function ( Title $title, ForeignTitle $foreignTitle, $revCount, - $sRevCount, $pageInfo ) use ( &$redirect ) { - if ( array_key_exists( 'redirect', $pageInfo ) ) { - $redirect = $pageInfo['redirect']; - } - }; - - $importer = new WikiImporter( - $source, - ConfigFactory::getDefaultInstance()->makeConfig( 'main' ) - ); - $importer->setPageOutCallback( $callback ); - $importer->doImport(); - - $this->assertEquals( $redirectTitle, $redirect ); - } - - public function getRedirectXML() { - // @codingStandardsIgnoreStart Generic.Files.LineLength - return array( - array( - <<< EOF - - - Test - 0 - 21 - - - 20 - 2014-05-27T10:00:00Z - - Admin - 10 - - Admin moved page [[Test]] to [[Test22]] - wikitext - text/x-wiki - #REDIRECT [[Test22]] - tq456o9x3abm7r9ozi6km8yrbbc56o6 - - - -EOF - , - 'Test22' - ), - array( - <<< EOF - - - Test - 0 - 42 - - 421 - 2014-05-27T11:00:00Z - - Admin - 10 - - Abcd - n7uomjq96szt60fy5w3x7ahf7q8m8rh - wikitext - text/x-wiki - - - -EOF - , - null - ), - ); - // @codingStandardsIgnoreEnd - } - - /** - * @covers WikiImporter::handleSiteInfo - * @dataProvider getSiteInfoXML - * @param string $xml - * @param array|null $namespaces - */ - public function testSiteInfoContainsNamespaces( $xml, $namespaces ) { - $source = $this->getDataSource( $xml ); - - $importNamespaces = null; - $callback = function ( array $siteinfo, $innerImporter ) use ( &$importNamespaces ) { - $importNamespaces = $siteinfo['_namespaces']; - }; - - $importer = new WikiImporter( - $source, - ConfigFactory::getDefaultInstance()->makeConfig( 'main' ) - ); - $importer->setSiteInfoCallback( $callback ); - $importer->doImport(); - - $this->assertEquals( $importNamespaces, $namespaces ); - } - - public function getSiteInfoXML() { - // @codingStandardsIgnoreStart Generic.Files.LineLength - return array( - array( - <<< EOF - - - - Media - Special - - Talk - User - User talk - Portal - Portal talk - - - -EOF - , - array( - '-2' => 'Media', - '-1' => 'Special', - '0' => '', - '1' => 'Talk', - '2' => 'User', - '3' => 'User talk', - '100' => 'Portal', - '101' => 'Portal talk', - ) - ), - ); - // @codingStandardsIgnoreEnd - } - -} diff --git a/tests/phpunit/includes/PreferencesTest.php b/tests/phpunit/includes/PreferencesTest.php index 5841bb6f07..fe431b6673 100644 --- a/tests/phpunit/includes/PreferencesTest.php +++ b/tests/phpunit/includes/PreferencesTest.php @@ -45,36 +45,36 @@ class PreferencesTest extends MediaWikiTestCase { * Placeholder to verify bug 34302 * @covers Preferences::profilePreferences */ - public function testEmailFieldsWhenUserHasNoEmail() { + public function testEmailAuthenticationFieldWhenUserHasNoEmail() { $prefs = $this->prefsFor( 'noemail' ); $this->assertArrayHasKey( 'cssclass', - $prefs['emailaddress'] + $prefs['emailauthentication'] ); - $this->assertEquals( 'mw-email-none', $prefs['emailaddress']['cssclass'] ); + $this->assertEquals( 'mw-email-none', $prefs['emailauthentication']['cssclass'] ); } /** * Placeholder to verify bug 34302 * @covers Preferences::profilePreferences */ - public function testEmailFieldsWhenUserEmailNotAuthenticated() { + public function testEmailAuthenticationFieldWhenUserEmailNotAuthenticated() { $prefs = $this->prefsFor( 'notauth' ); $this->assertArrayHasKey( 'cssclass', - $prefs['emailaddress'] + $prefs['emailauthentication'] ); - $this->assertEquals( 'mw-email-not-authenticated', $prefs['emailaddress']['cssclass'] ); + $this->assertEquals( 'mw-email-not-authenticated', $prefs['emailauthentication']['cssclass'] ); } /** * Placeholder to verify bug 34302 * @covers Preferences::profilePreferences */ - public function testEmailFieldsWhenUserEmailIsAuthenticated() { + public function testEmailAuthenticationFieldWhenUserEmailIsAuthenticated() { $prefs = $this->prefsFor( 'auth' ); $this->assertArrayHasKey( 'cssclass', - $prefs['emailaddress'] + $prefs['emailauthentication'] ); - $this->assertEquals( 'mw-email-authenticated', $prefs['emailaddress']['cssclass'] ); + $this->assertEquals( 'mw-email-authenticated', $prefs['emailauthentication']['cssclass'] ); } /** Helper */ diff --git a/tests/phpunit/includes/api/ApiOptionsTest.php b/tests/phpunit/includes/api/ApiOptionsTest.php index 51154ae398..fff05c76bf 100644 --- a/tests/phpunit/includes/api/ApiOptionsTest.php +++ b/tests/phpunit/includes/api/ApiOptionsTest.php @@ -36,6 +36,10 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mUserMock->expects( $this->any() ) ->method( 'getOptionKinds' )->will( $this->returnCallback( array( $this, 'getOptionKinds' ) ) ); + // No actual DB data + $this->mUserMock->expects( $this->any() ) + ->method( 'getInstanceForUpdate' )->will( $this->returnValue( $this->mUserMock ) ); + // Create a new context $this->mContext = new DerivativeContext( new RequestContext() ); $this->mContext->getContext()->setTitle( Title::newFromText( 'Test' ) ); @@ -283,21 +287,21 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mUserMock->expects( $this->at( 2 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 4 ) ) + $this->mUserMock->expects( $this->at( 5 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'willBeNull' ), $this->identicalTo( null ) ); - $this->mUserMock->expects( $this->at( 5 ) ) + $this->mUserMock->expects( $this->at( 6 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 6 ) ) + $this->mUserMock->expects( $this->at( 7 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'willBeEmpty' ), $this->equalTo( '' ) ); - $this->mUserMock->expects( $this->at( 7 ) ) + $this->mUserMock->expects( $this->at( 8 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 8 ) ) + $this->mUserMock->expects( $this->at( 9 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) ); @@ -317,17 +321,17 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mUserMock->expects( $this->once() ) ->method( 'resetOptions' ); - $this->mUserMock->expects( $this->at( 4 ) ) + $this->mUserMock->expects( $this->at( 5 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 5 ) ) + $this->mUserMock->expects( $this->at( 6 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) ); - $this->mUserMock->expects( $this->at( 6 ) ) + $this->mUserMock->expects( $this->at( 7 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 7 ) ) + $this->mUserMock->expects( $this->at( 8 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'name' ), $this->equalTo( 'value' ) ); @@ -350,19 +354,19 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mUserMock->expects( $this->never() ) ->method( 'resetOptions' ); - $this->mUserMock->expects( $this->at( 3 ) ) + $this->mUserMock->expects( $this->at( 4 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'testmultiselect-opt1' ), $this->identicalTo( true ) ); - $this->mUserMock->expects( $this->at( 4 ) ) + $this->mUserMock->expects( $this->at( 5 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'testmultiselect-opt2' ), $this->identicalTo( null ) ); - $this->mUserMock->expects( $this->at( 5 ) ) + $this->mUserMock->expects( $this->at( 6 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'testmultiselect-opt3' ), $this->identicalTo( false ) ); - $this->mUserMock->expects( $this->at( 6 ) ) + $this->mUserMock->expects( $this->at( 7 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'testmultiselect-opt4' ), $this->identicalTo( false ) ); @@ -429,7 +433,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mUserMock->expects( $this->never() ) ->method( 'resetOptions' ); - $this->mUserMock->expects( $this->at( 3 ) ) + $this->mUserMock->expects( $this->once() ) ->method( 'setOption' ) ->with( $this->equalTo( 'userjs-option' ), $this->equalTo( '1' ) ); diff --git a/tests/phpunit/includes/api/ApiResultTest.php b/tests/phpunit/includes/api/ApiResultTest.php index d43db71421..9dbde3d93f 100644 --- a/tests/phpunit/includes/api/ApiResultTest.php +++ b/tests/phpunit/includes/api/ApiResultTest.php @@ -458,6 +458,13 @@ class ApiResultTest extends MediaWikiTestCase { ); } + // Add two values and some metadata, but ensure metadata is not counted + $result = new ApiResult( 100 ); + $obj = array( 'attr' => '12345' ); + ApiResult::setContentValue( $obj, 'content', '1234567890' ); + $this->assertTrue( $result->addValue( null, 'foo', $obj ) ); + $this->assertSame( 15, $result->getSize() ); + $result = new ApiResult( 10 ); $formatter = new ApiErrorFormatter( $result, Language::factory( 'en' ), 'none', false ); $result->setErrorFormatter( $formatter ); diff --git a/tests/phpunit/includes/changes/RCCacheEntryFactoryTest.php b/tests/phpunit/includes/changes/RCCacheEntryFactoryTest.php index 0b877275ee..552dacb7b9 100644 --- a/tests/phpunit/includes/changes/RCCacheEntryFactoryTest.php +++ b/tests/phpunit/includes/changes/RCCacheEntryFactoryTest.php @@ -212,7 +212,7 @@ class RCCacheEntryFactoryTest extends MediaWikiLangTestCase { ), 'child' => array( 'tag' => 'a', - 'content' => 'Talk', + 'content' => 'talk', ) ), $cacheEntry->usertalklink, diff --git a/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php index 31e4f5b826..6403905e4e 100644 --- a/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php +++ b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php @@ -256,4 +256,59 @@ class DatabaseMysqlBaseTest extends MediaWikiTestCase { $this->assertTrue( $pos2->hasReached( $pos1 ) ); $this->assertFalse( $pos1->hasReached( $pos2 ) ); } + + /** + * @dataProvider provideLagAmounts + */ + function testPtHeartbeat( $lag ) { + $db = $this->getMockBuilder( 'DatabaseMysql' ) + ->disableOriginalConstructor() + ->setMethods( array( + 'getLagDetectionMethod', 'getHeartbeatData', 'getMasterServerInfo' ) ) + ->getMock(); + + $db->expects( $this->any() ) + ->method( 'getLagDetectionMethod' ) + ->will( $this->returnValue( 'pt-heartbeat' ) ); + + $db->expects( $this->any() ) + ->method( 'getMasterServerInfo' ) + ->will( $this->returnValue( array( 'serverId' => 172, 'asOf' => time() ) ) ); + + // Fake the current time. + list( $nowSecFrac, $nowSec ) = explode( ' ', microtime() ); + $now = (float)$nowSec + (float)$nowSecFrac; + // Fake the heartbeat time. + // Work arounds for weak DataTime microseconds support. + $ptTime = $now - $lag; + $ptSec = (int)$ptTime; + $ptSecFrac = ( $ptTime - $ptSec ); + $ptDateTime = new DateTime( "@$ptSec" ); + $ptTimeISO = $ptDateTime->format( 'Y-m-d\TH:i:s' ); + $ptTimeISO .= ltrim( number_format( $ptSecFrac, 6 ), '0' ); + + $db->expects( $this->any() ) + ->method( 'getHeartbeatData' ) + ->with( 172 ) + ->will( $this->returnValue( array( $ptTimeISO, $now ) ) ); + + $db->setLBInfo( 'clusterMasterHost', 'db1052' ); + $lagEst = $db->getLag(); + + $this->assertGreaterThan( $lag - .010, $lagEst, "Correct heatbeat lag" ); + $this->assertLessThan( $lag + .010, $lagEst, "Correct heatbeat lag" ); + } + + function provideLagAmounts() { + return array( + array( 0 ), + array( 0.3 ), + array( 6.5 ), + array( 10.1 ), + array( 200.2 ), + array( 400.7 ), + array( 600.22 ), + array( 1000.77 ), + ); + } } diff --git a/tests/phpunit/includes/db/LBFactoryTest.php b/tests/phpunit/includes/db/LBFactoryTest.php index 519d3a0dfb..a64744555a 100644 --- a/tests/phpunit/includes/db/LBFactoryTest.php +++ b/tests/phpunit/includes/db/LBFactoryTest.php @@ -103,9 +103,13 @@ class LBFactoryTest extends MediaWikiTestCase { $dbw = $lb->getConnection( DB_MASTER ); $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' ); + $this->assertEquals( + $wgDBserver, $dbw->getLBInfo( 'clusterMasterHost' ), 'cluster master set' ); $dbr = $lb->getConnection( DB_SLAVE ); $this->assertTrue( $dbr->getLBInfo( 'slave' ), 'slave shows as slave' ); + $this->assertEquals( + $wgDBserver, $dbr->getLBInfo( 'clusterMasterHost' ), 'cluster master set' ); $factory->shutdown(); $lb->closeAll(); diff --git a/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php b/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php index 9866ce140d..d2b267a060 100644 --- a/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php +++ b/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php @@ -167,7 +167,6 @@ class KafkaHandlerTest extends MediaWikiTestCase { } } - public function testBatchHandlesNullFormatterResult() { $produce = $this->getMockBuilder( 'Kafka\Produce' ) ->disableOriginalConstructor() diff --git a/tests/phpunit/includes/deferred/LinksUpdateTest.php b/tests/phpunit/includes/deferred/LinksUpdateTest.php index f8251ec205..016a7aaed8 100644 --- a/tests/phpunit/includes/deferred/LinksUpdateTest.php +++ b/tests/phpunit/includes/deferred/LinksUpdateTest.php @@ -6,6 +6,7 @@ * ^--- make sure temporary tables are used. */ class LinksUpdateTest extends MediaWikiLangTestCase { + protected $testingPageId; function __construct( $name = null, array $data = array(), $dataName = '' ) { parent::__construct( $name, $data, $dataName ); @@ -45,7 +46,8 @@ class LinksUpdateTest extends MediaWikiLangTestCase { } public function addDBData() { - $this->insertPage( 'Testing' ); + $res = $this->insertPage( 'Testing' ); + $this->testingPageId = $res['id']; $this->insertPage( 'Some_other_page' ); $this->insertPage( 'Template:TestingTemplate' ); } @@ -64,8 +66,9 @@ class LinksUpdateTest extends MediaWikiLangTestCase { * @covers ParserOutput::addLink */ public function testUpdate_pagelinks() { + /** @var Title $t */ /** @var ParserOutput $po */ - list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", $this->testingPageId ); $po->addLink( Title::newFromText( "Foo" ) ); $po->addLink( Title::newFromText( "Special:Foo" ) ); // special namespace should be ignored @@ -78,7 +81,7 @@ class LinksUpdateTest extends MediaWikiLangTestCase { 'pagelinks', 'pl_namespace, pl_title', - 'pl_from = 111', + 'pl_from = ' . $this->testingPageId, array( array( NS_MAIN, 'Foo' ) ) ); $this->assertArrayEquals( array( @@ -97,7 +100,7 @@ class LinksUpdateTest extends MediaWikiLangTestCase { 'pagelinks', 'pl_namespace, pl_title', - 'pl_from = 111', + 'pl_from = ' . $this->testingPageId, array( array( NS_MAIN, 'Bar' ), array( NS_TALK, 'Bar' ), @@ -117,13 +120,20 @@ class LinksUpdateTest extends MediaWikiLangTestCase { */ public function testUpdate_externallinks() { /** @var ParserOutput $po */ - list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", $this->testingPageId ); $po->addExternalLink( "http://testing.com/wiki/Foo" ); - $this->assertLinksUpdate( $t, $po, 'externallinks', 'el_to, el_index', 'el_from = 111', array( - array( 'http://testing.com/wiki/Foo', 'http://com.testing./wiki/Foo' ), - ) ); + $this->assertLinksUpdate( + $t, + $po, + 'externallinks', + 'el_to, el_index', + 'el_from = ' . $this->testingPageId, + array( + array( 'http://testing.com/wiki/Foo', 'http://com.testing./wiki/Foo' ), + ) + ); } /** @@ -133,13 +143,18 @@ class LinksUpdateTest extends MediaWikiLangTestCase { /** @var ParserOutput $po */ $this->setMwGlobals( 'wgCategoryCollation', 'uppercase' ); - list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", $this->testingPageId ); $po->addCategory( "Foo", "FOO" ); - $this->assertLinksUpdate( $t, $po, 'categorylinks', 'cl_to, cl_sortkey', 'cl_from = 111', array( - array( 'Foo', "FOO\nTESTING" ), - ) ); + $this->assertLinksUpdate( + $t, + $po, + 'categorylinks', + 'cl_to, cl_sortkey', + 'cl_from = ' . $this->testingPageId, + array( array( 'Foo', "FOO\nTESTING" ) ) + ); } public function testOnAddingAndRemovingCategory_recentChangesRowIsAdded() { @@ -217,14 +232,19 @@ class LinksUpdateTest extends MediaWikiLangTestCase { */ public function testUpdate_iwlinks() { /** @var ParserOutput $po */ - list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", $this->testingPageId ); $target = Title::makeTitleSafe( NS_MAIN, "Foo", '', 'linksupdatetest' ); $po->addInterwikiLink( $target ); - $this->assertLinksUpdate( $t, $po, 'iwlinks', 'iwl_prefix, iwl_title', 'iwl_from = 111', array( - array( 'linksupdatetest', 'Foo' ), - ) ); + $this->assertLinksUpdate( + $t, + $po, + 'iwlinks', + 'iwl_prefix, iwl_title', + 'iwl_from = ' . $this->testingPageId, + array( array( 'linksupdatetest', 'Foo' ) ) + ); } /** @@ -232,7 +252,7 @@ class LinksUpdateTest extends MediaWikiLangTestCase { */ public function testUpdate_templatelinks() { /** @var ParserOutput $po */ - list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", $this->testingPageId ); $po->addTemplate( Title::newFromText( "Template:Foo" ), 23, 42 ); @@ -242,7 +262,7 @@ class LinksUpdateTest extends MediaWikiLangTestCase { 'templatelinks', 'tl_namespace, tl_title', - 'tl_from = 111', + 'tl_from = ' . $this->testingPageId, array( array( NS_TEMPLATE, 'Foo' ) ) ); } @@ -252,13 +272,18 @@ class LinksUpdateTest extends MediaWikiLangTestCase { */ public function testUpdate_imagelinks() { /** @var ParserOutput $po */ - list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", $this->testingPageId ); $po->addImage( "Foo.png" ); - $this->assertLinksUpdate( $t, $po, 'imagelinks', 'il_to', 'il_from = 111', array( - array( 'Foo.png' ), - ) ); + $this->assertLinksUpdate( + $t, + $po, + 'imagelinks', + 'il_to', + 'il_from = ' . $this->testingPageId, + array( array( 'Foo.png' ) ) + ); } /** @@ -270,13 +295,18 @@ class LinksUpdateTest extends MediaWikiLangTestCase { ) ); /** @var ParserOutput $po */ - list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", $this->testingPageId ); $po->addLanguageLink( Title::newFromText( "en:Foo" )->getFullText() ); - $this->assertLinksUpdate( $t, $po, 'langlinks', 'll_lang, ll_title', 'll_from = 111', array( - array( 'En', 'Foo' ), - ) ); + $this->assertLinksUpdate( + $t, + $po, + 'langlinks', + 'll_lang, ll_title', + 'll_from = ' . $this->testingPageId, + array( array( 'En', 'Foo' ) ) + ); } /** @@ -286,7 +316,7 @@ class LinksUpdateTest extends MediaWikiLangTestCase { global $wgPagePropsHaveSortkey; /** @var ParserOutput $po */ - list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", $this->testingPageId ); $fields = array( 'pp_propname', 'pp_value' ); $expected = array(); @@ -318,7 +348,8 @@ class LinksUpdateTest extends MediaWikiLangTestCase { } } - $this->assertLinksUpdate( $t, $po, 'page_props', $fields, 'pp_page = 111', $expected ); + $this->assertLinksUpdate( + $t, $po, 'page_props', $fields, 'pp_page = ' . $this->testingPageId, $expected ); } public function testUpdate_page_props_without_sortkey() { diff --git a/tests/phpunit/includes/exception/HttpErrorTest.php b/tests/phpunit/includes/exception/HttpErrorTest.php index 66fe90c955..0aef146e2a 100644 --- a/tests/phpunit/includes/exception/HttpErrorTest.php +++ b/tests/phpunit/includes/exception/HttpErrorTest.php @@ -60,6 +60,4 @@ class HttpErrorTest extends MediaWikiTestCase { ) ); } - - } diff --git a/tests/phpunit/includes/import/ImportLinkCacheIntegrationTest.php b/tests/phpunit/includes/import/ImportLinkCacheIntegrationTest.php new file mode 100644 index 0000000000..5e3c6268aa --- /dev/null +++ b/tests/phpunit/includes/import/ImportLinkCacheIntegrationTest.php @@ -0,0 +1,112 @@ +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/import/ImportTest.php b/tests/phpunit/includes/import/ImportTest.php new file mode 100644 index 0000000000..f4aac235a2 --- /dev/null +++ b/tests/phpunit/includes/import/ImportTest.php @@ -0,0 +1,222 @@ + + */ +class ImportTest extends MediaWikiLangTestCase { + + private function getDataSource( $xml ) { + return new ImportStringSource( $xml ); + } + + /** + * @covers WikiImporter + * @dataProvider getUnknownTagsXML + * @param string $xml + * @param string $text + * @param string $title + */ + public function testUnknownXMLTags( $xml, $text, $title ) { + $source = $this->getDataSource( $xml ); + + $importer = new WikiImporter( + $source, + ConfigFactory::getDefaultInstance()->makeConfig( 'main' ) + ); + + $importer->doImport(); + $title = Title::newFromText( $title ); + $this->assertTrue( $title->exists() ); + + $this->assertEquals( WikiPage::factory( $title )->getContent()->getNativeData(), $text ); + } + + public function getUnknownTagsXML() { + // @codingStandardsIgnoreStart Generic.Files.LineLength + return array( + array( + <<< EOF + + + TestImportPage + Should be ignored + 0 + 14 + + 15 + Should be ignored + 2016-01-03T11:18:43Z + + Should be ignored + Admin + 1 + + wikitext + text/x-wiki + noitazinagro tseb eht si ikiWaideM + phoiac9h4m842xq45sp7s6u21eteeq1 + Should be ignored + + + Should be ignored + +EOF + , + 'noitazinagro tseb eht si ikiWaideM', + 'TestImportPage' + ) + ); + // @codingStandardsIgnoreEnd + } + + /** + * @covers WikiImporter::handlePage + * @dataProvider getRedirectXML + * @param string $xml + * @param string|null $redirectTitle + */ + public function testHandlePageContainsRedirect( $xml, $redirectTitle ) { + $source = $this->getDataSource( $xml ); + + $redirect = null; + $callback = function ( Title $title, ForeignTitle $foreignTitle, $revCount, + $sRevCount, $pageInfo ) use ( &$redirect ) { + if ( array_key_exists( 'redirect', $pageInfo ) ) { + $redirect = $pageInfo['redirect']; + } + }; + + $importer = new WikiImporter( + $source, + ConfigFactory::getDefaultInstance()->makeConfig( 'main' ) + ); + $importer->setPageOutCallback( $callback ); + $importer->doImport(); + + $this->assertEquals( $redirectTitle, $redirect ); + } + + public function getRedirectXML() { + // @codingStandardsIgnoreStart Generic.Files.LineLength + return array( + array( + <<< EOF + + + Test + 0 + 21 + + + 20 + 2014-05-27T10:00:00Z + + Admin + 10 + + Admin moved page [[Test]] to [[Test22]] + wikitext + text/x-wiki + #REDIRECT [[Test22]] + tq456o9x3abm7r9ozi6km8yrbbc56o6 + + + +EOF + , + 'Test22' + ), + array( + <<< EOF + + + Test + 0 + 42 + + 421 + 2014-05-27T11:00:00Z + + Admin + 10 + + Abcd + n7uomjq96szt60fy5w3x7ahf7q8m8rh + wikitext + text/x-wiki + + + +EOF + , + null + ), + ); + // @codingStandardsIgnoreEnd + } + + /** + * @covers WikiImporter::handleSiteInfo + * @dataProvider getSiteInfoXML + * @param string $xml + * @param array|null $namespaces + */ + public function testSiteInfoContainsNamespaces( $xml, $namespaces ) { + $source = $this->getDataSource( $xml ); + + $importNamespaces = null; + $callback = function ( array $siteinfo, $innerImporter ) use ( &$importNamespaces ) { + $importNamespaces = $siteinfo['_namespaces']; + }; + + $importer = new WikiImporter( + $source, + ConfigFactory::getDefaultInstance()->makeConfig( 'main' ) + ); + $importer->setSiteInfoCallback( $callback ); + $importer->doImport(); + + $this->assertEquals( $importNamespaces, $namespaces ); + } + + public function getSiteInfoXML() { + // @codingStandardsIgnoreStart Generic.Files.LineLength + return array( + array( + <<< EOF + + + + Media + Special + + Talk + User + User talk + Portal + Portal talk + + + +EOF + , + array( + '-2' => 'Media', + '-1' => 'Special', + '0' => '', + '1' => 'Talk', + '2' => 'User', + '3' => 'User talk', + '100' => 'Portal', + '101' => 'Portal talk', + ) + ), + ); + // @codingStandardsIgnoreEnd + } + +} diff --git a/tests/phpunit/includes/libs/MemoizedCallableTest.php b/tests/phpunit/includes/libs/MemoizedCallableTest.php index 921bba8466..6edb3d84d6 100644 --- a/tests/phpunit/includes/libs/MemoizedCallableTest.php +++ b/tests/phpunit/includes/libs/MemoizedCallableTest.php @@ -20,7 +20,6 @@ class ArrayBackedMemoizedCallable extends MemoizedCallable { } } - /** * PHP Unit tests for MemoizedCallable class. * @covers MemoizedCallable diff --git a/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php b/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php index 94b74cb651..b9fd6ab81f 100644 --- a/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php +++ b/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php @@ -183,6 +183,18 @@ class BagOStuffTest extends MediaWikiTestCase { $this->assertEquals( $expectedValue, $actualValue, 'Value should be 1 after incrementing' ); } + /** + * @covers BagOStuff::incrWithInit + */ + public function testIncrWithInit() { + $key = wfMemcKey( 'test' ); + $val = $this->cache->incrWithInit( $key, 0, 1, 3 ); + $this->assertEquals( 3, $val, "Correct init value" ); + + $val = $this->cache->incrWithInit( $key, 0, 1, 3 ); + $this->assertEquals( 4, $val, "Correct init value" ); + } + /** * @covers BagOStuff::getMulti */ diff --git a/tests/phpunit/includes/logging/ProtectLogFormatterTest.php b/tests/phpunit/includes/logging/ProtectLogFormatterTest.php index 17decf36fa..8010b77383 100644 --- a/tests/phpunit/includes/logging/ProtectLogFormatterTest.php +++ b/tests/phpunit/includes/logging/ProtectLogFormatterTest.php @@ -160,7 +160,6 @@ class ProtectLogFormatterTest extends LogFormatterTestCase { ); } - /** * @dataProvider provideProtectLogDatabaseRows */ @@ -329,7 +328,6 @@ class ProtectLogFormatterTest extends LogFormatterTestCase { ); } - /** * @dataProvider provideModifyLogDatabaseRows */ @@ -362,7 +360,6 @@ class ProtectLogFormatterTest extends LogFormatterTestCase { ); } - /** * @dataProvider provideUnprotectLogDatabaseRows */ diff --git a/tests/phpunit/includes/media/MediaWikiMediaTestCase.php b/tests/phpunit/includes/media/MediaWikiMediaTestCase.php index 8f28158dcd..cb10be3d27 100644 --- a/tests/phpunit/includes/media/MediaWikiMediaTestCase.php +++ b/tests/phpunit/includes/media/MediaWikiMediaTestCase.php @@ -11,7 +11,6 @@ abstract class MediaWikiMediaTestCase extends MediaWikiTestCase { /** @var string */ protected $filePath; - protected function setUp() { parent::setUp(); diff --git a/tests/phpunit/includes/media/XCFTest.php b/tests/phpunit/includes/media/XCFTest.php index 5b2de151f2..536827ae3a 100644 --- a/tests/phpunit/includes/media/XCFTest.php +++ b/tests/phpunit/includes/media/XCFTest.php @@ -13,7 +13,6 @@ class XCFHandlerTest extends MediaWikiMediaTestCase { $this->handler = new XCFHandler(); } - /** * @param string $filename * @param int $expectedWidth Width diff --git a/tests/phpunit/includes/page/WikiPageTest.php b/tests/phpunit/includes/page/WikiPageTest.php index 0a46f8a18c..002e86f534 100644 --- a/tests/phpunit/includes/page/WikiPageTest.php +++ b/tests/phpunit/includes/page/WikiPageTest.php @@ -1261,22 +1261,6 @@ more stuff ); } - /** - * @dataProvider providePreSaveTransform - * @covers WikiPage::preSaveTransform - */ - public function testPreSaveTransform( $text, $expected ) { - $this->hideDeprecated( 'WikiPage::preSaveTransform' ); - $user = new User(); - $user->setName( "127.0.0.1" ); - - // NOTE: assume Help namespace to contain wikitext - $page = $this->newPage( "Help:WikiPageTest_testPreloadTransform" ); - $text = $page->preSaveTransform( $text, $user ); - - $this->assertEquals( $expected, $text ); - } - /** * @covers WikiPage::factory */ diff --git a/tests/phpunit/includes/registration/ExtensionProcessorTest.php b/tests/phpunit/includes/registration/ExtensionProcessorTest.php index ddf552ef0d..590644fc06 100644 --- a/tests/phpunit/includes/registration/ExtensionProcessorTest.php +++ b/tests/phpunit/includes/registration/ExtensionProcessorTest.php @@ -118,7 +118,8 @@ class ExtensionProcessorTest extends MediaWikiTestCase { '_prefix' => 'eg', 'Bar' => 'somevalue' ), - ) + self::$default; + 'name' => 'FooBar2', + ); $processor->extractInfo( $this->dir, $info, 1 ); $processor->extractInfo( $this->dir, $info2, 1 ); $extracted = $processor->getExtractedInfo(); @@ -166,7 +167,6 @@ class ExtensionProcessorTest extends MediaWikiTestCase { } } - public static function provideExtractMessagesDirs() { $dir = __DIR__ . '/FooBar/'; return array( @@ -194,6 +194,16 @@ class ExtensionProcessorTest extends MediaWikiTestCase { } } + /** + * @covers ExtensionProcessor::extractCredits + */ + public function testExtractCredits() { + $processor = new ExtensionProcessor(); + $processor->extractInfo( $this->dir, self::$default, 1 ); + $this->setExpectedException( 'Exception' ); + $processor->extractInfo( $this->dir, self::$default, 1 ); + } + /** * @covers ExtensionProcessor::extractResourceLoaderModules * @dataProvider provideExtractResourceLoaderModules @@ -400,7 +410,6 @@ class ExtensionProcessorTest extends MediaWikiTestCase { } } - /** * Allow overriding the default value of $this->globals * so we can test merging diff --git a/tests/phpunit/includes/registration/ExtensionRegistryTest.php b/tests/phpunit/includes/registration/ExtensionRegistryTest.php index 201cbfcdf1..543eb5c78d 100644 --- a/tests/phpunit/includes/registration/ExtensionRegistryTest.php +++ b/tests/phpunit/includes/registration/ExtensionRegistryTest.php @@ -25,6 +25,7 @@ class ExtensionRegistryTest extends MediaWikiTestCase { 'defines' => array(), 'credits' => array(), 'attributes' => array(), + 'autoloaderPaths' => array() ); $registry = new ExtensionRegistry(); $class = new ReflectionClass( 'ExtensionRegistry' ); diff --git a/tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php b/tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php new file mode 100644 index 0000000000..163c52d016 --- /dev/null +++ b/tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php @@ -0,0 +1,85 @@ + 3 ), + __METHOD__ + ); + + if ( $res === false || strpos( $res, '"sitename":"Wikidata"' ) === false ) { + $connectivity = false; + } else { + $connectivity = true; + } + } + + if ( !$connectivity ) { + $this->markTestSkipped( 'MediaWikiPageNameNormalizerTest needs internet connectivity.' ); + } + } + + /** + * @dataProvider normalizePageTitleProvider + */ + public function testNormalizePageTitle( $expected, $pageName ) { + $normalizer = new MediaWikiPageNameNormalizer(); + + $this->assertSame( + $expected, + $normalizer->normalizePageName( $pageName, 'https://www.wikidata.org/w/api.php' ) + ); + } + + public function normalizePageTitleProvider() { + // Note: This makes (very conservative) assumptions about pages on Wikidata + // existing or not. + return array( + 'universe (Q1)' => array( + 'Q1', 'Q1' + ), + 'Q404 redirects to Q395' => array( + 'Q395', 'Q404' + ), + 'there is no Q0' => array( + false, 'Q0' + ) + ); + } + +} diff --git a/tests/phpunit/includes/specials/SpecialSearchTest.php b/tests/phpunit/includes/specials/SpecialSearchTest.php index df9b55232f..f32eeca57e 100644 --- a/tests/phpunit/includes/specials/SpecialSearchTest.php +++ b/tests/phpunit/includes/specials/SpecialSearchTest.php @@ -181,6 +181,7 @@ class SpecialSearchTest extends MediaWikiTestCase { ->will( $this->returnValue( $mockSearchEngine ) ); $search->getContext()->setTitle( Title::makeTitle( NS_SPECIAL, 'Search' ) ); + $search->getContext()->setLanguage( Language::factory( 'en' ) ); $search->load(); $search->showResults( 'this is a fake search' ); diff --git a/tests/phpunit/includes/upload/UploadBaseTest.php b/tests/phpunit/includes/upload/UploadBaseTest.php index 9ec1b4667b..90051ee11b 100644 --- a/tests/phpunit/includes/upload/UploadBaseTest.php +++ b/tests/phpunit/includes/upload/UploadBaseTest.php @@ -125,7 +125,6 @@ class UploadBaseTest extends MediaWikiTestCase { ); } - /** * @dataProvider provideCheckSvgScriptCallback */ diff --git a/tests/phpunit/includes/utils/BatchRowUpdateTest.php b/tests/phpunit/includes/utils/BatchRowUpdateTest.php index d224af8ed0..61d9a70a15 100644 --- a/tests/phpunit/includes/utils/BatchRowUpdateTest.php +++ b/tests/phpunit/includes/utils/BatchRowUpdateTest.php @@ -221,7 +221,6 @@ class BatchRowUpdateTest extends MediaWikiTestCase { return call_user_func_array( array( $this, 'onConsecutiveCalls' ), $retvals ); } - protected function genSelectResult( $batchSize, $numRows, $rowGenerator ) { $res = array(); for ( $i = 0; $i < $numRows; $i += $batchSize ) { diff --git a/tests/phpunit/includes/utils/IPTest.php b/tests/phpunit/includes/utils/IPTest.php index 04b8f48604..369e38bb1b 100644 --- a/tests/phpunit/includes/utils/IPTest.php +++ b/tests/phpunit/includes/utils/IPTest.php @@ -307,12 +307,34 @@ class IPTest extends PHPUnit_Framework_TestCase { } /** - * Improve IP::sanitizeIP() code coverage - * @todo Most probably incomplete + * @covers IP::sanitizeIP + * @dataProvider provideSanitizeIP */ - public function testSanitizeIP() { - $this->assertNull( IP::sanitizeIP( '' ) ); - $this->assertNull( IP::sanitizeIP( ' ' ) ); + public function testSanitizeIP( $expected, $input ) { + $result = IP::sanitizeIP( $input ); + $this->assertEquals( $expected, $result ); + } + + /** + * Provider for IP::testSanitizeIP() + */ + public static function provideSanitizeIP() { + return array( + array( '0.0.0.0', '0.0.0.0' ), + array( '0.0.0.0', '00.00.00.00' ), + array( '0.0.0.0', '000.000.000.000' ), + array( '141.0.11.253', '141.000.011.253' ), + array( '1.2.4.5', '1.2.4.5' ), + array( '1.2.4.5', '01.02.04.05' ), + array( '1.2.4.5', '001.002.004.005' ), + array( '10.0.0.1', '010.0.000.1' ), + array( '80.72.250.4', '080.072.250.04' ), + array( 'Foo.1000.00', 'Foo.1000.00' ), + array( 'Bar.01', 'Bar.01' ), + array( 'Bar.010', 'Bar.010' ), + array( null, '' ), + array( null, ' ' ) + ); } /** @@ -336,6 +358,7 @@ class IPTest extends PHPUnit_Framework_TestCase { array( '80000000', '128.0.0.0' ), array( 'DEADCAFE', '222.173.202.254' ), array( 'FFFFFFFF', '255.255.255.255' ), + array( '8D000BFD', '141.000.11.253' ), array( false, 'IN.VA.LI.D' ), array( 'v6-00000000000000000000000000000001', '::1' ), array( 'v6-20010DB885A3000000008A2E03707334', '2001:0db8:85a3:0000:0000:8a2e:0370:7334' ), diff --git a/tests/phpunit/includes/utils/MWCryptHKDFTest.php b/tests/phpunit/includes/utils/MWCryptHKDFTest.php index 2c51af3ce9..5dc049831b 100644 --- a/tests/phpunit/includes/utils/MWCryptHKDFTest.php +++ b/tests/phpunit/includes/utils/MWCryptHKDFTest.php @@ -91,6 +91,4 @@ class MWCryptHKDFTest extends MediaWikiTestCase { ); // @codingStandardsIgnoreEnd } - - } diff --git a/tests/phpunit/maintenance/MaintenanceTest.php b/tests/phpunit/maintenance/MaintenanceTest.php index 5c6a6cde85..245a97af17 100644 --- a/tests/phpunit/maintenance/MaintenanceTest.php +++ b/tests/phpunit/maintenance/MaintenanceTest.php @@ -120,6 +120,16 @@ class MaintenanceFixup extends Maintenance { return call_user_func_array( array( "parent", __FUNCTION__ ), func_get_args() ); } + public function addOption( $name, $description, $required = false, + $withArg = false, $shortName = false, $multiOccurance = false + ) { + return call_user_func_array( array( "parent", __FUNCTION__ ), func_get_args() ); + } + + public function getOption( $name, $default = null ) { + return call_user_func_array( array( "parent", __FUNCTION__ ), func_get_args() ); + } + // --- Requirements for getting instance of abstract class public function execute() { @@ -829,4 +839,37 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->setConfig( $conf ); $this->assertSame( $conf, $this->m->getConfig() ); } + + function testParseArgs() { + $m2 = new MaintenanceFixup( $this ); + // Create an option with an argument allowed to be specified multiple times + $m2->addOption( 'multi', 'This option does stuff', false, true, false, true ); + $m2->loadWithArgv( array( '--multi', 'this1', '--multi', 'this2' ) ); + + $this->assertEquals( array( 'this1', 'this2' ), $m2->getOption( 'multi' ) ); + $this->assertEquals( array( array( 'multi', 'this1' ), array( 'multi', 'this2' ) ), + $m2->orderedOptions ); + + $m2->simulateShutdown(); + + $m2 = new MaintenanceFixup( $this ); + + $m2->addOption( 'multi', 'This option does stuff', false, false, false, true ); + $m2->loadWithArgv( array( '--multi', '--multi' ) ); + + $this->assertEquals( array( 1, 1 ), $m2->getOption( 'multi' ) ); + $this->assertEquals( array( array( 'multi', 1 ), array( 'multi', 1 ) ), $m2->orderedOptions ); + + $m2->simulateShutdown(); + + $m2 = new MaintenanceFixup( $this ); + // Create an option with an argument allowed to be specified multiple times + $m2->addOption( 'multi', 'This option doesn\'t actually support multiple occurrences' ); + $m2->loadWithArgv( array( '--multi=yo' ) ); + + $this->assertEquals( 'yo', $m2->getOption( 'multi' ) ); + $this->assertEquals( array( array( 'multi', 'yo' ) ), $m2->orderedOptions ); + + $m2->simulateShutdown(); + } } diff --git a/tests/phpunit/maintenance/backupTextPassTest.php b/tests/phpunit/maintenance/backupTextPassTest.php index f5dd98b3fb..893e4f90f3 100644 --- a/tests/phpunit/maintenance/backupTextPassTest.php +++ b/tests/phpunit/maintenance/backupTextPassTest.php @@ -1,10 +1,15 @@ setUpStub(); $nameFull = $this->getNewTempFile(); - $dumper = new TextPassDumper( array( "--stub=file:" - . $nameStub, "--output=file:" . $nameFull ) ); + + $dumper = new TextPassDumper( array( "--stub=file:" . $nameStub, + "--output=file:" . $nameFull ) ); + $dumper->prefetch = $prefetchMock; $dumper->reporting = false; $dumper->setDb( $this->db ); @@ -261,7 +268,8 @@ class TextPassDumperDatabaseTest extends DumpTestCase { $this->assertTrue( wfMkdirParents( $nameOutputDir ), "Creating temporary output directory " ); $this->setUpStub( $nameStub, $iterations ); - $dumper = new TextPassDumper( array( "--stub=file:" . $nameStub, + $dumper = new TextPassDumper(); + $dumper->loadWithArgv( array( "--stub=file:" . $nameStub, "--output=" . $checkpointFormat . ":" . $nameOutputDir . "/full", "--maxtime=1" /*This is in minutes. Fixup is below*/, "--buffersize=32768", // The default of 32 iterations fill up 32KB about twice @@ -272,7 +280,7 @@ class TextPassDumperDatabaseTest extends DumpTestCase { // The actual dump and taking time $ts_before = microtime( true ); - $dumper->dump( WikiExporter::FULL, WikiExporter::TEXT ); + $dumper->execute(); $ts_after = microtime( true ); $lastDuration = $ts_after - $ts_before; @@ -634,7 +642,9 @@ class TextPassDumperDatabaselessTest extends MediaWikiLangTestCase { * @dataProvider bufferSizeProvider */ function testBufferSizeSetting( $expected, $size, $msg ) { - $dumper = new TextPassDumperAccessor( array( "--buffersize=" . $size ) ); + $dumper = new TextPassDumperAccessor(); + $dumper->loadWithArgv( array( "--buffersize=" . $size ) ); + $dumper->execute(); $this->assertEquals( $expected, $dumper->getBufferSize(), $msg ); } @@ -674,4 +684,8 @@ class TextPassDumperAccessor extends TextPassDumper { public function getBufferSize() { return $this->bufferSize; } + + function dump( $history, $text = null ) { + return true; + } } diff --git a/tests/phpunit/maintenance/backup_LogTest.php b/tests/phpunit/maintenance/backup_LogTest.php index 7ca45960c5..6629b67dba 100644 --- a/tests/phpunit/maintenance/backup_LogTest.php +++ b/tests/phpunit/maintenance/backup_LogTest.php @@ -2,6 +2,11 @@ /** * Tests for log dumps of BackupDumper * + * Some of these tests use the old constuctor for TextPassDumper + * and the dump() function, while others use the new loadWithArgv( $args ) + * function and execute(). This is to ensure both the old and new methods + * work properly. + * * @group Database * @group Dump * @covers BackupDumper @@ -136,7 +141,8 @@ class BackupDumperLoggerTest extends DumpTestCase { // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array( "--output=file:" . $fname ) ); + + $dumper = new DumpBackup( array( '--output=file:' . $fname ) ); $dumper->startId = $this->logId1; $dumper->endId = $this->logId3 + 1; $dumper->reporting = false; @@ -173,8 +179,10 @@ class BackupDumperLoggerTest extends DumpTestCase { // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array( "--output=gzip:" . $fname, - "--reporting=2" ) ); + + $dumper = new DumpBackup(); + $dumper->loadWithArgv( array( '--logs', '--output=gzip:' . $fname, + '--reporting=2' ) ); $dumper->startId = $this->logId1; $dumper->endId = $this->logId3 + 1; $dumper->setDb( $this->db ); @@ -190,7 +198,7 @@ class BackupDumperLoggerTest extends DumpTestCase { } // Performing the dump - $dumper->dump( WikiExporter::LOGS, WikiExporter::TEXT ); + $dumper->execute(); $this->assertTrue( fclose( $dumper->stderr ), "Closing stderr handle" ); diff --git a/tests/phpunit/maintenance/backup_PageTest.php b/tests/phpunit/maintenance/backup_PageTest.php index 8b6221ba92..5781d1c6d5 100644 --- a/tests/phpunit/maintenance/backup_PageTest.php +++ b/tests/phpunit/maintenance/backup_PageTest.php @@ -6,6 +6,7 @@ * @group Dump * @covers BackupDumper */ + class BackupDumperPageTest extends DumpTestCase { // We'll add several pages, revision and texts. The following variables hold the @@ -98,14 +99,15 @@ class BackupDumperPageTest extends DumpTestCase { function testFullTextPlain() { // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array( "--output=file:" . $fname ) ); + + $dumper = new DumpBackup(); + $dumper->loadWithArgv( array( '--full', '--quiet', '--output', 'file:' . $fname ) ); $dumper->startId = $this->pageId1; $dumper->endId = $this->pageId4 + 1; - $dumper->reporting = false; $dumper->setDb( $this->db ); // Performing the dump - $dumper->dump( WikiExporter::FULL, WikiExporter::TEXT ); + $dumper->execute(); // Checking the dumped data $this->assertDumpStart( $fname ); @@ -153,14 +155,15 @@ class BackupDumperPageTest extends DumpTestCase { function testFullStubPlain() { // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array( "--output=file:" . $fname ) ); + + $dumper = new DumpBackup(); + $dumper->loadWithArgv( array( '--full', '--quiet', '--output', 'file:' . $fname, '--stub' ) ); $dumper->startId = $this->pageId1; $dumper->endId = $this->pageId4 + 1; - $dumper->reporting = false; $dumper->setDb( $this->db ); // Performing the dump - $dumper->dump( WikiExporter::FULL, WikiExporter::STUB ); + $dumper->execute(); // Checking the dumped data $this->assertDumpStart( $fname ); @@ -202,7 +205,8 @@ class BackupDumperPageTest extends DumpTestCase { function testCurrentStubPlain() { // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array( "--output=file:" . $fname ) ); + + $dumper = new DumpBackup( array( '--output', 'file:' . $fname ) ); $dumper->startId = $this->pageId1; $dumper->endId = $this->pageId4 + 1; $dumper->reporting = false; @@ -247,7 +251,8 @@ class BackupDumperPageTest extends DumpTestCase { // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array( "--output=gzip:" . $fname ) ); + + $dumper = new DumpBackup( array( '--output', 'gzip:' . $fname ) ); $dumper->startId = $this->pageId1; $dumper->endId = $this->pageId4 + 1; $dumper->reporting = false; @@ -306,7 +311,7 @@ class BackupDumperPageTest extends DumpTestCase { $fnameMetaCurrent = $this->getNewTempFile(); $fnameArticles = $this->getNewTempFile(); - $dumper = new BackupDumper( array( "--output=gzip:" . $fnameMetaHistory, + $dumper = new DumpBackup( array( "--full", "--stub", "--output=gzip:" . $fnameMetaHistory, "--output=gzip:" . $fnameMetaCurrent, "--filter=latest", "--output=gzip:" . $fnameArticles, "--filter=latest", "--filter=notalk", "--filter=namespace:!NS_USER", diff --git a/tests/phpunit/phpunit.php b/tests/phpunit/phpunit.php index aaa77514f6..09dc931df9 100755 --- a/tests/phpunit/phpunit.php +++ b/tests/phpunit/phpunit.php @@ -242,7 +242,6 @@ if ( version_compare( PHP_VERSION, '5.4.0', '<' ) ) { } ); } - $ok = false; if ( class_exists( 'PHPUnit_TextUI_Command' ) ) { @@ -251,12 +250,19 @@ if ( class_exists( 'PHPUnit_TextUI_Command' ) ) { } else { foreach ( array( stream_resolve_include_path( 'phpunit.phar' ), + stream_resolve_include_path( 'phpunit-old.phar' ), 'PHPUnit/Runner/Version.php', 'PHPUnit/Autoload.php' ) as $includePath ) { - // @codingStandardsIgnoreStart - @include_once $includePath; - // @codingStandardsIgnoreEnd + + if ( $includePath === false ) { + // stream_resolve_include_path can return false + continue; + } + + \MediaWiki\suppressWarnings(); + include_once $includePath; + \MediaWiki\restoreWarnings(); if ( class_exists( 'PHPUnit_TextUI_Command' ) ) { $ok = true; echo "Using PHPUnit from $includePath\n"; diff --git a/tests/phpunit/tests/MediaWikiTestCaseTest.php b/tests/phpunit/tests/MediaWikiTestCaseTest.php index 2846fde000..64def9125a 100644 --- a/tests/phpunit/tests/MediaWikiTestCaseTest.php +++ b/tests/phpunit/tests/MediaWikiTestCaseTest.php @@ -6,58 +6,81 @@ */ class MediaWikiTestCaseTest extends MediaWikiTestCase { - const GLOBAL_KEY_EXISTING = 'MediaWikiTestCaseTestGLOBAL-Existing'; const GLOBAL_KEY_NONEXISTING = 'MediaWikiTestCaseTestGLOBAL-NONExisting'; + private static $startGlobals = array( + 'MediaWikiTestCaseTestGLOBAL-ExistingString' => 'foo', + 'MediaWikiTestCaseTestGLOBAL-ExistingStringEmpty' => '', + 'MediaWikiTestCaseTestGLOBAL-ExistingArray' => array( 1, 'foo' => 'bar' ), + 'MediaWikiTestCaseTestGLOBAL-ExistingArrayEmpty' => array(), + ); + public static function setUpBeforeClass() { parent::setUpBeforeClass(); - $GLOBALS[self::GLOBAL_KEY_EXISTING] = 'foo'; + foreach ( self::$startGlobals as $key => $value ) { + $GLOBALS[$key] = $value; + } } public static function tearDownAfterClass() { parent::tearDownAfterClass(); - unset( $GLOBALS[self::GLOBAL_KEY_EXISTING] ); + foreach ( self::$startGlobals as $key => $value ) { + unset( $GLOBALS[$key] ); + } + } + + public function provideExistingKeysAndNewValues() { + $providedArray = array(); + foreach ( array_keys( self::$startGlobals ) as $key ) { + $providedArray[] = array( $key, 'newValue' ); + $providedArray[] = array( $key, array( 'newValue' ) ); + } + return $providedArray; } /** + * @dataProvider provideExistingKeysAndNewValues + * * @covers MediaWikiTestCase::setMwGlobals * @covers MediaWikiTestCase::tearDown */ - public function testSetGlobalsAreRestoredOnTearDown() { - $this->setMwGlobals( self::GLOBAL_KEY_EXISTING, 'bar' ); + public function testSetGlobalsAreRestoredOnTearDown( $globalKey, $newValue ) { + $this->setMwGlobals( $globalKey, $newValue ); $this->assertEquals( - 'bar', - $GLOBALS[self::GLOBAL_KEY_EXISTING], + $newValue, + $GLOBALS[$globalKey], 'Global failed to correctly set' ); $this->tearDown(); $this->assertEquals( - 'foo', - $GLOBALS[self::GLOBAL_KEY_EXISTING], + self::$startGlobals[$globalKey], + $GLOBALS[$globalKey], 'Global failed to be restored on tearDown' ); } /** + * @dataProvider provideExistingKeysAndNewValues + * * @covers MediaWikiTestCase::stashMwGlobals * @covers MediaWikiTestCase::tearDown */ - public function testStashedGlobalsAreRestoredOnTearDown() { - $this->stashMwGlobals( self::GLOBAL_KEY_EXISTING ); - $GLOBALS[self::GLOBAL_KEY_EXISTING] = 'bar'; + public function testStashedGlobalsAreRestoredOnTearDown( $globalKey, $newValue ) { + $this->stashMwGlobals( $globalKey ); + $GLOBALS[$globalKey] = $newValue; $this->assertEquals( - 'bar', - $GLOBALS[self::GLOBAL_KEY_EXISTING], + $newValue, + $GLOBALS[$globalKey], 'Global failed to correctly set' ); $this->tearDown(); $this->assertEquals( - 'foo', - $GLOBALS[self::GLOBAL_KEY_EXISTING], + self::$startGlobals[$globalKey], + $GLOBALS[$globalKey], 'Global failed to be restored on tearDown' ); } diff --git a/tests/qunit/QUnitTestResources.php b/tests/qunit/QUnitTestResources.php index 545718a66e..926e986d6b 100644 --- a/tests/qunit/QUnitTestResources.php +++ b/tests/qunit/QUnitTestResources.php @@ -71,6 +71,7 @@ return array( '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.template.mustache.test.js', 'tests/qunit/suites/resources/mediawiki/mediawiki.test.js', 'tests/qunit/suites/resources/mediawiki/mediawiki.html.test.js', 'tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js', @@ -126,6 +127,7 @@ return array( 'mediawiki.toc', 'mediawiki.Uri', 'mediawiki.user', + 'mediawiki.template.mustache', 'mediawiki.template', 'mediawiki.util', 'mediawiki.special.recentchanges', diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js index 4bcb12e6ae..2e63b7a67b 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js @@ -332,7 +332,7 @@ } ); - QUnit.test( 'getUrl', 3, function ( assert ) { + QUnit.test( 'getUrl', 4, function ( assert ) { var title; // Config @@ -344,6 +344,9 @@ title = new mw.Title( 'John Doe', 3 ); assert.equal( title.getUrl(), '/wiki/User_talk:John_Doe', 'Escaping in title and namespace for urls' ); + + title = new mw.Title( 'John Cena#And_His_Name_Is', 3 ); + assert.equal( title.getUrl( { meme: true } ), '/wiki/User_talk:John_Cena?meme=true#And_His_Name_Is', 'title with fragment and query parameter' ); } ); QUnit.test( 'newFromImg', 44, function ( assert ) { diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js index 53a714f81c..43b324edb5 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js @@ -333,6 +333,29 @@ mw.user.options.set( 'gender', originalGender ); } ); + QUnit.test( 'Case changing', 8, function ( assert ) { + mw.messages.set( 'to-lowercase', '{{lc:thIS hAS MEsSed uP CapItaliZatiON}}' ); + assert.equal( formatParse( 'to-lowercase' ), 'this has messed up capitalization', 'To lowercase' ); + + mw.messages.set( 'to-caps', '{{uc:thIS hAS MEsSed uP CapItaliZatiON}}' ); + assert.equal( formatParse( 'to-caps' ), 'THIS HAS MESSED UP CAPITALIZATION', 'To caps' ); + + mw.messages.set( 'uc-to-lcfirst', '{{lcfirst:THis hAS MEsSed uP CapItaliZatiON}}' ); + mw.messages.set( 'lc-to-lcfirst', '{{lcfirst:thIS hAS MEsSed uP CapItaliZatiON}}' ); + assert.equal( formatParse( 'uc-to-lcfirst' ), 'tHis hAS MEsSed uP CapItaliZatiON', 'Lcfirst caps' ); + assert.equal( formatParse( 'lc-to-lcfirst' ), 'thIS hAS MEsSed uP CapItaliZatiON', 'Lcfirst lowercase' ); + + mw.messages.set( 'uc-to-ucfirst', '{{ucfirst:THis hAS MEsSed uP CapItaliZatiON}}' ); + mw.messages.set( 'lc-to-ucfirst', '{{ucfirst:thIS hAS MEsSed uP CapItaliZatiON}}' ); + assert.equal( formatParse( 'uc-to-ucfirst' ), 'THis hAS MEsSed uP CapItaliZatiON', 'Ucfirst caps' ); + assert.equal( formatParse( 'lc-to-ucfirst' ), 'ThIS hAS MEsSed uP CapItaliZatiON', 'Ucfirst lowercase' ); + + mw.messages.set( 'mixed-to-sentence', '{{ucfirst:{{lc:thIS hAS MEsSed uP CapItaliZatiON}}}}' ); + assert.equal( formatParse( 'mixed-to-sentence' ), 'This has messed up capitalization', 'To sentence case' ); + mw.messages.set( 'all-caps-except-first', '{{lcfirst:{{uc:thIS hAS MEsSed uP CapItaliZatiON}}}}' ); + assert.equal( formatParse( 'all-caps-except-first' ), 'tHIS HAS MESSED UP CAPITALIZATION', 'To opposite sentence case' ); + } ); + QUnit.test( 'Grammar', 2, function ( assert ) { assert.equal( formatParse( 'grammar-msg' ), 'Przeszukaj Wiki', 'Grammar Test with sitename' ); 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 288b527b64..b3c4beeb4e 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js @@ -21,7 +21,7 @@ function () { mw.messagePoster.factory.register( TEST_MODEL, testMessagePosterConstructor ); }, - new RegExp( 'The content model \'' + TEST_MODEL + '\' is already registered.' ), + new RegExp( 'Content model "' + TEST_MODEL + '" is already registered' ), 'Throws exception is same model is registered a second time' ); } ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.template.mustache.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.template.mustache.test.js new file mode 100644 index 0000000000..38ae5e490f --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.template.mustache.test.js @@ -0,0 +1,32 @@ +( function ( mw ) { + + QUnit.module( 'mediawiki.template.mustache', { + setup: function () { + // Stub register some templates + this.sandbox.stub( mw.templates, 'get' ).returns( { + 'test_greeting.mustache': '
            {{foo}}{{>suffix}}
            ', + 'test_greeting_suffix.mustache': ' goodbye' + } ); + } + } ); + + QUnit.test( 'render', 2, function ( assert ) { + var html, htmlPartial, data, partials, + template = mw.template.get( 'stub', 'test_greeting.mustache' ), + partial = mw.template.get( 'stub', 'test_greeting_suffix.mustache' ); + + data = { + foo: 'Hello' + }; + partials = { + suffix: partial + }; + + html = template.render( data ).html(); + htmlPartial = template.render( data, partials ).html(); + + assert.strictEqual( html, 'Hello', 'Render without partial' ); + assert.strictEqual( htmlPartial, 'Hello goodbye', 'Render with partial' ); + } ); + +}( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js index d40c00af12..5d72179560 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js @@ -92,6 +92,31 @@ assert.equal( mw.util.rawurlencode( 'Test:A & B/Here' ), 'Test%3AA%20%26%20B%2FHere' ); } ); + QUnit.test( 'escapeId', 17, function ( assert ) { + mw.config.set( 'wgExperimentalHtmlIds', false ); + $.each( { + '+': '.2B', + '&': '.26', + '=': '.3D', + ':': ':', + ';': '.3B', + '@': '.40', + $: '.24', + '-_.': '-_.', + '!': '.21', + '*': '.2A', + '/': '.2F', + '[]': '.5B.5D', + '<>': '.3C.3E', + '\'': '.27', + '§': '.C2.A7', + 'Test:A & B/Here': 'Test:A_.26_B.2FHere', + 'A&B&C&amp;D&amp;amp;E': 'A.26B.26amp.3BC.26amp.3Bamp.3BD.26amp.3Bamp.3Bamp.3BE' + }, function ( input, output ) { + assert.equal( mw.util.escapeId( input ), output ); + } ); + } ); + 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 @@ -111,10 +136,11 @@ } ); } ); - QUnit.test( 'getUrl', 5, function ( assert ) { - // Not part of startUp module - mw.config.set( 'wgArticlePath', '/wiki/$1' ); - mw.config.set( 'wgPageName', 'Foobar' ); + QUnit.test( 'getUrl', 12, function ( assert ) { + mw.config.set( { + wgArticlePath: '/wiki/$1', + wgPageName: 'Foobar' + } ); var href = mw.util.getUrl( 'Sandbox' ); assert.equal( href, '/wiki/Sandbox', 'simple title' ); @@ -130,12 +156,36 @@ href = mw.util.getUrl( 'Sandbox', { action: 'edit' } ); assert.equal( href, '/wiki/Sandbox?action=edit', 'simple title with query string' ); + + // Test fragments + href = mw.util.getUrl( 'Foo:Sandbox#Fragment', { action: 'edit' } ); + assert.equal( href, '/wiki/Foo:Sandbox?action=edit#Fragment', 'advanced title with query string and fragment' ); + + href = mw.util.getUrl( 'Foo:Sandbox#', { action: 'edit' } ); + assert.equal( href, '/wiki/Foo:Sandbox?action=edit', 'title with query string and empty fragment' ); + + href = mw.util.getUrl( '#Fragment' ); + assert.equal( href, '/wiki/#Fragment', 'epmty title with fragment' ); + + href = mw.util.getUrl( '#Fragment', { action: 'edit' } ); + assert.equal( href, '/wiki/?action=edit#Fragment', 'epmty title with query string and fragment' ); + + href = mw.util.getUrl( 'Foo:Sandbox \xC4#Fragment \xC4', { action: 'edit' } ); + assert.equal( href, '/wiki/Foo:Sandbox_%C3%84?action=edit#Fragment_.C3.84', 'title with query string, fragment, and special characters' ); + + href = mw.util.getUrl( 'Foo:%23#Fragment', { action: 'edit' } ); + assert.equal( href, '/wiki/Foo:%2523?action=edit#Fragment', 'title containing %23 (#), fragment, and a query string' ); + + href = mw.util.getUrl( '#+&=:;@$-_.!*/[]<>\'§', { action: 'edit' } ); + assert.equal( href, '/wiki/?action=edit#.2B.26.3D:.3B.40.24-_..21.2A.2F.5B.5D.3C.3E.27.C2.A7', 'fragment with various characters' ); } ); 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 + // customized wgScript for T41103 + wgScript: '/w/i.php', + // customized wgLoadScript for T41103 + wgLoadScript: '/w/l.php', wgScriptPath: '/w' } ); diff --git a/thumb.php b/thumb.php index fed025874b..04b3e42ac8 100644 --- a/thumb.php +++ b/thumb.php @@ -567,7 +567,6 @@ function wfExtractThumbParams( $file, $params ) { return null; } - /** * Output a thumbnail generation error message *