summaryrefslogtreecommitdiff
path: root/www/wiki/tests
diff options
context:
space:
mode:
authorYaco <franco@reevo.org>2019-07-31 14:50:50 -0300
committerYaco <franco@reevo.org>2019-07-31 14:50:50 -0300
commit3848848fc3bc2db035c824f1453635949505d76e (patch)
tree71fd898ebb220e7ba034cf2bc1bf708fdd0d6219 /www/wiki/tests
parent2dfe0b926fe5c6c4f27ad1f9bc1c1377cb091111 (diff)
ACTUALIZA MW a 1.31.3, SMW a 3.0.2 y extensiones menores
Diffstat (limited to 'www/wiki/tests')
-rw-r--r--www/wiki/tests/TestsAutoLoader.php153
-rw-r--r--www/wiki/tests/browser/ci.yml8
-rw-r--r--www/wiki/tests/browser/environments.yml39
-rw-r--r--www/wiki/tests/browser/features/create_account.feature17
-rw-r--r--www/wiki/tests/browser/features/create_and_follow_wiki_link.feature9
-rw-r--r--www/wiki/tests/browser/features/edit_page.feature11
-rw-r--r--www/wiki/tests/browser/features/file.feature11
-rw-r--r--www/wiki/tests/browser/features/login.feature30
-rw-r--r--www/wiki/tests/browser/features/main_page_links.feature19
-rw-r--r--www/wiki/tests/browser/features/preferences.feature47
-rw-r--r--www/wiki/tests/browser/features/step_definitions/create_account_steps.rb15
-rw-r--r--www/wiki/tests/browser/features/step_definitions/create_and_follow_wiki_link_steps.rb26
-rw-r--r--www/wiki/tests/browser/features/step_definitions/edit_page_steps.rb23
-rw-r--r--www/wiki/tests/browser/features/step_definitions/file_steps.rb7
-rw-r--r--www/wiki/tests/browser/features/step_definitions/login_steps.rb58
-rw-r--r--www/wiki/tests/browser/features/step_definitions/main_page_links_steps.rb47
-rw-r--r--www/wiki/tests/browser/features/step_definitions/preferences_appearance_steps.rb69
-rw-r--r--www/wiki/tests/browser/features/step_definitions/preferences_editing_steps.rb43
-rw-r--r--www/wiki/tests/browser/features/step_definitions/preferences_user_profile_steps.rb31
-rw-r--r--www/wiki/tests/browser/features/step_definitions/view_history_steps.rb7
-rw-r--r--www/wiki/tests/browser/features/support/env.rb3
-rw-r--r--www/wiki/tests/browser/features/support/hooks.rb2
-rw-r--r--www/wiki/tests/browser/features/support/pages/create_account_page.rb8
-rw-r--r--www/wiki/tests/browser/features/support/pages/edit_page.rb8
-rw-r--r--www/wiki/tests/browser/features/support/pages/file_does_not_exist_page.rb7
-rw-r--r--www/wiki/tests/browser/features/support/pages/login_error_page.rb5
-rw-r--r--www/wiki/tests/browser/features/support/pages/login_page.rb27
-rw-r--r--www/wiki/tests/browser/features/support/pages/main_page.rb18
-rw-r--r--www/wiki/tests/browser/features/support/pages/preferences_appearance_page.rb25
-rw-r--r--www/wiki/tests/browser/features/support/pages/preferences_editing_page.rb16
-rw-r--r--www/wiki/tests/browser/features/support/pages/preferences_page.rb10
-rw-r--r--www/wiki/tests/browser/features/support/pages/preferences_user_profile_page.rb16
-rw-r--r--www/wiki/tests/browser/features/support/pages/view_history_page.rb6
-rw-r--r--www/wiki/tests/browser/features/support/pages/ztargetpage.rb7
-rw-r--r--www/wiki/tests/browser/features/view_history.feature11
-rw-r--r--www/wiki/tests/common/TestSetup.php6
-rw-r--r--www/wiki/tests/common/TestsAutoLoader.php60
-rw-r--r--www/wiki/tests/integration/includes/http/CurlHttpRequestTest.php4
-rw-r--r--www/wiki/tests/integration/includes/http/MWHttpRequestTestCase.php8
-rw-r--r--www/wiki/tests/integration/includes/http/PhpHttpRequestTest.php4
-rw-r--r--www/wiki/tests/integration/includes/shell/FirejailCommandTest.php78
-rw-r--r--www/wiki/tests/parser/ParserTestParserHook.php3
-rw-r--r--www/wiki/tests/parser/ParserTestResultNormalizer.php4
-rw-r--r--www/wiki/tests/parser/ParserTestRunner.php86
-rw-r--r--www/wiki/tests/parser/PhpunitTestRecorder.php2
-rw-r--r--www/wiki/tests/parser/TestFileEditor.php24
-rw-r--r--www/wiki/tests/parser/TidySupport.php23
-rw-r--r--www/wiki/tests/parser/editTests.php6
-rw-r--r--www/wiki/tests/parser/fuzzTest.php4
-rw-r--r--www/wiki/tests/parser/parserTest.inc1666
-rw-r--r--www/wiki/tests/parser/parserTests.php5
-rw-r--r--www/wiki/tests/parser/parserTests.txt5796
-rw-r--r--www/wiki/tests/parser/parserTestsParserHook.php67
-rw-r--r--www/wiki/tests/parserTests.php93
-rw-r--r--www/wiki/tests/phan/config.php41
-rw-r--r--www/wiki/tests/phan/stubs/hhvm.php2
-rw-r--r--www/wiki/tests/phan/stubs/mail.php2
-rw-r--r--www/wiki/tests/phan/stubs/memcached.php2
-rw-r--r--www/wiki/tests/phan/stubs/phpunit4.php11
-rw-r--r--www/wiki/tests/phan/stubs/tideways.php2
-rw-r--r--www/wiki/tests/phan/stubs/wikidiff.php12
-rw-r--r--www/wiki/tests/phpunit/HamcrestPHPUnitIntegration.php34
-rw-r--r--www/wiki/tests/phpunit/MediaWikiCoversValidator.php50
-rw-r--r--www/wiki/tests/phpunit/MediaWikiPHPUnitTestListener.php2
-rw-r--r--www/wiki/tests/phpunit/MediaWikiTestCase.php392
-rw-r--r--www/wiki/tests/phpunit/PHPUnit4And6Compat.php121
-rw-r--r--www/wiki/tests/phpunit/ResourceLoaderTestCase.php7
-rw-r--r--www/wiki/tests/phpunit/autoload.ide.php11
-rw-r--r--www/wiki/tests/phpunit/data/categoriesrdf/categoriesRdf-out.nt21
-rw-r--r--www/wiki/tests/phpunit/data/composer/composer.json2
-rw-r--r--www/wiki/tests/phpunit/data/composer/composer.lock8
-rw-r--r--www/wiki/tests/phpunit/data/composer/installed.json1682
-rw-r--r--www/wiki/tests/phpunit/data/composer/new-composer.json2
-rw-r--r--www/wiki/tests/phpunit/data/cssmin/circle.svg5
-rw-r--r--www/wiki/tests/phpunit/data/db/sqlite/tables-1.19.sql531
-rw-r--r--www/wiki/tests/phpunit/data/db/sqlite/tables-1.20.sql534
-rw-r--r--www/wiki/tests/phpunit/data/db/sqlite/tables-1.21.sql577
-rw-r--r--www/wiki/tests/phpunit/data/db/sqlite/tables-1.22.sql575
-rw-r--r--www/wiki/tests/phpunit/data/db/sqlite/tables-1.23.sql580
-rw-r--r--www/wiki/tests/phpunit/data/helpers/WellProtectedClass.php59
-rw-r--r--www/wiki/tests/phpunit/data/localisationcache/uk.json3
-rw-r--r--www/wiki/tests/phpunit/data/media/README10
-rw-r--r--www/wiki/tests/phpunit/data/media/jpeg-segment-loop1.jpgbin0 -> 20 bytes
-rw-r--r--www/wiki/tests/phpunit/data/media/jpeg-segment-loop2.jpgbin0 -> 24 bytes
-rw-r--r--www/wiki/tests/phpunit/data/registration/bad_spdx.json6
-rw-r--r--www/wiki/tests/phpunit/data/registration/good.json4
-rw-r--r--www/wiki/tests/phpunit/data/registration/invalid.json5
-rw-r--r--www/wiki/tests/phpunit/data/registration/newer_manifest_version.json4
-rw-r--r--www/wiki/tests/phpunit/data/registration/no_manifest_version.json3
-rw-r--r--www/wiki/tests/phpunit/data/registration/notjson.txt1
-rw-r--r--www/wiki/tests/phpunit/data/registration/old_manifest_version.json4
-rw-r--r--www/wiki/tests/phpunit/data/resourceloader/add.gifbin74 -> 0 bytes
-rw-r--r--www/wiki/tests/phpunit/data/resourceloader/bold-a.svg6
-rw-r--r--www/wiki/tests/phpunit/data/resourceloader/bold-b.svg6
-rw-r--r--www/wiki/tests/phpunit/data/resourceloader/bold-f.svg6
-rw-r--r--www/wiki/tests/phpunit/data/resourceloader/help-ltr.svg10
-rw-r--r--www/wiki/tests/phpunit/data/resourceloader/help-rtl.svg10
-rw-r--r--www/wiki/tests/phpunit/data/resourceloader/next.svg4
-rw-r--r--www/wiki/tests/phpunit/data/resourceloader/next_massage.svg4
-rw-r--r--www/wiki/tests/phpunit/data/resourceloader/prev.svg4
-rw-r--r--www/wiki/tests/phpunit/data/resourceloader/remove.svg6
-rw-r--r--www/wiki/tests/phpunit/data/resourceloader/remove_variantize.svg6
-rw-r--r--www/wiki/tests/phpunit/data/templates/conds.mustache1
-rw-r--r--www/wiki/tests/phpunit/includes/ActorMigrationTest.php695
-rw-r--r--www/wiki/tests/phpunit/includes/AutopromoteTest.php5
-rw-r--r--www/wiki/tests/phpunit/includes/BlockTest.php118
-rw-r--r--www/wiki/tests/phpunit/includes/CollationTest.php117
-rw-r--r--www/wiki/tests/phpunit/includes/CommentStoreTest.php203
-rw-r--r--www/wiki/tests/phpunit/includes/EditPageTest.php12
-rw-r--r--www/wiki/tests/phpunit/includes/ExtraParserTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/FauxRequestTest.php16
-rw-r--r--www/wiki/tests/phpunit/includes/GitInfoTest.php56
-rw-r--r--www/wiki/tests/phpunit/includes/GlobalFunctions/GlobalTest.php47
-rw-r--r--www/wiki/tests/phpunit/includes/GlobalFunctions/wfArrayFilterTest.php7
-rw-r--r--www/wiki/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php121
-rw-r--r--www/wiki/tests/phpunit/includes/GlobalFunctions/wfStringToBoolTest.php51
-rw-r--r--www/wiki/tests/phpunit/includes/HooksTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/HtmlTest.php60
-rw-r--r--www/wiki/tests/phpunit/includes/HttpTest.php534
-rw-r--r--www/wiki/tests/phpunit/includes/LicensesTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/LinkFilterTest.php1
-rw-r--r--www/wiki/tests/phpunit/includes/LinkerTest.php25
-rw-r--r--www/wiki/tests/phpunit/includes/ListToggleTest.php49
-rw-r--r--www/wiki/tests/phpunit/includes/MWNamespaceTest.php160
-rw-r--r--www/wiki/tests/phpunit/includes/MWTimestampTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/MediaWikiServicesTest.php31
-rw-r--r--www/wiki/tests/phpunit/includes/MediaWikiVersionFetcherTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/MergeHistoryTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/MessageTest.php17
-rw-r--r--www/wiki/tests/phpunit/includes/MimeMagicTest.php51
-rw-r--r--www/wiki/tests/phpunit/includes/OutputPageTest.php37
-rw-r--r--www/wiki/tests/phpunit/includes/PageArchiveTest.php167
-rw-r--r--www/wiki/tests/phpunit/includes/PagePropsTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/PathRouterTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/PreferencesTest.php82
-rw-r--r--www/wiki/tests/phpunit/includes/PrefixSearchTest.php11
-rw-r--r--www/wiki/tests/phpunit/includes/RevisionContentHandlerDbTest.php14
-rw-r--r--www/wiki/tests/phpunit/includes/RevisionDbTestBase.php1505
-rw-r--r--www/wiki/tests/phpunit/includes/RevisionNoContentHandlerDbTest.php14
-rw-r--r--www/wiki/tests/phpunit/includes/RevisionStorageTest.php574
-rw-r--r--www/wiki/tests/phpunit/includes/RevisionStorageTestContentHandlerUseDB.php89
-rw-r--r--www/wiki/tests/phpunit/includes/RevisionTest.php1583
-rw-r--r--www/wiki/tests/phpunit/includes/RevisionTestModifyableContent.php23
-rw-r--r--www/wiki/tests/phpunit/includes/RevisionTestModifyableContentHandler.php19
-rw-r--r--www/wiki/tests/phpunit/includes/SampleTest.php10
-rw-r--r--www/wiki/tests/phpunit/includes/SanitizerValidateEmailTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/SiteStatsTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/StatusTest.php35
-rw-r--r--www/wiki/tests/phpunit/includes/Storage/BlobStoreFactoryTest.php46
-rw-r--r--www/wiki/tests/phpunit/includes/Storage/MutableRevisionRecordTest.php212
-rw-r--r--www/wiki/tests/phpunit/includes/Storage/MutableRevisionSlotsTest.php76
-rw-r--r--www/wiki/tests/phpunit/includes/Storage/NameTableStoreTest.php298
-rw-r--r--www/wiki/tests/phpunit/includes/Storage/RevisionArchiveRecordTest.php272
-rw-r--r--www/wiki/tests/phpunit/includes/Storage/RevisionRecordTests.php512
-rw-r--r--www/wiki/tests/phpunit/includes/Storage/RevisionSlotsTest.php139
-rw-r--r--www/wiki/tests/phpunit/includes/Storage/RevisionStoreDbTest.php1281
-rw-r--r--www/wiki/tests/phpunit/includes/Storage/RevisionStoreRecordTest.php363
-rw-r--r--www/wiki/tests/phpunit/includes/Storage/RevisionStoreTest.php690
-rw-r--r--www/wiki/tests/phpunit/includes/Storage/SlotRecordTest.php298
-rw-r--r--www/wiki/tests/phpunit/includes/Storage/SqlBlobStoreTest.php241
-rw-r--r--www/wiki/tests/phpunit/includes/TemplateCategoriesTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/TemplateParserTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/TestUserRegistry.php15
-rw-r--r--www/wiki/tests/phpunit/includes/TestingAccessWrapper.php128
-rw-r--r--www/wiki/tests/phpunit/includes/TestingAccessWrapperTest.php119
-rw-r--r--www/wiki/tests/phpunit/includes/TitleArrayFromResultTest.php10
-rw-r--r--www/wiki/tests/phpunit/includes/TitleMethodsTest.php83
-rw-r--r--www/wiki/tests/phpunit/includes/TitlePermissionTest.php173
-rw-r--r--www/wiki/tests/phpunit/includes/TitleTest.php59
-rw-r--r--www/wiki/tests/phpunit/includes/WatchedItemIntegrationTest.php145
-rw-r--r--www/wiki/tests/phpunit/includes/WatchedItemUnitTest.php161
-rw-r--r--www/wiki/tests/phpunit/includes/WebRequestTest.php19
-rw-r--r--www/wiki/tests/phpunit/includes/WikiReferenceTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/XmlJsTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/XmlTest.php88
-rw-r--r--www/wiki/tests/phpunit/includes/actions/ActionTest.php7
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiBaseTest.php1095
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiBlockTest.php242
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiCheckTokenTest.php95
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiClearHasMsgTest.php24
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiComparePagesTest.php50
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiContinuationManagerTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiDeleteTest.php168
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiDisabledTest.php19
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiEditPageTest.php1103
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiErrorFormatterTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiLoginTest.php18
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiLogoutTest.php75
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiMainTest.php594
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiModuleManagerTest.php86
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiMoveTest.php393
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiOpenSearchTest.php9
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiOptionsTest.php65
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiPageSetTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiParseTest.php821
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiPurgeTest.php5
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiQueryAllPagesTest.php14
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiQueryRecentChangesIntegrationTest.php976
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php12
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiQueryWatchlistRawIntegrationTest.php5
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiSetNotificationTimestampIntegrationTest.php1
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiStashEditTest.php1
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiTestCase.php141
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiTestCaseUpload.php149
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiUnblockTest.php10
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiUploadTest.php21
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiUploadTestCase.php153
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiUsageExceptionTest.php44
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiUserrightsTest.php358
-rw-r--r--www/wiki/tests/phpunit/includes/api/ApiWatchTest.php7
-rw-r--r--www/wiki/tests/phpunit/includes/api/format/ApiFormatBaseTest.php388
-rw-r--r--www/wiki/tests/phpunit/includes/api/format/ApiFormatPhpTest.php14
-rw-r--r--www/wiki/tests/phpunit/includes/api/format/ApiFormatRawTest.php120
-rw-r--r--www/wiki/tests/phpunit/includes/api/format/ApiFormatTestBase.php55
-rw-r--r--www/wiki/tests/phpunit/includes/api/format/ApiFormatXmlTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/api/query/ApiQueryBasicTest.php9
-rw-r--r--www/wiki/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php2
-rw-r--r--www/wiki/tests/phpunit/includes/api/query/ApiQueryContinueTest.php16
-rw-r--r--www/wiki/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php4
-rw-r--r--www/wiki/tests/phpunit/includes/api/query/ApiQueryTest.php14
-rw-r--r--www/wiki/tests/phpunit/includes/api/query/ApiQueryTestBase.php2
-rw-r--r--www/wiki/tests/phpunit/includes/api/query/ApiQueryUserContributionsTest.php194
-rw-r--r--www/wiki/tests/phpunit/includes/auth/AbstractAuthenticationProviderTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/auth/AbstractPasswordPrimaryAuthenticationProviderTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/auth/AuthManagerTest.php72
-rw-r--r--www/wiki/tests/phpunit/includes/auth/AuthPluginPrimaryAuthenticationProviderTest.php86
-rw-r--r--www/wiki/tests/phpunit/includes/auth/AuthenticationRequestTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/auth/AuthenticationRequestTestCase.php6
-rw-r--r--www/wiki/tests/phpunit/includes/auth/CheckBlocksSecondaryAuthenticationProviderTest.php11
-rw-r--r--www/wiki/tests/phpunit/includes/auth/EmailNotificationSecondaryAuthenticationProviderTest.php12
-rw-r--r--www/wiki/tests/phpunit/includes/auth/LegacyHookPreAuthenticationProviderTest.php20
-rw-r--r--www/wiki/tests/phpunit/includes/auth/PasswordAuthenticationRequestTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/auth/PasswordDomainAuthenticationRequestTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/auth/TemporaryPasswordAuthenticationRequestTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/auth/ThrottlePreAuthenticationProviderTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/auth/ThrottlerTest.php1
-rw-r--r--www/wiki/tests/phpunit/includes/cache/LocalisationCacheTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/changes/CategoryMembershipChangeTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/changes/ChangesListBooleanFilterGroupTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/changes/ChangesListBooleanFilterTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/changes/ChangesListFilterGroupTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/changes/ChangesListFilterTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/changes/ChangesListStringOptionsFilterGroupTest.php15
-rw-r--r--www/wiki/tests/phpunit/includes/changes/RCCacheEntryFactoryTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/changes/RecentChangeTest.php15
-rw-r--r--www/wiki/tests/phpunit/includes/changetags/ChangeTagsTest.php64
-rw-r--r--www/wiki/tests/phpunit/includes/collation/CollationFaTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/collation/CollationTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/collation/CustomUppercaseCollationTest.php15
-rw-r--r--www/wiki/tests/phpunit/includes/composer/ComposerVersionNormalizerTest.php7
-rw-r--r--www/wiki/tests/phpunit/includes/config/ConfigFactoryTest.php23
-rw-r--r--www/wiki/tests/phpunit/includes/config/EtcdConfigTest.php180
-rw-r--r--www/wiki/tests/phpunit/includes/config/GlobalVarConfigTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/config/HashConfigTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/config/MultiConfigTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/content/ContentHandlerTest.php75
-rw-r--r--www/wiki/tests/phpunit/includes/content/CssContentHandlerTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/content/CssContentTest.php5
-rw-r--r--www/wiki/tests/phpunit/includes/content/FileContentHandlerTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/content/JavaScriptContentTest.php25
-rw-r--r--www/wiki/tests/phpunit/includes/content/JsonContentTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/content/TextContentHandlerTest.php9
-rw-r--r--www/wiki/tests/phpunit/includes/content/TextContentTest.php13
-rw-r--r--www/wiki/tests/phpunit/includes/content/WikitextContentHandlerTest.php120
-rw-r--r--www/wiki/tests/phpunit/includes/content/WikitextContentTest.php23
-rw-r--r--www/wiki/tests/phpunit/includes/content/WikitextStructureTest.php5
-rw-r--r--www/wiki/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php364
-rw-r--r--www/wiki/tests/phpunit/includes/db/DatabaseOracleTest.php52
-rw-r--r--www/wiki/tests/phpunit/includes/db/DatabasePostgresTest.php177
-rw-r--r--www/wiki/tests/phpunit/includes/db/DatabaseSQLTest.php805
-rw-r--r--www/wiki/tests/phpunit/includes/db/DatabaseSqliteTest.php29
-rw-r--r--www/wiki/tests/phpunit/includes/db/DatabaseTest.php263
-rw-r--r--www/wiki/tests/phpunit/includes/db/DatabaseTestHelper.php65
-rw-r--r--www/wiki/tests/phpunit/includes/db/LBFactoryTest.php278
-rw-r--r--www/wiki/tests/phpunit/includes/db/LoadBalancerTest.php258
-rw-r--r--www/wiki/tests/phpunit/includes/debug/MWDebugTest.php10
-rw-r--r--www/wiki/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php7
-rw-r--r--www/wiki/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php10
-rw-r--r--www/wiki/tests/phpunit/includes/debug/logger/monolog/LogstashFormatterTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/deferred/CdnCacheUpdateTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/deferred/DeferredUpdatesTest.php123
-rw-r--r--www/wiki/tests/phpunit/includes/deferred/LinksUpdateTest.php1
-rw-r--r--www/wiki/tests/phpunit/includes/deferred/MWCallableUpdateTest.php82
-rw-r--r--www/wiki/tests/phpunit/includes/deferred/SiteStatsUpdateTest.php77
-rw-r--r--www/wiki/tests/phpunit/includes/deferred/TransactionRoundDefiningUpdateTest.php19
-rw-r--r--www/wiki/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/diff/DifferenceEngineTest.php28
-rw-r--r--www/wiki/tests/phpunit/includes/editpage/TextboxBuilderTest.php210
-rw-r--r--www/wiki/tests/phpunit/includes/exception/BadTitleErrorTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/exception/ErrorPageErrorTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/exception/MWExceptionTest.php9
-rw-r--r--www/wiki/tests/phpunit/includes/exception/ThrottledErrorTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/externalstore/ExternalStoreFactoryTest.php41
-rw-r--r--www/wiki/tests/phpunit/includes/externalstore/ExternalStoreForTesting.php46
-rw-r--r--www/wiki/tests/phpunit/includes/externalstore/ExternalStoreTest.php68
-rw-r--r--www/wiki/tests/phpunit/includes/filebackend/FileBackendTest.php20
-rw-r--r--www/wiki/tests/phpunit/includes/filebackend/SwiftFileBackendTest.php67
-rw-r--r--www/wiki/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/filerepo/FileRepoTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php9
-rw-r--r--www/wiki/tests/phpunit/includes/filerepo/RepoGroupTest.php10
-rw-r--r--www/wiki/tests/phpunit/includes/filerepo/file/FileTest.php21
-rw-r--r--www/wiki/tests/phpunit/includes/filerepo/file/LocalFileTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/htmlform/HTMLCheckMatrixTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/htmlform/HTMLFormTest.php46
-rw-r--r--www/wiki/tests/phpunit/includes/htmlform/HTMLRestrictionsFieldTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/http/HttpTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/import/ImportLinkCacheIntegrationTest.php15
-rw-r--r--www/wiki/tests/phpunit/includes/import/ImportTest.php118
-rw-r--r--www/wiki/tests/phpunit/includes/installer/DatabaseUpdaterTest.php279
-rw-r--r--www/wiki/tests/phpunit/includes/installer/InstallDocFormatterTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/installer/OracleInstallerTest.php1
-rw-r--r--www/wiki/tests/phpunit/includes/interwiki/ClassicInterwikiLookupTest.php10
-rw-r--r--www/wiki/tests/phpunit/includes/interwiki/InterwikiLookupAdapterTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/interwiki/InterwikiTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/jobqueue/JobQueueMemoryTest.php12
-rw-r--r--www/wiki/tests/phpunit/includes/jobqueue/JobQueueTest.php14
-rw-r--r--www/wiki/tests/phpunit/includes/jobqueue/JobTest.php12
-rw-r--r--www/wiki/tests/phpunit/includes/jobqueue/RefreshLinksPartitionTest.php1
-rw-r--r--www/wiki/tests/phpunit/includes/jobqueue/jobs/CategoryMembershipChangeJobTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/jobqueue/jobs/ClearUserWatchlistJobTest.php79
-rw-r--r--www/wiki/tests/phpunit/includes/json/FormatJsonTest.php10
-rw-r--r--www/wiki/tests/phpunit/includes/libs/ArrayUtilsTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/libs/CSSMinTest.php132
-rw-r--r--www/wiki/tests/phpunit/includes/libs/DeferredStringifierTest.php10
-rw-r--r--www/wiki/tests/phpunit/includes/libs/DnsSrvDiscovererTest.php9
-rw-r--r--www/wiki/tests/phpunit/includes/libs/GenericArrayObjectTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/libs/HashRingTest.php5
-rw-r--r--www/wiki/tests/phpunit/includes/libs/HtmlArmorTest.php29
-rw-r--r--www/wiki/tests/phpunit/includes/libs/IEUrlExtensionTest.php5
-rw-r--r--www/wiki/tests/phpunit/includes/libs/IPTest.php14
-rw-r--r--www/wiki/tests/phpunit/includes/libs/JavaScriptMinifierTest.php38
-rw-r--r--www/wiki/tests/phpunit/includes/libs/MWMessagePackTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/libs/MapCacheLRUTest.php117
-rw-r--r--www/wiki/tests/phpunit/includes/libs/MemoizedCallableTest.php10
-rw-r--r--www/wiki/tests/phpunit/includes/libs/ObjectFactoryTest.php180
-rw-r--r--www/wiki/tests/phpunit/includes/libs/ProcessCacheLRUTest.php5
-rw-r--r--www/wiki/tests/phpunit/includes/libs/SamplingStatsdClientTest.php14
-rw-r--r--www/wiki/tests/phpunit/includes/libs/StringUtilsTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/libs/TimingTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/libs/XhprofDataTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/libs/XhprofTest.php7
-rw-r--r--www/wiki/tests/phpunit/includes/libs/XmlTypeCheckTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/libs/composer/ComposerInstalledTest.php499
-rw-r--r--www/wiki/tests/phpunit/includes/libs/composer/ComposerLockTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/libs/http/HttpAcceptNegotiatorTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/libs/http/HttpAcceptParserTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/libs/mime/MimeAnalyzerTest.php7
-rw-r--r--www/wiki/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/libs/objectcache/CachedBagOStuffTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/libs/objectcache/HashBagOStuffTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php641
-rw-r--r--www/wiki/tests/phpunit/includes/libs/rdbms/TransactionProfilerTest.php26
-rw-r--r--www/wiki/tests/phpunit/includes/libs/rdbms/connectionmanager/ConnectionManagerTest.php5
-rw-r--r--www/wiki/tests/phpunit/includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManagerTest.php5
-rw-r--r--www/wiki/tests/phpunit/includes/libs/rdbms/database/DBConnRefTest.php148
-rw-r--r--www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseDomainTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseMssqlTest.php55
-rw-r--r--www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php602
-rw-r--r--www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php958
-rw-r--r--www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseSqliteRdbmsTest.php60
-rw-r--r--www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php248
-rw-r--r--www/wiki/tests/phpunit/includes/libs/xmp/XMPTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/libs/xmp/XMPValidateTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/linkeddata/PageDataRequestHandlerTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/logging/BlockLogFormatterTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/logging/ContentModelLogFormatterTest.php60
-rw-r--r--www/wiki/tests/phpunit/includes/logging/DatabaseLogEntryTest.php162
-rw-r--r--www/wiki/tests/phpunit/includes/logging/DeleteLogFormatterTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/logging/ImportLogFormatterTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/logging/LogFormatterTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/logging/LogFormatterTestCase.php1
-rw-r--r--www/wiki/tests/phpunit/includes/logging/MergeLogFormatterTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/logging/MoveLogFormatterTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/logging/NewUsersLogFormatterTest.php11
-rw-r--r--www/wiki/tests/phpunit/includes/logging/PageLangLogFormatterTest.php5
-rw-r--r--www/wiki/tests/phpunit/includes/logging/PatrolLogFormatterTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/logging/ProtectLogFormatterTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/logging/RightsLogFormatterTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/logging/UploadLogFormatterTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/mail/MailAddressTest.php23
-rw-r--r--www/wiki/tests/phpunit/includes/media/BitmapScalingTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/media/ExifBitmapTest.php9
-rw-r--r--www/wiki/tests/phpunit/includes/media/ExifRotationTest.php5
-rw-r--r--www/wiki/tests/phpunit/includes/media/FakeDimensionFile.php3
-rw-r--r--www/wiki/tests/phpunit/includes/media/FormatMetadataTest.php48
-rw-r--r--www/wiki/tests/phpunit/includes/media/GIFTest.php9
-rw-r--r--www/wiki/tests/phpunit/includes/media/IPTCTest.php7
-rw-r--r--www/wiki/tests/phpunit/includes/media/JpegMetadataExtractorTest.php17
-rw-r--r--www/wiki/tests/phpunit/includes/media/JpegTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/media/MediaWikiMediaTestCase.php2
-rw-r--r--www/wiki/tests/phpunit/includes/media/PNGMetadataExtractorTest.php18
-rw-r--r--www/wiki/tests/phpunit/includes/media/PNGTest.php9
-rw-r--r--www/wiki/tests/phpunit/includes/media/SVGMetadataExtractorTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/media/SVGTest.php74
-rw-r--r--www/wiki/tests/phpunit/includes/media/TiffTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/media/WebPTest.php10
-rw-r--r--www/wiki/tests/phpunit/includes/media/XMPTest.php223
-rw-r--r--www/wiki/tests/phpunit/includes/media/XMPValidateTest.php53
-rw-r--r--www/wiki/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/objectcache/ObjectCacheTest.php12
-rw-r--r--www/wiki/tests/phpunit/includes/objectcache/RESTBagOStuffTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/objectcache/RedisBagOStuffTest.php7
-rw-r--r--www/wiki/tests/phpunit/includes/page/ArticleTest.php22
-rw-r--r--www/wiki/tests/phpunit/includes/page/ImagePage404Test.php1
-rw-r--r--www/wiki/tests/phpunit/includes/page/ImagePageTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/page/WikiPageContentHandlerDbTest.php42
-rw-r--r--www/wiki/tests/phpunit/includes/page/WikiPageDbTestBase.php1903
-rw-r--r--www/wiki/tests/phpunit/includes/page/WikiPageNoContentHandlerDbTest.php14
-rw-r--r--www/wiki/tests/phpunit/includes/page/WikiPageTest.php1208
-rw-r--r--www/wiki/tests/phpunit/includes/page/WikiPageTestContentHandlerUseDB.php61
-rw-r--r--www/wiki/tests/phpunit/includes/pager/RangeChronologicalPagerTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/pager/ReverseChronologicalPagerTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/parser/CoreParserFunctionsTest.php21
-rw-r--r--www/wiki/tests/phpunit/includes/parser/MagicVariableTest.php7
-rw-r--r--www/wiki/tests/phpunit/includes/parser/MediaWikiParserTest.php138
-rw-r--r--www/wiki/tests/phpunit/includes/parser/NewParserTest.php1123
-rw-r--r--www/wiki/tests/phpunit/includes/parser/ParserIntegrationTest.php20
-rw-r--r--www/wiki/tests/phpunit/includes/parser/ParserMethodsTest.php1
-rw-r--r--www/wiki/tests/phpunit/includes/parser/ParserOptionsTest.php36
-rw-r--r--www/wiki/tests/phpunit/includes/parser/ParserOutputTest.php202
-rw-r--r--www/wiki/tests/phpunit/includes/parser/PreprocessorTest.php16
-rw-r--r--www/wiki/tests/phpunit/includes/parser/SanitizerTest.php (renamed from www/wiki/tests/phpunit/includes/SanitizerTest.php)32
-rw-r--r--www/wiki/tests/phpunit/includes/parser/StripStateTest.php136
-rw-r--r--www/wiki/tests/phpunit/includes/parser/TagHooksTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/parser/TidyTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/password/BcryptPasswordTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/password/EncryptedPasswordTest.php13
-rw-r--r--www/wiki/tests/phpunit/includes/password/LayeredParameterizedPasswordTest.php12
-rw-r--r--www/wiki/tests/phpunit/includes/password/MWOldPasswordTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/password/MWSaltedPasswordTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/password/PasswordFactoryTest.php32
-rw-r--r--www/wiki/tests/phpunit/includes/password/PasswordTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/password/Pbkdf2PasswordFallbackTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/password/Pbkdf2PasswordTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/password/UserPasswordPolicyTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/poolcounter/PoolCounterTest.php13
-rw-r--r--www/wiki/tests/phpunit/includes/preferences/DefaultPreferencesFactoryTest.php183
-rw-r--r--www/wiki/tests/phpunit/includes/rcfeed/RCFeedIntegrationTest.php21
-rw-r--r--www/wiki/tests/phpunit/includes/registration/CoreVersionCheckerTest.php38
-rw-r--r--www/wiki/tests/phpunit/includes/registration/ExtensionJsonValidatorTest.php84
-rw-r--r--www/wiki/tests/phpunit/includes/registration/ExtensionProcessorTest.php216
-rw-r--r--www/wiki/tests/phpunit/includes/registration/ExtensionRegistryTest.php64
-rw-r--r--www/wiki/tests/phpunit/includes/registration/VersionCheckerTest.php141
-rw-r--r--www/wiki/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/resourceloader/MessageBlobStoreTest.php20
-rw-r--r--www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderClientHtmlTest.php101
-rw-r--r--www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderContextTest.php5
-rw-r--r--www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderOOUIImageModuleTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderSkinModuleTest.php162
-rw-r--r--www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php57
-rw-r--r--www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php18
-rw-r--r--www/wiki/tests/phpunit/includes/search/SearchEnginePrefixTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/search/SearchEngineTest.php69
-rw-r--r--www/wiki/tests/phpunit/includes/search/SearchSuggestionSetTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/services/ServiceContainerTest.php (renamed from www/wiki/tests/phpunit/includes/Services/ServiceContainerTest.php)27
-rw-r--r--www/wiki/tests/phpunit/includes/services/TestWiring1.php (renamed from www/wiki/tests/phpunit/includes/Services/TestWiring1.php)0
-rw-r--r--www/wiki/tests/phpunit/includes/services/TestWiring2.php (renamed from www/wiki/tests/phpunit/includes/Services/TestWiring2.php)0
-rw-r--r--www/wiki/tests/phpunit/includes/session/BotPasswordSessionProviderTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/session/CookieSessionProviderTest.php10
-rw-r--r--www/wiki/tests/phpunit/includes/session/ImmutableSessionProviderWithCookieTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/session/MetadataMergeExceptionTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/session/PHPSessionHandlerTest.php15
-rw-r--r--www/wiki/tests/phpunit/includes/session/SessionBackendTest.php38
-rw-r--r--www/wiki/tests/phpunit/includes/session/SessionManagerTest.php34
-rw-r--r--www/wiki/tests/phpunit/includes/session/SessionTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/session/TestBagOStuff.php31
-rw-r--r--www/wiki/tests/phpunit/includes/session/TestUtils.php2
-rw-r--r--www/wiki/tests/phpunit/includes/session/UserInfoTest.php12
-rw-r--r--www/wiki/tests/phpunit/includes/shell/CommandFactoryTest.php21
-rw-r--r--www/wiki/tests/phpunit/includes/shell/CommandTest.php93
-rw-r--r--www/wiki/tests/phpunit/includes/shell/FirejailCommandTest.php85
-rw-r--r--www/wiki/tests/phpunit/includes/shell/ShellTest.php76
-rw-r--r--www/wiki/tests/phpunit/includes/site/CachingSiteStoreTest.php10
-rw-r--r--www/wiki/tests/phpunit/includes/site/DBSiteStoreTest.php12
-rw-r--r--www/wiki/tests/phpunit/includes/site/FileBasedSiteLookupTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/site/SiteExporterTest.php9
-rw-r--r--www/wiki/tests/phpunit/includes/site/SiteImporterTest.php13
-rw-r--r--www/wiki/tests/phpunit/includes/site/SiteListTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/site/SiteTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/site/SitesCacheFileBuilderTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/site/TestSites.php2
-rw-r--r--www/wiki/tests/phpunit/includes/skins/SkinFactoryTest.php22
-rw-r--r--www/wiki/tests/phpunit/includes/skins/SkinTemplateTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/sparql/SparqlClientTest.php190
-rw-r--r--www/wiki/tests/phpunit/includes/specialpage/AbstractChangesListSpecialPageTestCase.php9
-rw-r--r--www/wiki/tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php128
-rw-r--r--www/wiki/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/specialpage/SpecialPageTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/specials/ContribsPagerTest.php1
-rw-r--r--www/wiki/tests/phpunit/includes/specials/ImageListPagerTest.php1
-rw-r--r--www/wiki/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialBlankPageTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialEditWatchlistTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialMIMESearchTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialMyLanguageTest.php13
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialPageDataTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialPageTestBase.php4
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialPreferencesTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialSearchTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialShortpagesTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialUncategorizedcategoriesTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialUploadTest.php29
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialWatchlistTest.php9
-rw-r--r--www/wiki/tests/phpunit/includes/tidy/RemexDriverTest.php14
-rw-r--r--www/wiki/tests/phpunit/includes/title/ForeignTitleTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php168
-rw-r--r--www/wiki/tests/phpunit/includes/title/MediaWikiTitleCodecTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/title/SubpageImportTitleFactoryTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/title/TitleValueTest.php31
-rw-r--r--www/wiki/tests/phpunit/includes/upload/UploadBaseTest.php11
-rw-r--r--www/wiki/tests/phpunit/includes/upload/UploadFromUrlTest.php2
-rw-r--r--www/wiki/tests/phpunit/includes/upload/UploadStashTest.php40
-rw-r--r--www/wiki/tests/phpunit/includes/user/BotPasswordTest.php42
-rw-r--r--www/wiki/tests/phpunit/includes/user/CentralIdLookupTest.php30
-rw-r--r--www/wiki/tests/phpunit/includes/user/ExternalUserNamesTest.php131
-rw-r--r--www/wiki/tests/phpunit/includes/user/PasswordResetTest.php9
-rw-r--r--www/wiki/tests/phpunit/includes/user/UserArrayFromResultTest.php6
-rw-r--r--www/wiki/tests/phpunit/includes/user/UserGroupMembershipTest.php3
-rw-r--r--www/wiki/tests/phpunit/includes/user/UserTest.php290
-rw-r--r--www/wiki/tests/phpunit/includes/utils/AvroValidatorTest.php10
-rw-r--r--www/wiki/tests/phpunit/includes/utils/BatchRowUpdateTest.php23
-rw-r--r--www/wiki/tests/phpunit/includes/utils/ClassCollectorTest.php10
-rw-r--r--www/wiki/tests/phpunit/includes/utils/FileContentsHasherTest.php4
-rw-r--r--www/wiki/tests/phpunit/includes/utils/IPTest.php670
-rw-r--r--www/wiki/tests/phpunit/includes/utils/MWCryptHKDFTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/utils/MWCryptHashTest.php13
-rw-r--r--www/wiki/tests/phpunit/includes/utils/MWRestrictionsTest.php12
-rw-r--r--www/wiki/tests/phpunit/includes/utils/UIDGeneratorTest.php8
-rw-r--r--www/wiki/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php5
-rw-r--r--www/wiki/tests/phpunit/includes/watcheditem/NoWriteWatchedItemStoreUnitTest.php246
-rw-r--r--www/wiki/tests/phpunit/includes/watcheditem/WatchedItemQueryServiceUnitTest.php (renamed from www/wiki/tests/phpunit/includes/WatchedItemQueryServiceUnitTest.php)260
-rw-r--r--www/wiki/tests/phpunit/includes/watcheditem/WatchedItemStoreIntegrationTest.php (renamed from www/wiki/tests/phpunit/includes/WatchedItemStoreIntegrationTest.php)17
-rw-r--r--www/wiki/tests/phpunit/includes/watcheditem/WatchedItemStoreUnitTest.php (renamed from www/wiki/tests/phpunit/includes/WatchedItemStoreUnitTest.php)217
-rw-r--r--www/wiki/tests/phpunit/languages/LanguageCodeTest.php123
-rw-r--r--www/wiki/tests/phpunit/languages/LanguageConverterTest.php2
-rw-r--r--www/wiki/tests/phpunit/languages/LanguageTest.php116
-rw-r--r--www/wiki/tests/phpunit/languages/SpecialPageAliasTest.php2
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageArTest.php4
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageBe_taraskTest.php7
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageBsTest.php6
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageCrhTest.php92
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageCuTest.php4
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageDsbTest.php4
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageGanTest.php4
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageHsbTest.php4
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageHuTest.php4
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageHyTest.php6
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageIuTest.php4
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageKkTest.php5
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageKshTest.php4
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageKuTest.php4
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageMlTest.php4
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguagePlTest.php27
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageRuTest.php11
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageShiTest.php4
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageSlTest.php4
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageSrTest.php5
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageTgTest.php4
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageTrTest.php4
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageUkTest.php1
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageUzTest.php5
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageWaTest.php4
-rw-r--r--www/wiki/tests/phpunit/languages/classes/LanguageZhTest.php5
-rw-r--r--www/wiki/tests/phpunit/maintenance/BenchmarkerTest.php142
-rw-r--r--www/wiki/tests/phpunit/maintenance/DumpTestCase.php12
-rw-r--r--www/wiki/tests/phpunit/maintenance/MaintenanceBaseTestCase.php93
-rw-r--r--www/wiki/tests/phpunit/maintenance/MaintenanceTest.php1015
-rw-r--r--www/wiki/tests/phpunit/maintenance/backupPrefetchTest.php8
-rw-r--r--www/wiki/tests/phpunit/maintenance/backupTextPassTest.php13
-rw-r--r--www/wiki/tests/phpunit/maintenance/backup_LogTest.php9
-rw-r--r--www/wiki/tests/phpunit/maintenance/backup_PageTest.php14
-rw-r--r--www/wiki/tests/phpunit/maintenance/categoriesRdfTest.php40
-rw-r--r--www/wiki/tests/phpunit/maintenance/deleteAutoPatrolLogsTest.php252
-rw-r--r--www/wiki/tests/phpunit/maintenance/fetchTextTest.php12
-rw-r--r--www/wiki/tests/phpunit/mocks/MockChangesListFilterGroup.php14
-rw-r--r--www/wiki/tests/phpunit/mocks/MockMessageLocalizer.php49
-rw-r--r--www/wiki/tests/phpunit/mocks/content/DummyContentForTesting.php6
-rw-r--r--www/wiki/tests/phpunit/mocks/content/DummyContentHandlerForTesting.php4
-rw-r--r--www/wiki/tests/phpunit/mocks/content/DummyNonTextContent.php2
-rw-r--r--www/wiki/tests/phpunit/mocks/content/DummySerializeErrorContentHandler.php51
-rw-r--r--www/wiki/tests/phpunit/mocks/media/MockOggHandler.php93
-rwxr-xr-xwww/wiki/tests/phpunit/phpunit.php9
-rw-r--r--www/wiki/tests/phpunit/skins/SideBarTest.php10
-rw-r--r--www/wiki/tests/phpunit/structure/ApiDocumentationTest.php180
-rw-r--r--www/wiki/tests/phpunit/structure/ApiStructureTest.php408
-rw-r--r--www/wiki/tests/phpunit/structure/AutoLoaderTest.php5
-rw-r--r--www/wiki/tests/phpunit/structure/AvailableRightsTest.php4
-rw-r--r--www/wiki/tests/phpunit/structure/ExtensionJsonValidationTest.php4
-rw-r--r--www/wiki/tests/phpunit/structure/ResourcesTest.php15
-rw-r--r--www/wiki/tests/phpunit/structure/StructureTest.php2
-rw-r--r--www/wiki/tests/phpunit/suite.xml1
-rw-r--r--www/wiki/tests/phpunit/suites/ParserTestFileSuite.php6
-rw-r--r--www/wiki/tests/phpunit/suites/UploadFromUrlTestSuite.php53
-rw-r--r--www/wiki/tests/phpunit/tests/MediaWikiTestCaseSchema1Test.php51
-rw-r--r--www/wiki/tests/phpunit/tests/MediaWikiTestCaseSchema2Test.php48
-rw-r--r--www/wiki/tests/phpunit/tests/MediaWikiTestCaseSchemaTest.sql18
-rw-r--r--www/wiki/tests/phpunit/tests/MediaWikiTestCaseTest.php5
-rw-r--r--www/wiki/tests/qunit/QUnitTestResources.php16
-rw-r--r--www/wiki/tests/qunit/data/callMwLoaderTestCallback.js1
-rw-r--r--www/wiki/tests/qunit/data/generateJqueryMsgData.php4
-rw-r--r--www/wiki/tests/qunit/data/load.mock.php55
-rw-r--r--www/wiki/tests/qunit/data/qunitOkCall.js2
-rw-r--r--www/wiki/tests/qunit/data/testrunner.js272
-rw-r--r--www/wiki/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js53
-rw-r--r--www/wiki/tests/qunit/suites/resources/jquery/jquery.byteLength.test.js37
-rw-r--r--www/wiki/tests/qunit/suites/resources/jquery/jquery.highlightText.test.js2
-rw-r--r--www/wiki/tests/qunit/suites/resources/jquery/jquery.lengthLimit.test.js (renamed from www/wiki/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js)46
-rw-r--r--www/wiki/tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js4
-rw-r--r--www/wiki/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js64
-rw-r--r--www/wiki/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js145
-rw-r--r--www/wiki/tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js2
-rw-r--r--www/wiki/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js6
-rw-r--r--www/wiki/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js2
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.category.test.js89
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.edit.test.js67
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js8
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js6
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js12
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/UriProcessor.test.js184
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FilterItem.test.js18
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js312
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.SavedQueriesModel.test.js520
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.SavedQueryItemModel.test.js89
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js4
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js6
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.String.byteLength.test.js39
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.String.trimByteLength.test.js150
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js14
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js4
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js24
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js24
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js240
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js2
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js8
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js34
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.visibleTimeout.test.js115
-rw-r--r--www/wiki/tests/qunit/suites/resources/startup.test.js17
-rw-r--r--www/wiki/tests/selenium/README.md16
-rw-r--r--www/wiki/tests/selenium/pageobjects/createaccount.page.js64
-rw-r--r--www/wiki/tests/selenium/pageobjects/delete.page.js39
-rw-r--r--www/wiki/tests/selenium/pageobjects/edit.page.js29
-rw-r--r--www/wiki/tests/selenium/pageobjects/page.js5
-rw-r--r--www/wiki/tests/selenium/pageobjects/restore.page.js21
-rw-r--r--www/wiki/tests/selenium/pageobjects/userlogin.page.js4
-rw-r--r--www/wiki/tests/selenium/specs/page.js82
-rw-r--r--www/wiki/tests/selenium/wdio.conf.jenkins.js20
-rw-r--r--www/wiki/tests/selenium/wdio.conf.js194
-rw-r--r--www/wiki/tests/testHelpers.inc874
656 files changed, 41050 insertions, 18930 deletions
diff --git a/www/wiki/tests/TestsAutoLoader.php b/www/wiki/tests/TestsAutoLoader.php
deleted file mode 100644
index ebb6d901..00000000
--- a/www/wiki/tests/TestsAutoLoader.php
+++ /dev/null
@@ -1,153 +0,0 @@
-<?php
-/**
- * AutoLoader for the testing suite.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Testing
- */
-
-global $wgAutoloadClasses;
-$testDir = __DIR__;
-
-$wgAutoloadClasses += [
-
- # tests
- 'DbTestPreviewer' => "$testDir/testHelpers.inc",
- 'DbTestRecorder' => "$testDir/testHelpers.inc",
- 'DelayedParserTest' => "$testDir/testHelpers.inc",
- 'ParserTestResult' => "$testDir/parser/ParserTestResult.php",
- 'TestFileIterator' => "$testDir/testHelpers.inc",
- 'TestFileDataProvider' => "$testDir/testHelpers.inc",
- 'TestRecorder' => "$testDir/testHelpers.inc",
- 'ITestRecorder' => "$testDir/testHelpers.inc",
- 'DjVuSupport' => "$testDir/testHelpers.inc",
- 'TidySupport' => "$testDir/testHelpers.inc",
-
- # tests/phpunit
- 'MediaWikiTestCase' => "$testDir/phpunit/MediaWikiTestCase.php",
- 'MediaWikiPHPUnitTestListener' => "$testDir/phpunit/MediaWikiPHPUnitTestListener.php",
- 'MediaWikiLangTestCase' => "$testDir/phpunit/MediaWikiLangTestCase.php",
- 'ResourceLoaderTestCase' => "$testDir/phpunit/ResourceLoaderTestCase.php",
- 'ResourceLoaderTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
- 'ResourceLoaderFileModuleTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
- 'TestUser' => "$testDir/phpunit/includes/TestUser.php",
- 'LessFileCompilationTest' => "$testDir/phpunit/LessFileCompilationTest.php",
-
- # tests/phpunit/includes
- 'RevisionStorageTest' => "$testDir/phpunit/includes/RevisionStorageTest.php",
- 'TestingAccessWrapper' => "$testDir/phpunit/includes/TestingAccessWrapper.php",
- 'TestLogger' => "$testDir/phpunit/includes/TestLogger.php",
-
- # tests/phpunit/includes/api
- 'ApiFormatTestBase' => "$testDir/phpunit/includes/api/format/ApiFormatTestBase.php",
- 'ApiQueryTestBase' => "$testDir/phpunit/includes/api/query/ApiQueryTestBase.php",
- 'ApiQueryContinueTestBase' => "$testDir/phpunit/includes/api/query/ApiQueryContinueTestBase.php",
- 'ApiTestCase' => "$testDir/phpunit/includes/api/ApiTestCase.php",
- 'ApiTestCaseUpload' => "$testDir/phpunit/includes/api/ApiTestCaseUpload.php",
- 'ApiTestContext' => "$testDir/phpunit/includes/api/ApiTestContext.php",
- 'MockApi' => "$testDir/phpunit/includes/api/MockApi.php",
- 'MockApiQueryBase' => "$testDir/phpunit/includes/api/MockApiQueryBase.php",
- 'UserWrapper' => "$testDir/phpunit/includes/api/UserWrapper.php",
- 'RandomImageGenerator' => "$testDir/phpunit/includes/api/RandomImageGenerator.php",
-
- # tests/phpunit/includes/auth
- 'MediaWiki\\Auth\\AuthenticationRequestTestCase' =>
- "$testDir/phpunit/includes/auth/AuthenticationRequestTestCase.php",
-
- # tests/phpunit/includes/changes
- 'TestRecentChangesHelper' => "$testDir/phpunit/includes/changes/TestRecentChangesHelper.php",
-
- # tests/phpunit/includes/content
- 'DummyContentHandlerForTesting' =>
- "$testDir/phpunit/mocks/content/DummyContentHandlerForTesting.php",
- 'DummyContentForTesting' => "$testDir/phpunit/mocks/content/DummyContentForTesting.php",
- 'DummyNonTextContentHandler' => "$testDir/phpunit/mocks/content/DummyNonTextContentHandler.php",
- 'DummyNonTextContent' => "$testDir/phpunit/mocks/content/DummyNonTextContent.php",
- 'ContentHandlerTest' => "$testDir/phpunit/includes/content/ContentHandlerTest.php",
- 'JavaScriptContentTest' => "$testDir/phpunit/includes/content/JavaScriptContentTest.php",
- 'TextContentTest' => "$testDir/phpunit/includes/content/TextContentTest.php",
- 'WikitextContentTest' => "$testDir/phpunit/includes/content/WikitextContentTest.php",
-
- # tests/phpunit/includes/db
- 'DatabaseTestHelper' => "$testDir/phpunit/includes/db/DatabaseTestHelper.php",
-
- # tests/phpunit/includes/diff
- 'FakeDiffOp' => "$testDir/phpunit/includes/diff/FakeDiffOp.php",
-
- # tests/phpunit/includes/logging
- 'LogFormatterTestCase' => "$testDir/phpunit/includes/logging/LogFormatterTestCase.php",
-
- # tests/phpunit/includes/page
- 'WikiPageTest' => "$testDir/phpunit/includes/page/WikiPageTest.php",
-
- # tests/phpunit/includes/password
- 'PasswordTestCase' => "$testDir/phpunit/includes/password/PasswordTestCase.php",
-
- # tests/phpunit/includes/resourceloader
- 'ResourceLoaderImageModuleTest' =>
- "$testDir/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php",
- 'ResourceLoaderImageModuleTestable' =>
- "$testDir/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php",
-
- # tests/phpunit/includes/session
- 'MediaWiki\\Session\\TestBagOStuff' => "$testDir/phpunit/includes/session/TestBagOStuff.php",
- 'MediaWiki\\Session\\TestUtils' => "$testDir/phpunit/includes/session/TestUtils.php",
-
- # tests/phpunit/includes/specials
- 'SpecialPageTestBase' => "$testDir/phpunit/includes/specials/SpecialPageTestBase.php",
- 'SpecialPageExecutor' => "$testDir/phpunit/includes/specials/SpecialPageExecutor.php",
-
- # tests/phpunit/languages
- 'LanguageClassesTestCase' => "$testDir/phpunit/languages/LanguageClassesTestCase.php",
-
- # tests/phpunit/includes/libs
- 'GenericArrayObjectTest' => "$testDir/phpunit/includes/libs/GenericArrayObjectTest.php",
-
- # tests/phpunit/maintenance
- 'DumpTestCase' => "$testDir/phpunit/maintenance/DumpTestCase.php",
-
- # tests/phpunit/media
- 'FakeDimensionFile' => "$testDir/phpunit/includes/media/FakeDimensionFile.php",
- 'MediaWikiMediaTestCase' => "$testDir/phpunit/includes/media/MediaWikiMediaTestCase.php",
-
- # tests/phpunit/mocks
- 'MockFSFile' => "$testDir/phpunit/mocks/filebackend/MockFSFile.php",
- 'MockFileBackend' => "$testDir/phpunit/mocks/filebackend/MockFileBackend.php",
- 'MockBitmapHandler' => "$testDir/phpunit/mocks/media/MockBitmapHandler.php",
- 'MockImageHandler' => "$testDir/phpunit/mocks/media/MockImageHandler.php",
- 'MockSvgHandler' => "$testDir/phpunit/mocks/media/MockSvgHandler.php",
- 'MockDjVuHandler' => "$testDir/phpunit/mocks/media/MockDjVuHandler.php",
- 'MockOggHandler' => "$testDir/phpunit/mocks/media/MockOggHandler.php",
- 'MockWebRequest' => "$testDir/phpunit/mocks/MockWebRequest.php",
- 'MediaWiki\\Session\\DummySessionBackend'
- => "$testDir/phpunit/mocks/session/DummySessionBackend.php",
- 'DummySessionProvider' => "$testDir/phpunit/mocks/session/DummySessionProvider.php",
-
- # tests/parser
- 'NewParserTest' => "$testDir/phpunit/includes/parser/NewParserTest.php",
- 'MediaWikiParserTest' => "$testDir/phpunit/includes/parser/MediaWikiParserTest.php",
- 'ParserTest' => "$testDir/parser/parserTest.inc",
- 'ParserTestParserHook' => "$testDir/parser/parserTestsParserHook.php",
-
- # tests/phpunit/includes/site
- 'SiteTest' => "$testDir/phpunit/includes/site/SiteTest.php",
- 'TestSites' => "$testDir/phpunit/includes/site/TestSites.php",
-
- # tests/phpunit/includes/specialpage
- 'SpecialPageTestHelper' => "$testDir/phpunit/includes/specialpage/SpecialPageTestHelper.php",
-];
diff --git a/www/wiki/tests/browser/ci.yml b/www/wiki/tests/browser/ci.yml
deleted file mode 100644
index 8c9865e6..00000000
--- a/www/wiki/tests/browser/ci.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-BROWSER:
- - firefox
-
-MEDIAWIKI_ENVIRONMENT:
- - beta
-
-PLATFORM:
- - Linux
diff --git a/www/wiki/tests/browser/environments.yml b/www/wiki/tests/browser/environments.yml
deleted file mode 100644
index 35eb153f..00000000
--- a/www/wiki/tests/browser/environments.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-# Customize this configuration as necessary to provide defaults for various
-# test environments.
-#
-# The set of defaults to use is determined by the MEDIAWIKI_ENVIRONMENT
-# environment variable.
-#
-# export MEDIAWIKI_ENVIRONMENT=mw-vagrant-host
-# bundle exec cucumber
-#
-# Additional variables set by the environment will override the corresponding
-# defaults defined here.
-#
-# export MEDIAWIKI_ENVIRONMENT=mw-vagrant-host
-# export MEDIAWIKI_USER=Selenium_user2
-# bundle exec cucumber
-#
-mw-vagrant-host: &default
- user_factory: true
- mediawiki_url: http://127.0.0.1:8080/wiki/
-
-mw-vagrant-guest:
- user_factory: true
- mediawiki_url: http://127.0.0.1/wiki/
-
-beta:
- mediawiki_url: http://en.wikipedia.beta.wmflabs.org/wiki/
- mediawiki_user: Selenium_user
- # mediawiki_password: SET THIS IN THE ENVIRONMENT!
-
-test2:
- mediawiki_url: http://test2.wikipedia.org/wiki/
- 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/www/wiki/tests/browser/features/create_account.feature b/www/wiki/tests/browser/features/create_account.feature
deleted file mode 100644
index 80291828..00000000
--- a/www/wiki/tests/browser/features/create_account.feature
+++ /dev/null
@@ -1,17 +0,0 @@
-@chrome @firefox @vagrant
-Feature: Create account
-
- Scenario Outline: Go to Create account page
- Given I go to Create account page at <path>
- Then form has Create account button
-
- Examples:
- | path |
- | Special:CreateAccount |
- | Special:UserLogin/signup |
- | Special:UserLogin?type=signup |
-
- Scenario: If no username is entered then an error is displayed
- Given I go to Create account page at Special:CreateAccount
- When I submit the form
- Then an error message is displayed
diff --git a/www/wiki/tests/browser/features/create_and_follow_wiki_link.feature b/www/wiki/tests/browser/features/create_and_follow_wiki_link.feature
deleted file mode 100644
index 510c467b..00000000
--- a/www/wiki/tests/browser/features/create_and_follow_wiki_link.feature
+++ /dev/null
@@ -1,9 +0,0 @@
-@chrome @firefox @vagrant
-Feature: Create Page With Wiki Link
-
- Scenario: Create Page With Wiki Link
- Given I create page "Link Target Test Page" with content "Link Target Test Page"
- And I go to the "Link Source Test Page" page with content "This is a [[Link Target Test Page|link to the test target page]] right here."
- When I click the Link Target link
- Then I should be on the Link Target Test Page
- And the page content should contain "Link Target Test Page"
diff --git a/www/wiki/tests/browser/features/edit_page.feature b/www/wiki/tests/browser/features/edit_page.feature
deleted file mode 100644
index ade69145..00000000
--- a/www/wiki/tests/browser/features/edit_page.feature
+++ /dev/null
@@ -1,11 +0,0 @@
-@chrome @firefox @vagrant
-Feature: Edit Page
-
- Scenario: Create and edit page
- Given I go to the "Editing Test Page" page with content "This is a page to test editing"
- When I click Edit
- And I edit the page with "Edited and a random string"
- And I click Preview
- And I click Show Changes
- And I save the edit
- Then the edited page content should contain "Edited and a random string"
diff --git a/www/wiki/tests/browser/features/file.feature b/www/wiki/tests/browser/features/file.feature
deleted file mode 100644
index 0b59c88a..00000000
--- a/www/wiki/tests/browser/features/file.feature
+++ /dev/null
@@ -1,11 +0,0 @@
-@chrome @firefox @vagrant
-Feature: File
-
- Scenario: Anonymous goes to file that does not exist
- Given I am at file that does not exist
- Then page should show that no such file exists
-
- Scenario: Logged-in user goes to file that does not exist
- Given I am logged in
- And I am at file that does not exist
- Then page should show that no such file exists
diff --git a/www/wiki/tests/browser/features/login.feature b/www/wiki/tests/browser/features/login.feature
deleted file mode 100644
index c18f0872..00000000
--- a/www/wiki/tests/browser/features/login.feature
+++ /dev/null
@@ -1,30 +0,0 @@
-@chrome @firefox @vagrant
-Feature: Log in
-
- Background:
- Given I am at Log in page
-
- Scenario: Go to Log in page
- Then Username element should be there
- And Password element should be there
- And Log in element should be there
-
- Scenario: Log in without entering credentials
- When I log in without entering credentials
- Then error box should be visible
-
- Scenario: Log in without entering password
- When I log in without entering password
- Then error box should be visible
-
- Scenario: Log in with incorrect username
- When I log in with incorrect username
- Then error box should be visible
-
- Scenario: Log in with incorrect password
- When I log in with incorrect password
- Then error box should be visible
-
- Scenario: Log in with valid credentials
- When I log in
- Then error box should not be visible
diff --git a/www/wiki/tests/browser/features/main_page_links.feature b/www/wiki/tests/browser/features/main_page_links.feature
deleted file mode 100644
index 1f3621bb..00000000
--- a/www/wiki/tests/browser/features/main_page_links.feature
+++ /dev/null
@@ -1,19 +0,0 @@
-@chrome @firefox @vagrant
-Feature: Main Page View History Links
-
- Background:
- Given I open the main wiki URL
-
- Scenario: Main Page View History links exist
- Then I should see a link for View History
-
- Scenario: Main Page Sidebar Links
- Then I should see a link for Recent changes
- And I should see a link for Random page
- And I should see a link for Help
- And I should see a link for What links here
- And I should see a link for Related changes
- And I should see a link for Special pages
- And I should see a link for Printable version
- And I should see a link for Permanent link
- And I should see a link for Page information
diff --git a/www/wiki/tests/browser/features/preferences.feature b/www/wiki/tests/browser/features/preferences.feature
deleted file mode 100644
index 23663c24..00000000
--- a/www/wiki/tests/browser/features/preferences.feature
+++ /dev/null
@@ -1,47 +0,0 @@
-@chrome @firefox @vagrant
-Feature: Preferences
-
- Scenario: Preferences Appearance
- Given I am logged in
- When I navigate to Preferences
- And I click Appearance
- Then I can select skin Vector
- And I can select image size
- And I can select thumbnail size
- And I can select Threshold for stub link
- And I can select underline preferences
- And I have advanced options checkboxes
- And I can click Save
- And I can restore default settings
- And I can select date format
- And I can see time offset section
- And I can see local time
- And I can select my time zone
-
- Scenario: Preferences Editing
- Given I am logged in
- When I navigate to Preferences
- And I click Editing
- Then I can select edit area font style
- And I can select section editing via edit links
- And I can select section editing by right clicking
- And I can select section editing by double clicking
- And I can select to prompt me when entering a blank edit summary
- And I can select to warn me when I leave an edit page with unsaved changes
- And I can select show edit toolbar
- And I can select show preview on first edit
- And I can select show preview before edit box
- And I can select live preview
-
- Scenario: Preferences User profile
- Given I am logged in
- When I navigate to Preferences
- And I click User profile
- Then I can see my Basic informations
- And I can change my language
- And I can change my gender
- And I can see my signature
- And I can change my signature
- And I can see my email
- And I can click Save
- And I can restore default settings
diff --git a/www/wiki/tests/browser/features/step_definitions/create_account_steps.rb b/www/wiki/tests/browser/features/step_definitions/create_account_steps.rb
deleted file mode 100644
index fa0570c6..00000000
--- a/www/wiki/tests/browser/features/step_definitions/create_account_steps.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-Given(/^I go to Create account page at (.+)$/) do |path|
- visit(CreateAccountPage, using_params: { page_title: path })
-end
-
-Then(/^form has Create account button$/) do
- expect(on(CreateAccountPage).create_account_element).to exist
-end
-
-When(/^I submit the form$/) do
- on(CreateAccountPage).create_account
-end
-
-Then(/^an error message is displayed$/) do
- expect(on(CreateAccountPage).error_message_element.class_name).to eq 'errorbox'
-end
diff --git a/www/wiki/tests/browser/features/step_definitions/create_and_follow_wiki_link_steps.rb b/www/wiki/tests/browser/features/step_definitions/create_and_follow_wiki_link_steps.rb
deleted file mode 100644
index 504d3454..00000000
--- a/www/wiki/tests/browser/features/step_definitions/create_and_follow_wiki_link_steps.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-Given(/^I go to the "(.+)" page with content "(.+)"$/) do |page_title, page_content|
- @wikitext = page_content
- api.create_page page_title, page_content
- step "I am on the #{page_title} page"
-end
-
-Given(/^I am on the (.+) page$/) do |article|
- article = article.gsub(/ /, '_')
- visit(ZtargetPage, using_params: { article_name: article })
-end
-
-Given(/^I create page "(.*?)" with content "(.*?)"$/) do |page_title, page_content|
- api.create_page page_title, page_content
-end
-
-When(/^I click the Link Target link$/) do
- on(ZtargetPage).link_target_page_link
-end
-
-Then(/^I should be on the Link Target Test Page$/) do
- expect(@browser.url).to match /Link_Target_Test_Page/
-end
-
-Then(/^the page content should contain "(.*?)"$/) do |content|
- expect(on(ZtargetPage).page_content).to match content
-end
diff --git a/www/wiki/tests/browser/features/step_definitions/edit_page_steps.rb b/www/wiki/tests/browser/features/step_definitions/edit_page_steps.rb
deleted file mode 100644
index 0e0aeb17..00000000
--- a/www/wiki/tests/browser/features/step_definitions/edit_page_steps.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-When(/^I click Edit$/) do
- on(MainPage).edit_link
-end
-
-When(/^I click Preview$/) do
- on(EditPage).preview_button
-end
-
-When(/^I click Show Changes$/) do
- on(EditPage).show_changes_button
-end
-
-When(/^I edit the page with "(.*?)"$/) do |edit_content|
- on(EditPage).edit_page_content_element.send_keys(edit_content + @random_string)
-end
-
-When(/^I save the edit$/) do
- on(EditPage).save_button
-end
-
-Then(/^the edited page content should contain "(.*?)"$/) do |content|
- expect(on(MainPage).page_content).to match(content + @random_string)
-end
diff --git a/www/wiki/tests/browser/features/step_definitions/file_steps.rb b/www/wiki/tests/browser/features/step_definitions/file_steps.rb
deleted file mode 100644
index 15069b2c..00000000
--- a/www/wiki/tests/browser/features/step_definitions/file_steps.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-Given(/^I am at file that does not exist$/) do
- visit(FileDoesNotExistPage, using_params: { page_name: @random_string })
-end
-
-Then(/^page should show that no such file exists$/) do
- expect(on(FileDoesNotExistPage).file_does_not_exist_message_element).to be_visible
-end
diff --git a/www/wiki/tests/browser/features/step_definitions/login_steps.rb b/www/wiki/tests/browser/features/step_definitions/login_steps.rb
deleted file mode 100644
index bda0faac..00000000
--- a/www/wiki/tests/browser/features/step_definitions/login_steps.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-Given(/^I am at Log in page$/) do
- visit LoginPage
-end
-
-When(/^I log in$/) do
- on(LoginPage).login_with(user, password, false)
-end
-
-When(/^I log in with incorrect password$/) do
- on(LoginPage).login_with(user, 'incorrect password', false)
-end
-
-When(/^I log in with incorrect username$/) do
- on(LoginPage).login_with('incorrect username', password, false)
-end
-
-When(/^I log in without entering credentials$/) do
- on(LoginPage).login_with('', '', false)
-end
-
-When(/^I log in without entering password$/) do
- on(LoginPage).login_with(user, '', false)
-end
-
-Then(/^error box should be visible$/) do
- expect(on(LoginErrorPage).error_box_element).to be_visible
-end
-
-Then(/^error box should not be visible$/) do
- expect(on(LoginErrorPage).error_box_element).not_to be_visible
-end
-
-Then(/^feedback should be (.+)$/) do |feedback|
- on(LoginPage) do |page|
- page.feedback_element.when_present.click
- expect(page.feedback).to match Regexp.escape(feedback)
- end
-end
-
-Then(/^Log in element should be there$/) do
- expect(on(LoginPage).login_element).to exist
-end
-
-Then(/^main page should open$/) do
- expect(@browser.url).to eq on(MainPage).class.url
-end
-
-Then(/^Password element should be there$/) do
- expect(on(LoginPage).password_element).to exist
-end
-
-Then(/^there should be a link to (.+)$/) do |text|
- expect(on(LoginPage).username_displayed_element.when_present.text).to eq text
-end
-
-Then(/^Username element should be there$/) do
- expect(on(LoginPage).username_element).to exist
-end
diff --git a/www/wiki/tests/browser/features/step_definitions/main_page_links_steps.rb b/www/wiki/tests/browser/features/step_definitions/main_page_links_steps.rb
deleted file mode 100644
index 7f588c05..00000000
--- a/www/wiki/tests/browser/features/step_definitions/main_page_links_steps.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-Given(/^I open the main wiki URL$/) do
- visit(MainPage)
-end
-
-Then(/^I should see a link for View History$/) do
- expect(on(MainPage).view_history_link_element).to be_visible
-end
-
-Then(/^I should see a link for Edit$/) do
- expect(on(MainPage).edit_link_element).to be_visible
-end
-
-Then(/^I should see a link for Recent changes$/) do
- expect(on(MainPage).recent_changes_link_element).to be_visible
-end
-
-Then(/^I should see a link for Random page$/) do
- expect(on(MainPage).random_page_link_element).to be_visible
-end
-
-Then(/^I should see a link for Help$/) do
- expect(on(MainPage).help_link_element).to be_visible
-end
-
-Then(/^I should see a link for What links here$/) do
- expect(on(MainPage).what_links_here_link_element).to be_visible
-end
-
-Then(/^I should see a link for Related changes$/) do
- expect(on(MainPage).related_changes_link_element).to be_visible
-end
-
-Then(/^I should see a link for Special pages$/) do
- expect(on(MainPage).special_pages_link_element).to be_visible
-end
-
-Then(/^I should see a link for Printable version$/) do
- expect(on(MainPage).printable_version_link_element).to be_visible
-end
-
-Then(/^I should see a link for Permanent link$/) do
- expect(on(MainPage).permanent_link_link_element).to be_visible
-end
-
-Then(/^I should see a link for Page information$/) do
- expect(on(MainPage).page_information_link_element).to be_visible
-end
diff --git a/www/wiki/tests/browser/features/step_definitions/preferences_appearance_steps.rb b/www/wiki/tests/browser/features/step_definitions/preferences_appearance_steps.rb
deleted file mode 100644
index 8ffdaf1d..00000000
--- a/www/wiki/tests/browser/features/step_definitions/preferences_appearance_steps.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-When(/^I click Appearance$/) do
- visit(PreferencesPage).appearance_link_element.when_present.click
-end
-
-When(/^I navigate to Preferences$/) do
- visit(PreferencesPage)
-end
-
-Then(/^I can click Save$/) do
- expect(on(PreferencesPage).save_button_element).to exist
-end
-
-Then(/^I can restore default settings$/) do
- expect(on(PreferencesAppearancePage).restore_default_link_element).to exist
-end
-
-Then(/^I can see local time$/) do
- expect(on(PreferencesAppearancePage).local_time_span_element).to exist
-end
-
-Then(/^I can see time offset section$/) do
- expect(on(PreferencesAppearancePage).time_offset_table_element).to be_visible
-end
-
-Then(/^I can select date format$/) do
- on(PreferencesAppearancePage) do |page|
- expect(page.no_preference_radio_element).to exist
- expect(page.mo_day_year_radio_element).to exist
- expect(page.day_mo_year_radio_element).to exist
- expect(page.year_mo_day_radio_element).to exist
- expect(page.iso_8601_radio_element).to exist
- end
-end
-
-Then(/^I can select image size$/) do
- expect(on(PreferencesAppearancePage).size_select_element).to exist
-end
-
-Then(/^I can select my time zone$/) do
- on(PreferencesAppearancePage) do |page|
- expect(page.time_offset_select_element).to exist
- expect(page.other_offset_element).to exist
- end
-end
-
-Then(/^I can select skin Vector$/) do
- on(PreferencesAppearancePage) do |page|
- expect(page.vector_element).to exist
- end
-end
-
-Then(/^I can select Threshold for stub link$/) do
- expect(on(PreferencesAppearancePage).threshold_select_element).to exist
-end
-
-Then(/^I can select thumbnail size$/) do
- expect(on(PreferencesAppearancePage).thumb_select_element).to exist
-end
-
-Then(/^I can select underline preferences$/) do
- expect(on(PreferencesAppearancePage).underline_select_element).to exist
-end
-
-Then(/^I have advanced options checkboxes$/) do
- on(PreferencesAppearancePage) do |page|
- expect(page.hidden_categories_check_element).to exist
- expect(page.auto_number_check_element).to exist
- end
-end
diff --git a/www/wiki/tests/browser/features/step_definitions/preferences_editing_steps.rb b/www/wiki/tests/browser/features/step_definitions/preferences_editing_steps.rb
deleted file mode 100644
index f691ffdc..00000000
--- a/www/wiki/tests/browser/features/step_definitions/preferences_editing_steps.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-When(/^I click Editing$/) do
- visit(PreferencesPage).editing_link_element.when_present.click
-end
-
-Then(/^I can select edit area font style$/) do
- expect(on(PreferencesEditingPage).edit_area_font_style_select_element.when_present).to exist
-end
-
-Then(/^I can select live preview$/) do
- expect(on(PreferencesEditingPage).live_preview_check_element.when_present).to exist
-end
-
-Then(/^I can select section editing by double clicking$/) do
- expect(on(PreferencesEditingPage).edit_section_double_click_check_element.when_present).to exist
-end
-
-Then(/^I can select section editing by right clicking$/) do
- expect(on(PreferencesEditingPage).edit_section_right_click_check_element.when_present).to exist
-end
-
-Then(/^I can select section editing via edit links$/) do
- expect(on(PreferencesEditingPage).edit_section_edit_link_element.when_present).to exist
-end
-
-Then(/^I can select show edit toolbar$/) do
- expect(on(PreferencesEditingPage).show_edit_toolbar_check_element.when_present).to exist
-end
-
-Then(/^I can select show preview before edit box$/) do
- expect(on(PreferencesEditingPage).preview_on_top_check_element.when_present).to exist
-end
-
-Then(/^I can select show preview on first edit$/) do
- expect(on(PreferencesEditingPage).preview_on_first_check_element.when_present).to exist
-end
-
-Then(/^I can select to prompt me when entering a blank edit summary$/) do
- expect(on(PreferencesEditingPage).forced_edit_summary_check_element.when_present).to exist
-end
-
-Then(/^I can select to warn me when I leave an edit page with unsaved changes$/) do
- expect(on(PreferencesEditingPage).unsaved_changes_check_element.when_present).to exist
-end
diff --git a/www/wiki/tests/browser/features/step_definitions/preferences_user_profile_steps.rb b/www/wiki/tests/browser/features/step_definitions/preferences_user_profile_steps.rb
deleted file mode 100644
index 5660d491..00000000
--- a/www/wiki/tests/browser/features/step_definitions/preferences_user_profile_steps.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-When(/^I click User profile$/) do
- visit(PreferencesPage).user_profile_link_element.when_present.click
-end
-
-Then(/^I can change my gender$/) do
- on(PreferencesUserProfilePage) do |page|
- expect(page.gender_undefined_radio_element).to exist
- expect(page.gender_male_radio_element).to exist
- expect(page.gender_female_radio_element).to exist
- end
-end
-
-Then(/^I can change my language$/) do
- expect(on(PreferencesUserProfilePage).lang_select_element).to exist
-end
-
-Then(/^I can change my signature$/) do
- expect(on(PreferencesUserProfilePage).signature_field_element).to exist
-end
-
-Then(/^I can see my Basic informations$/) do
- expect(on(PreferencesUserProfilePage).basic_info_table_element).to exist
-end
-
-Then(/^I can see my email$/) do
- expect(on(PreferencesUserProfilePage).email_table_element).to exist
-end
-
-Then(/^I can see my signature$/) do
- expect(on(PreferencesUserProfilePage).signature_table_element).to exist
-end
diff --git a/www/wiki/tests/browser/features/step_definitions/view_history_steps.rb b/www/wiki/tests/browser/features/step_definitions/view_history_steps.rb
deleted file mode 100644
index d9b93817..00000000
--- a/www/wiki/tests/browser/features/step_definitions/view_history_steps.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-When(/^I click View History$/) do
- on(ViewHistoryPage).view_history_link
-end
-
-Then(/^I should see a link to a previous version of the page$/) do
- expect(on(ViewHistoryPage).old_version_link_element).to be_visible
-end
diff --git a/www/wiki/tests/browser/features/support/env.rb b/www/wiki/tests/browser/features/support/env.rb
deleted file mode 100644
index c1072b26..00000000
--- a/www/wiki/tests/browser/features/support/env.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-require 'mediawiki_selenium/cucumber'
-require 'mediawiki_selenium/pages'
-require 'mediawiki_selenium/step_definitions'
diff --git a/www/wiki/tests/browser/features/support/hooks.rb b/www/wiki/tests/browser/features/support/hooks.rb
deleted file mode 100644
index 85309f39..00000000
--- a/www/wiki/tests/browser/features/support/hooks.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-# Needed for cucumber --dry-run -f stepdefs
-require 'page-object'
diff --git a/www/wiki/tests/browser/features/support/pages/create_account_page.rb b/www/wiki/tests/browser/features/support/pages/create_account_page.rb
deleted file mode 100644
index 9c1c3ba5..00000000
--- a/www/wiki/tests/browser/features/support/pages/create_account_page.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-class CreateAccountPage
- include PageObject
-
- page_url '<%=params[:page_title]%>'
-
- button(:create_account, id: 'wpCreateaccount')
- div(:error_message, id: 'mw-createacct-status-area')
-end
diff --git a/www/wiki/tests/browser/features/support/pages/edit_page.rb b/www/wiki/tests/browser/features/support/pages/edit_page.rb
deleted file mode 100644
index b0f6bffe..00000000
--- a/www/wiki/tests/browser/features/support/pages/edit_page.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-class EditPage
- include PageObject
-
- text_area(:edit_page_content, id: 'wpTextbox1')
- button(:preview_button, id: 'wpPreview')
- button(:show_changes_button, id: 'wpDiff')
- button(:save_button, id: 'wpSave')
-end
diff --git a/www/wiki/tests/browser/features/support/pages/file_does_not_exist_page.rb b/www/wiki/tests/browser/features/support/pages/file_does_not_exist_page.rb
deleted file mode 100644
index 632e3037..00000000
--- a/www/wiki/tests/browser/features/support/pages/file_does_not_exist_page.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class FileDoesNotExistPage
- include PageObject
-
- page_url 'File:<%=params[:page_name]%>'
-
- div(:file_does_not_exist_message, id: 'mw-imagepage-nofile')
-end
diff --git a/www/wiki/tests/browser/features/support/pages/login_error_page.rb b/www/wiki/tests/browser/features/support/pages/login_error_page.rb
deleted file mode 100644
index 9a1805f3..00000000
--- a/www/wiki/tests/browser/features/support/pages/login_error_page.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class LoginErrorPage
- include PageObject
-
- div(:error_box, class: 'errorbox')
-end
diff --git a/www/wiki/tests/browser/features/support/pages/login_page.rb b/www/wiki/tests/browser/features/support/pages/login_page.rb
deleted file mode 100644
index 8ef1e44c..00000000
--- a/www/wiki/tests/browser/features/support/pages/login_page.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require 'page-object'
-
-class LoginPage
- include PageObject
-
- page_url 'Special:UserLogin'
-
- div(:feedback, class: 'errorbox')
- button(:login, id: 'wpLoginAttempt')
- li(:logout, id: 'pt-logout')
- text_field(:password, id: 'wpPassword1')
- a(:password_strength, text: 'password strength')
- a(:phishing, text: 'phishing')
- text_field(:username, id: 'wpName1')
- a(:username_displayed, title: /Your user page/)
-
- def logged_in_as_element
- @browser.div(id: 'mw-content-text').p.b
- end
-
- def login_with(username, password, wait_for_logout_element = true)
- username_element.when_present.send_keys(username)
- password_element.when_present.send_keys(password)
- login_element.when_present.click
- logout_element.when_present(10) if wait_for_logout_element
- end
-end
diff --git a/www/wiki/tests/browser/features/support/pages/main_page.rb b/www/wiki/tests/browser/features/support/pages/main_page.rb
deleted file mode 100644
index 3092ab5c..00000000
--- a/www/wiki/tests/browser/features/support/pages/main_page.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-class MainPage
- include PageObject
-
- page_url ''
-
- a(:edit_link, css: '#ca-edit a')
- li(:help_link, id: 'n-help')
- div(:page_content, id: 'content')
- li(:page_information_link, id: 't-info')
- li(:permanent_link_link, id: 't-permalink')
- a(:printable_version_link, css: '#t-print a')
- li(:random_page_link, id: 'n-randompage')
- li(:recent_changes_link, id: 'n-recentchanges')
- li(:related_changes_link, id: 't-recentchangeslinked')
- li(:special_pages_link, id: 't-specialpages')
- a(:view_history_link, css: '#ca-history a')
- li(:what_links_here_link, id: 't-whatlinkshere')
-end
diff --git a/www/wiki/tests/browser/features/support/pages/preferences_appearance_page.rb b/www/wiki/tests/browser/features/support/pages/preferences_appearance_page.rb
deleted file mode 100644
index c871e642..00000000
--- a/www/wiki/tests/browser/features/support/pages/preferences_appearance_page.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-class PreferencesAppearancePage
- include PageObject
-
- page_url 'Special:Preferences#mw-prefsection-rendering'
-
- checkbox(:auto_number_check, id: 'mw-input-wpnumberheadings')
- radio_button(:day_mo_year_radio, id: 'mw-input-wpdate-dmy')
- checkbox(:dont_show_aft_check, id: 'mw-input-wparticlefeedback-disable')
- checkbox(:exclude_from_experiments_check, id: 'mw-input-wpvector-noexperiments')
- checkbox(:hidden_categories_check, id: 'mw-input-wpshowhiddencats')
- radio_button(:iso_8601_radio, id: 'mw-input-wpdate-ISO_8601')
- span(:local_time_span, id: 'wpLocalTime')
- radio_button(:mo_day_year_radio, id: 'mw-input-wpdate-mdy')
- radio_button(:no_preference_radio, id: 'mw-input-wpdate-default')
- text_field(:other_offset, id: 'mw-input-wptimecorrection-other')
- a(:restore_default_link, id: 'mw-prefs-restoreprefs')
- select_list(:size_select, id: 'mw-input-wpimagesize')
- select_list(:threshold_select, id: 'mw-input-wpstubthreshold')
- select_list(:time_offset_select, id: 'mw-input-wptimecorrection')
- table(:time_offset_table, id: 'mw-htmlform-timeoffset')
- select_list(:thumb_select, id: 'mw-input-wpthumbsize')
- select_list(:underline_select, id: 'mw-input-wpunderline')
- radio_button(:vector, id: 'mw-input-wpskin-vector')
- radio_button(:year_mo_day_radio, id: 'mw-input-wpdate-ymd')
-end
diff --git a/www/wiki/tests/browser/features/support/pages/preferences_editing_page.rb b/www/wiki/tests/browser/features/support/pages/preferences_editing_page.rb
deleted file mode 100644
index 3b54d458..00000000
--- a/www/wiki/tests/browser/features/support/pages/preferences_editing_page.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-class PreferencesEditingPage
- include PageObject
-
- page_url 'Special:Preferences#mw-prefsection-rendering'
-
- select_list(:edit_area_font_style_select, id: 'mw-input-wpeditfont')
- checkbox(:edit_section_double_click_check, id: 'mw-input-wpeditondblclick')
- checkbox(:edit_section_edit_link, id: 'mw-input-wpeditsectiononrightclick')
- checkbox(:edit_section_right_click_check, id: 'mw-input-wpeditsectiononrightclick')
- checkbox(:forced_edit_summary_check, id: 'mw-input-wpforceeditsummary')
- checkbox(:live_preview_check, id: 'mw-input-wpuselivepreview')
- checkbox(:preview_on_first_check, id: 'mw-input-wppreviewonfirst')
- checkbox(:preview_on_top_check, id: 'mw-input-wppreviewontop')
- checkbox(:show_edit_toolbar_check, id: 'mw-input-wpshowtoolbar')
- checkbox(:unsaved_changes_check, id: 'mw-input-wpuseeditwarning')
-end
diff --git a/www/wiki/tests/browser/features/support/pages/preferences_page.rb b/www/wiki/tests/browser/features/support/pages/preferences_page.rb
deleted file mode 100644
index 1d836ea2..00000000
--- a/www/wiki/tests/browser/features/support/pages/preferences_page.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-class PreferencesPage
- include PageObject
-
- page_url 'Special:Preferences'
-
- a(:appearance_link, id: 'preftab-rendering')
- a(:editing_link, id: 'preftab-editing')
- a(:user_profile_link, id: 'preftab-personal')
- button(:save_button, id: 'prefcontrol')
-end
diff --git a/www/wiki/tests/browser/features/support/pages/preferences_user_profile_page.rb b/www/wiki/tests/browser/features/support/pages/preferences_user_profile_page.rb
deleted file mode 100644
index ab5eb93e..00000000
--- a/www/wiki/tests/browser/features/support/pages/preferences_user_profile_page.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-class PreferencesUserProfilePage
- include PageObject
-
- page_url 'Special:Preferences#mw-prefsection-personal'
-
- table(:basic_info_table, id: 'mw-htmlform-info')
- link(:change_password_link, text: 'Change password')
- table(:email_table, id: 'mw-htmlform-email')
- radio_button(:gender_female_radio, id: 'mw-input-wpgender-male')
- radio_button(:gender_male_radio, id: 'mw-input-wpgender-female')
- radio_button(:gender_undefined_radio, id: 'mw-input-wpgender-unknown')
- select_list(:lang_select, id: 'mw-input-wplanguage')
- checkbox(:remember_password_check, id: 'mw-input-wprememberpassword')
- text_field(:signature_field, id: 'mw-input-wpnickname')
- table(:signature_table, id: 'mw-htmlform-signature')
-end
diff --git a/www/wiki/tests/browser/features/support/pages/view_history_page.rb b/www/wiki/tests/browser/features/support/pages/view_history_page.rb
deleted file mode 100644
index ee4d757a..00000000
--- a/www/wiki/tests/browser/features/support/pages/view_history_page.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-class ViewHistoryPage
- include PageObject
-
- a(:view_history_link, css: '#ca-history a')
- a(:old_version_link, css: '#pagehistory a.mw-changeslist-date')
-end
diff --git a/www/wiki/tests/browser/features/support/pages/ztargetpage.rb b/www/wiki/tests/browser/features/support/pages/ztargetpage.rb
deleted file mode 100644
index da789e5e..00000000
--- a/www/wiki/tests/browser/features/support/pages/ztargetpage.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class ZtargetPage < MainPage
- include PageObject
-
- page_url '<%=params[:article_name]%>'
-
- a(:link_target_page_link, text: 'link to the test target page')
-end
diff --git a/www/wiki/tests/browser/features/view_history.feature b/www/wiki/tests/browser/features/view_history.feature
deleted file mode 100644
index 95136d27..00000000
--- a/www/wiki/tests/browser/features/view_history.feature
+++ /dev/null
@@ -1,11 +0,0 @@
-@chrome @firefox @vagrant
-Feature: View History
-
- Scenario: Edit page and view history
- Given I go to the "History Test Page" page with content "This is a page that will have history"
- When I click Edit
- And I edit the page with "Edited and a random string"
- And I save the edit
- And the edited page content should contain "Edited and a random string"
- And I click View History
- Then I should see a link to a previous version of the page
diff --git a/www/wiki/tests/common/TestSetup.php b/www/wiki/tests/common/TestSetup.php
index 3733e60f..c176a67f 100644
--- a/www/wiki/tests/common/TestSetup.php
+++ b/www/wiki/tests/common/TestSetup.php
@@ -39,7 +39,7 @@ class TestSetup {
$wgMainStash = 'hash';
// Use memory job queue
$wgJobTypeConf = [
- 'default' => [ 'class' => 'JobQueueMemory', 'order' => 'fifo' ],
+ 'default' => [ 'class' => JobQueueMemory::class, 'order' => 'fifo' ],
];
$wgUseDatabaseMessages = false; # Set for future resets
@@ -47,10 +47,10 @@ class TestSetup {
// Assume UTC for testing purposes
$wgLocaltimezone = 'UTC';
- $wgLocalisationCacheConf['storeClass'] = 'LCStoreNull';
+ $wgLocalisationCacheConf['storeClass'] = LCStoreNull::class;
// Do not bother updating search tables
- $wgSearchType = 'SearchEngineDummy';
+ $wgSearchType = SearchEngineDummy::class;
// Generic MediaWiki\Session\SessionManager configuration for tests
// We use CookieSessionProvider because things might be expecting
diff --git a/www/wiki/tests/common/TestsAutoLoader.php b/www/wiki/tests/common/TestsAutoLoader.php
index 8f752df6..abf718d0 100644
--- a/www/wiki/tests/common/TestsAutoLoader.php
+++ b/www/wiki/tests/common/TestsAutoLoader.php
@@ -24,7 +24,7 @@
global $wgAutoloadClasses;
$testDir = __DIR__ . "/..";
-// @codingStandardsIgnoreStart Generic.Files.LineLength.TooLong
+// phpcs:disable Generic.Files.LineLength
$wgAutoloadClasses += [
# tests/common
@@ -62,9 +62,14 @@ $wgAutoloadClasses += [
'TestUser' => "$testDir/phpunit/includes/TestUser.php",
'TestUserRegistry' => "$testDir/phpunit/includes/TestUserRegistry.php",
'LessFileCompilationTest' => "$testDir/phpunit/LessFileCompilationTest.php",
+ 'MediaWikiCoversValidator' => "$testDir/phpunit/MediaWikiCoversValidator.php",
+ 'PHPUnit4And6Compat' => "$testDir/phpunit/PHPUnit4And6Compat.php",
+ 'HamcrestPHPUnitIntegration' => "$testDir/phpunit/HamcrestPHPUnitIntegration.php",
# tests/phpunit/includes
- 'RevisionStorageTest' => "$testDir/phpunit/includes/RevisionStorageTest.php",
+ 'RevisionDbTestBase' => "$testDir/phpunit/includes/RevisionDbTestBase.php",
+ 'RevisionTestModifyableContent' => "$testDir/phpunit/includes/RevisionTestModifyableContent.php",
+ 'RevisionTestModifyableContentHandler' => "$testDir/phpunit/includes/RevisionTestModifyableContentHandler.php",
'TestLogger' => "$testDir/phpunit/includes/TestLogger.php",
# tests/phpunit/includes/api
@@ -74,6 +79,7 @@ $wgAutoloadClasses += [
'ApiTestCase' => "$testDir/phpunit/includes/api/ApiTestCase.php",
'ApiTestCaseUpload' => "$testDir/phpunit/includes/api/ApiTestCaseUpload.php",
'ApiTestContext' => "$testDir/phpunit/includes/api/ApiTestContext.php",
+ 'ApiUploadTestCase' => "$testDir/phpunit/includes/api/ApiUploadTestCase.php",
'MockApi' => "$testDir/phpunit/includes/api/MockApi.php",
'MockApiQueryBase' => "$testDir/phpunit/includes/api/MockApiQueryBase.php",
'UserWrapper' => "$testDir/phpunit/includes/api/UserWrapper.php",
@@ -92,6 +98,8 @@ $wgAutoloadClasses += [
'DummyContentForTesting' => "$testDir/phpunit/mocks/content/DummyContentForTesting.php",
'DummyNonTextContentHandler' => "$testDir/phpunit/mocks/content/DummyNonTextContentHandler.php",
'DummyNonTextContent' => "$testDir/phpunit/mocks/content/DummyNonTextContent.php",
+ 'DummySerializeErrorContentHandler' =>
+ "$testDir/phpunit/mocks/content/DummySerializeErrorContentHandler.php",
'ContentHandlerTest' => "$testDir/phpunit/includes/content/ContentHandlerTest.php",
'JavaScriptContentTest' => "$testDir/phpunit/includes/content/JavaScriptContentTest.php",
'TextContentTest' => "$testDir/phpunit/includes/content/TextContentTest.php",
@@ -103,11 +111,14 @@ $wgAutoloadClasses += [
# tests/phpunit/includes/diff
'FakeDiffOp' => "$testDir/phpunit/includes/diff/FakeDiffOp.php",
+ # tests/phpunit/includes/externalstore
+ 'ExternalStoreForTesting' => "$testDir/phpunit/includes/externalstore/ExternalStoreForTesting.php",
+
# tests/phpunit/includes/logging
'LogFormatterTestCase' => "$testDir/phpunit/includes/logging/LogFormatterTestCase.php",
# tests/phpunit/includes/page
- 'WikiPageTest' => "$testDir/phpunit/includes/page/WikiPageTest.php",
+ 'WikiPageDbTestBase' => "$testDir/phpunit/includes/page/WikiPageDbTestBase.php",
# tests/phpunit/includes/parser
'ParserIntegrationTest' => "$testDir/phpunit/includes/parser/ParserIntegrationTest.php",
@@ -137,6 +148,10 @@ $wgAutoloadClasses += [
'SpecialPageTestBase' => "$testDir/phpunit/includes/specials/SpecialPageTestBase.php",
'SpecialPageExecutor' => "$testDir/phpunit/includes/specials/SpecialPageExecutor.php",
+ # tests/phpunit/includes/Storage
+ 'MediaWiki\Tests\Storage\RevisionSlotsTest' => "$testDir/phpunit/includes/Storage/RevisionSlotsTest.php",
+ 'MediaWiki\Tests\Storage\RevisionRecordTests' => "$testDir/phpunit/includes/Storage/RevisionRecordTests.php",
+
# tests/phpunit/languages
'LanguageClassesTestCase' => "$testDir/phpunit/languages/LanguageClassesTestCase.php",
@@ -144,7 +159,8 @@ $wgAutoloadClasses += [
'GenericArrayObjectTest' => "$testDir/phpunit/includes/libs/GenericArrayObjectTest.php",
# tests/phpunit/maintenance
- 'DumpTestCase' => "$testDir/phpunit/maintenance/DumpTestCase.php",
+ 'MediaWiki\Tests\Maintenance\DumpTestCase' => "$testDir/phpunit/maintenance/DumpTestCase.php",
+ 'MediaWiki\Tests\Maintenance\MaintenanceBaseTestCase' => "$testDir/phpunit/maintenance/MaintenanceBaseTestCase.php",
# tests/phpunit/media
'FakeDimensionFile' => "$testDir/phpunit/includes/media/FakeDimensionFile.php",
@@ -164,9 +180,43 @@ $wgAutoloadClasses += [
'MediaWiki\\Session\\DummySessionBackend'
=> "$testDir/phpunit/mocks/session/DummySessionBackend.php",
'DummySessionProvider' => "$testDir/phpunit/mocks/session/DummySessionProvider.php",
+ 'MockMessageLocalizer' => "$testDir/phpunit/mocks/MockMessageLocalizer.php",
# tests/suites
'ParserTestFileSuite' => "$testDir/phpunit/suites/ParserTestFileSuite.php",
'ParserTestTopLevelSuite' => "$testDir/phpunit/suites/ParserTestTopLevelSuite.php",
];
-// @codingStandardsIgnoreEnd
+// phpcs:enable
+
+/**
+ * Alias any PHPUnit 4 era PHPUnit_... class
+ * to it's PHPUnit 6 replacement. For most classes
+ * this is a direct _ -> \ replacement, but for
+ * some others we might need to maintain a manual
+ * mapping. Once we drop support for PHPUnit 4 this
+ * should be considered deprecated and eventually removed.
+ */
+spl_autoload_register( function ( $class ) {
+ if ( strpos( $class, 'PHPUnit_' ) !== 0 ) {
+ // Skip if it doesn't start with the old prefix
+ return;
+ }
+
+ // Classes that don't map 100%
+ $map = [
+ 'PHPUnit_Framework_TestSuite_DataProvider' => 'PHPUnit\Framework\DataProviderTestSuite',
+ 'PHPUnit_Framework_Error' => 'PHPUnit\Framework\Error\Error',
+ ];
+
+ if ( isset( $map[$class] ) ) {
+ $newForm = $map[$class];
+ } else {
+ $newForm = str_replace( '_', '\\', $class );
+ }
+
+ if ( class_exists( $newForm ) || interface_exists( $newForm ) ) {
+ // If the new class name exists, alias
+ // the old name to it.
+ class_alias( $newForm, $class );
+ }
+} );
diff --git a/www/wiki/tests/integration/includes/http/CurlHttpRequestTest.php b/www/wiki/tests/integration/includes/http/CurlHttpRequestTest.php
index 04f80f43..c1884b87 100644
--- a/www/wiki/tests/integration/includes/http/CurlHttpRequestTest.php
+++ b/www/wiki/tests/integration/includes/http/CurlHttpRequestTest.php
@@ -1,5 +1,9 @@
<?php
+/**
+ * @group large
+ * @covers CurlHttpRequest
+ */
class CurlHttpRequestTest extends MWHttpRequestTestCase {
protected static $httpEngine = 'curl';
}
diff --git a/www/wiki/tests/integration/includes/http/MWHttpRequestTestCase.php b/www/wiki/tests/integration/includes/http/MWHttpRequestTestCase.php
index 81473df2..262eb350 100644
--- a/www/wiki/tests/integration/includes/http/MWHttpRequestTestCase.php
+++ b/www/wiki/tests/integration/includes/http/MWHttpRequestTestCase.php
@@ -2,7 +2,7 @@
use Wikimedia\TestingAccessWrapper;
-class MWHttpRequestTestCase extends PHPUnit_Framework_TestCase {
+abstract class MWHttpRequestTestCase extends PHPUnit\Framework\TestCase {
protected static $httpEngine;
protected $oldHttpEngine;
@@ -195,6 +195,11 @@ class MWHttpRequestTestCase extends PHPUnit_Framework_TestCase {
$this->assertSame( 401, $request->getStatus() );
}
+ public function testFactoryDefaults() {
+ $request = MWHttpRequest::factory( 'http://acme.test' );
+ $this->assertInstanceOf( MWHttpRequest::class, $request );
+ }
+
// --------------------
/**
@@ -242,4 +247,5 @@ class MWHttpRequestTestCase extends PHPUnit_Framework_TestCase {
$this->assertArrayNotHasKey( strtolower( $name ),
array_change_key_case( $cookieJar->cookie, CASE_LOWER ) );
}
+
}
diff --git a/www/wiki/tests/integration/includes/http/PhpHttpRequestTest.php b/www/wiki/tests/integration/includes/http/PhpHttpRequestTest.php
index d0222a5e..8c461f35 100644
--- a/www/wiki/tests/integration/includes/http/PhpHttpRequestTest.php
+++ b/www/wiki/tests/integration/includes/http/PhpHttpRequestTest.php
@@ -1,5 +1,9 @@
<?php
+/**
+ * @group large
+ * @covers PhpHttpRequest
+ */
class PhpHttpRequestTest extends MWHttpRequestTestCase {
protected static $httpEngine = 'php';
}
diff --git a/www/wiki/tests/integration/includes/shell/FirejailCommandTest.php b/www/wiki/tests/integration/includes/shell/FirejailCommandTest.php
new file mode 100644
index 00000000..1e008ee2
--- /dev/null
+++ b/www/wiki/tests/integration/includes/shell/FirejailCommandTest.php
@@ -0,0 +1,78 @@
+<?php
+
+use MediaWiki\Shell\FirejailCommand;
+use MediaWiki\Shell\Shell;
+
+/**
+ * Integration tests to ensure that firejail actually prevents execution.
+ * Meant to run on vagrant, although will probably work on other setups
+ * as long as firejail and sudo has similar config.
+ *
+ * @group large
+ * @group Shell
+ * @covers FirejailCommand
+ */
+class FirejailCommandIntegrationTest extends PHPUnit\Framework\TestCase {
+
+ public function setUp() {
+ parent::setUp();
+ if ( Shell::command( 'which', 'firejail' )->execute()->getExitCode() ) {
+ $this->markTestSkipped( 'firejail not installed' );
+ } elseif ( wfIsWindows() ) {
+ $this->markTestSkipped( 'test supports POSIX environments only' );
+ }
+ }
+
+ public function testSanity() {
+ // Make sure that firejail works at all.
+ $command = new FirejailCommand( 'firejail' );
+ $command
+ ->unsafeParams( 'ls .' )
+ ->restrict( Shell::RESTRICT_DEFAULT );
+ $result = $command->execute();
+ $this->assertSame( 0, $result->getExitCode() );
+ }
+
+ /**
+ * @coversNothing
+ * @dataProvider provideExecute
+ */
+ public function testExecute( $testCommand, $flag ) {
+ if ( preg_match( '/^sudo /', $testCommand ) ) {
+ if ( Shell::command( 'sudo', '-n', 'ls', '/' )->execute()->getExitCode() ) {
+ $this->markTestSkipped( 'need passwordless sudo' );
+ }
+ }
+
+ $command = new FirejailCommand( 'firejail' );
+ $command
+ ->unsafeParams( $testCommand )
+ // If we don't restrict at all, firejail won't be invoked,
+ // so the test will give a false positive if firejail breaks
+ // the command for some non-flag-related reason. Instead,
+ // set some flag that won't get in the way.
+ ->restrict( $flag === Shell::NO_NETWORK ? Shell::PRIVATE_DEV : Shell::NO_NETWORK );
+ $result = $command->execute();
+ $this->assertSame( 0, $result->getExitCode(), 'sanity check' );
+
+ $command = new FirejailCommand( 'firejail' );
+ $command
+ ->unsafeParams( $testCommand )
+ ->restrict( $flag );
+ $result = $command->execute();
+ $this->assertNotSame( 0, $result->getExitCode(), 'real check' );
+ }
+
+ public function provideExecute() {
+ global $IP;
+ return [
+ [ 'sudo -n ls /', Shell::NO_ROOT ],
+ [ 'sudo -n ls /', Shell::SECCOMP ], // not a great test but seems to work
+ [ 'ls /dev/cpu', Shell::PRIVATE_DEV ],
+ [ 'curl -fsSo /dev/null https://wikipedia.org/', Shell::NO_NETWORK ],
+ [ 'exec ls /', Shell::NO_EXECVE ],
+ [ "cat $IP/LocalSettings.php", Shell::NO_LOCALSETTINGS ],
+ ];
+ }
+
+}
diff --git a/www/wiki/tests/parser/ParserTestParserHook.php b/www/wiki/tests/parser/ParserTestParserHook.php
index 5bf50ead..5995012b 100644
--- a/www/wiki/tests/parser/ParserTestParserHook.php
+++ b/www/wiki/tests/parser/ParserTestParserHook.php
@@ -57,8 +57,7 @@ class ParserTestParserHook {
$parser->static_tag_buf = null;
return $tmp;
} else { // wtf?
- return
- "\nCall this extension as <statictag>string</statictag> or as" .
+ return "\nCall this extension as <statictag>string</statictag> or as" .
" <statictag action=flush/>, not in any other way.\n" .
"text: " . var_export( $in, true ) . "\n" .
"argv: " . var_export( $argv, true ) . "\n";
diff --git a/www/wiki/tests/parser/ParserTestResultNormalizer.php b/www/wiki/tests/parser/ParserTestResultNormalizer.php
index 61aa0d79..fbeed97b 100644
--- a/www/wiki/tests/parser/ParserTestResultNormalizer.php
+++ b/www/wiki/tests/parser/ParserTestResultNormalizer.php
@@ -25,11 +25,11 @@ class ParserTestResultNormalizer {
// guaranteed to give accurate results. For example, it may introduce
// differences in the number of line breaks in <pre> tags.
- MediaWiki\suppressWarnings();
+ Wikimedia\suppressWarnings();
if ( !$this->doc->loadXML( '<html><body>' . $text . '</body></html>' ) ) {
$this->invalid = true;
}
- MediaWiki\restoreWarnings();
+ Wikimedia\restoreWarnings();
$this->xpath = new DOMXPath( $this->doc );
$this->body = $this->xpath->query( '//body' )->item( 0 );
}
diff --git a/www/wiki/tests/parser/ParserTestRunner.php b/www/wiki/tests/parser/ParserTestRunner.php
index 5fe2177d..844a43f3 100644
--- a/www/wiki/tests/parser/ParserTestRunner.php
+++ b/www/wiki/tests/parser/ParserTestRunner.php
@@ -290,10 +290,10 @@ class ParserTestRunner {
// Set up null lock managers
$setup['wgLockManagers'] = [ [
'name' => 'fsLockManager',
- 'class' => 'NullLockManager',
+ 'class' => NullLockManager::class,
], [
'name' => 'nullLockManager',
- 'class' => 'NullLockManager',
+ 'class' => NullLockManager::class,
] ];
$reset = function () {
LockManagerGroup::destroySingletons();
@@ -384,7 +384,7 @@ class ParserTestRunner {
// Changing wgExtraNamespaces invalidates caches in MWNamespace and
// any live Language object, both on setup and teardown
$reset = function () {
- MWNamespace::getCanonicalNamespaces( true );
+ MWNamespace::clearCaches();
$GLOBALS['wgContLang']->resetNamespaces();
};
$setup[] = $reset;
@@ -435,7 +435,7 @@ class ParserTestRunner {
return new RepoGroup(
[
- 'class' => 'MockLocalRepo',
+ 'class' => MockLocalRepo::class,
'name' => 'local',
'url' => 'http://example.com/images',
'hashLevels' => 2,
@@ -615,9 +615,13 @@ class ParserTestRunner {
return false;
} );// hooks::register
+ // Reset the service in case any other tests already cached some prefixes.
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'InterwikiLookup' );
+
return function () {
// Tear down
Hooks::clear( 'InterwikiLoadPrefix' );
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'InterwikiLookup' );
};
}
@@ -636,7 +640,6 @@ class ParserTestRunner {
/**
* Remove last character if it is a newline
- * @group utility
* @param string $s
* @return string
*/
@@ -708,15 +711,15 @@ class ParserTestRunner {
public function meetsRequirements( $requirements ) {
foreach ( $requirements as $requirement ) {
switch ( $requirement['type'] ) {
- case 'hook':
- $ok = $this->requireHook( $requirement['name'] );
- break;
- case 'functionHook':
- $ok = $this->requireFunctionHook( $requirement['name'] );
- break;
- case 'transparentHook':
- $ok = $this->requireTransparentHook( $requirement['name'] );
- break;
+ case 'hook':
+ $ok = $this->requireHook( $requirement['name'] );
+ break;
+ case 'functionHook':
+ $ok = $this->requireFunctionHook( $requirement['name'] );
+ break;
+ case 'transparentHook':
+ $ok = $this->requireTransparentHook( $requirement['name'] );
+ break;
}
if ( !$ok ) {
return false;
@@ -811,10 +814,6 @@ class ParserTestRunner {
$options = ParserOptions::newFromContext( $context );
$options->setTimestamp( $this->getFakeTimestamp() );
- if ( !isset( $opts['wrap'] ) ) {
- $options->setWrapOutputClass( false );
- }
-
if ( isset( $opts['tidy'] ) ) {
if ( !$this->tidySupport->isEnabled() ) {
$this->recorder->skipped( $test, 'tidy extension is not installed' );
@@ -835,6 +834,19 @@ class ParserTestRunner {
$parser = $this->getParser( $preprocessor );
$title = Title::newFromText( $titleText );
+ if ( isset( $opts['styletag'] ) ) {
+ // For testing the behavior of <style> (including those deduplicated
+ // into <link> tags), add tag hooks to allow them to be generated.
+ $parser->setHook( 'style', function ( $content, $attributes, $parser ) {
+ $marker = Parser::MARKER_PREFIX . '-style-' . md5( $content ) . Parser::MARKER_SUFFIX;
+ $parser->mStripState->addNoWiki( $marker, $content );
+ return Html::inlineStyle( $marker, 'all', $attributes );
+ } );
+ $parser->setHook( 'link', function ( $content, $attributes, $parser ) {
+ return Html::element( 'link', $attributes );
+ } );
+ }
+
if ( isset( $opts['pst'] ) ) {
$out = $parser->preSaveTransform( $test['input'], $title, $user, $options );
$output = $parser->getOutput();
@@ -853,8 +865,10 @@ class ParserTestRunner {
$out = $parser->getPreloadText( $test['input'], $title, $options );
} else {
$output = $parser->parse( $test['input'], $title, $options, true, true, 1337 );
- $output->setTOCEnabled( !isset( $opts['notoc'] ) );
- $out = $output->getText();
+ $out = $output->getText( [
+ 'allowTOC' => !isset( $opts['notoc'] ),
+ 'unwrap' => !isset( $opts['wrap'] ),
+ ] );
if ( isset( $opts['tidy'] ) ) {
$out = preg_replace( '/\s+$/', '', $out );
}
@@ -891,7 +905,7 @@ class ParserTestRunner {
if ( isset( $output ) && isset( $opts['showflags'] ) ) {
$actualFlags = array_keys( TestingAccessWrapper::newFromObject( $output )->mFlags );
sort( $actualFlags );
- $out .= "\nflags=" . join( ', ', $actualFlags );
+ $out .= "\nflags=" . implode( ', ', $actualFlags );
}
ScopedCallback::consume( $teardownGuard );
@@ -1098,6 +1112,7 @@ class ParserTestRunner {
// Set content language. This invalidates the magic word cache and title services
$lang = Language::factory( $langCode );
+ $lang->resetNamespaces();
$setup['wgContLang'] = $lang;
$reset = function () {
MagicWord::clearCache();
@@ -1150,6 +1165,8 @@ class ParserTestRunner {
* @return array
*/
private function listTables() {
+ global $wgCommentTableSchemaMigrationStage, $wgActorTableSchemaMigrationStage;
+
$tables = [ 'user', 'user_properties', 'user_former_groups', 'page', 'page_restrictions',
'protected_titles', 'revision', 'ip_changes', 'text', 'pagelinks', 'imagelinks',
'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks',
@@ -1159,6 +1176,19 @@ class ParserTestRunner {
'archive', 'user_groups', 'page_props', 'category'
];
+ if ( $wgCommentTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+ // The new tables for comments are in use
+ $tables[] = 'comment';
+ $tables[] = 'revision_comment_temp';
+ $tables[] = 'image_comment_temp';
+ }
+
+ if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+ // The new tables for actors are in use
+ $tables[] = 'actor';
+ $tables[] = 'revision_actor_temp';
+ }
+
if ( in_array( $this->db->getType(), [ 'mysql', 'sqlite', 'oracle' ] ) ) {
array_push( $tables, 'searchindex' );
}
@@ -1592,11 +1622,21 @@ class ParserTestRunner {
throw new MWException( "invalid title '$name' at $file:$line\n" );
}
+ $newContent = ContentHandler::makeContent( $text, $title );
+
$page = WikiPage::factory( $title );
$page->loadPageData( 'fromdbmaster' );
if ( $page->exists() ) {
- throw new MWException( "duplicate article '$name' at $file:$line\n" );
+ $content = $page->getContent( Revision::RAW );
+ // Only reject the title, if the content/content model is different.
+ // This makes it easier to create Template:(( or Template:)) in different extensions
+ if ( $newContent->equals( $content ) ) {
+ return;
+ }
+ throw new MWException(
+ "duplicate article '$name' with different content at $file:$line\n"
+ );
}
// Use mock parser, to make debugging of actual parser tests simpler.
@@ -1606,7 +1646,7 @@ class ParserTestRunner {
$restore = $this->executeSetupSnippets( [ 'wgParser' => new ParserTestMockParser ] );
try {
$status = $page->doEditContent(
- ContentHandler::makeContent( $text, $title ),
+ $newContent,
'',
EDIT_NEW | EDIT_INTERNAL
);
diff --git a/www/wiki/tests/parser/PhpunitTestRecorder.php b/www/wiki/tests/parser/PhpunitTestRecorder.php
index 2f82ca72..1a2cfc91 100644
--- a/www/wiki/tests/parser/PhpunitTestRecorder.php
+++ b/www/wiki/tests/parser/PhpunitTestRecorder.php
@@ -3,7 +3,7 @@
class PhpunitTestRecorder extends TestRecorder {
private $testCase;
- public function setTestCase( PHPUnit_Framework_TestCase $testCase ) {
+ public function setTestCase( PHPUnit\Framework\TestCase $testCase ) {
$this->testCase = $testCase;
}
diff --git a/www/wiki/tests/parser/TestFileEditor.php b/www/wiki/tests/parser/TestFileEditor.php
index 7f646710..1bee31ea 100644
--- a/www/wiki/tests/parser/TestFileEditor.php
+++ b/www/wiki/tests/parser/TestFileEditor.php
@@ -162,18 +162,18 @@ class TestFileEditor {
if ( isset( $changes[$sectionName] ) ) {
$change = $changes[$sectionName];
switch ( $change['op'] ) {
- case 'rename':
- $test[$i]['name'] = $change['value'];
- $test[$i]['headingLine'] = "!! {$change['value']}";
- break;
- case 'update':
- $test[$i]['contents'] = $change['value'];
- break;
- case 'delete':
- $test[$i]['deleted'] = true;
- break;
- default:
- throw new Exception( "Unknown op: ${change['op']}" );
+ case 'rename':
+ $test[$i]['name'] = $change['value'];
+ $test[$i]['headingLine'] = "!! {$change['value']}";
+ break;
+ case 'update':
+ $test[$i]['contents'] = $change['value'];
+ break;
+ case 'delete':
+ $test[$i]['deleted'] = true;
+ break;
+ default:
+ throw new Exception( "Unknown op: ${change['op']}" );
}
// Acknowledge
// Note that we use the old section name for the rename op
diff --git a/www/wiki/tests/parser/TidySupport.php b/www/wiki/tests/parser/TidySupport.php
index 6aed02f3..559960de 100644
--- a/www/wiki/tests/parser/TidySupport.php
+++ b/www/wiki/tests/parser/TidySupport.php
@@ -32,7 +32,7 @@ class TidySupport {
* @param bool $useConfiguration
*/
public function __construct( $useConfiguration = false ) {
- global $IP, $wgUseTidy, $wgTidyBin, $wgTidyInternal, $wgTidyConfig,
+ global $wgUseTidy, $wgTidyBin, $wgTidyInternal, $wgTidyConfig,
$wgTidyConf, $wgTidyOpts;
$this->enabled = true;
@@ -55,26 +55,7 @@ class TidySupport {
$this->enabled = false;
}
} else {
- $this->config = [
- 'tidyConfigFile' => "$IP/includes/tidy/tidy.conf",
- 'tidyCommandLine' => '',
- ];
- if ( extension_loaded( 'tidy' ) && ( wfIsHHVM() || class_exists( 'tidy' ) ) ) {
- $this->config['driver'] = wfIsHHVM() ? 'RaggettInternalHHVM' : 'RaggettInternalPHP';
- } else {
- if ( is_executable( $wgTidyBin ) ) {
- $this->config['driver'] = 'RaggettExternal';
- $this->config['tidyBin'] = $wgTidyBin;
- } else {
- $path = Installer::locateExecutableInDefaultPaths( $wgTidyBin );
- if ( $path !== false ) {
- $this->config['driver'] = 'RaggettExternal';
- $this->config['tidyBin'] = $wgTidyBin;
- } else {
- $this->enabled = false;
- }
- }
- }
+ $this->config = [ 'driver' => 'RemexHtml' ];
}
if ( !$this->enabled ) {
$this->config = [ 'driver' => 'disabled' ];
diff --git a/www/wiki/tests/parser/editTests.php b/www/wiki/tests/parser/editTests.php
index a9704e69..3e18370a 100644
--- a/www/wiki/tests/parser/editTests.php
+++ b/www/wiki/tests/parser/editTests.php
@@ -62,10 +62,9 @@ class ParserEditTests extends Maintenance {
}
protected function setupFileData() {
- global $wgParserTestFiles;
$this->testFiles = [];
$this->testCount = 0;
- foreach ( $wgParserTestFiles as $file ) {
+ foreach ( ParserTestRunner::getParserTestFiles() as $file ) {
$fileInfo = TestFileReader::read( $file );
$this->testFiles[$file] = $fileInfo;
$this->testCount += count( $fileInfo['tests'] );
@@ -419,8 +418,7 @@ class ParserEditTests extends Maintenance {
print "Wrote updated file\n";
} else {
print "Cannot write updated file, here is a patch you can paste:\n\n";
- print
- "--- {$fileName}\n" .
+ print "--- {$fileName}\n" .
"+++ {$fileName}~\n" .
$this->unifiedDiff( $text, $result ) .
"\n";
diff --git a/www/wiki/tests/parser/fuzzTest.php b/www/wiki/tests/parser/fuzzTest.php
index 9a2a9c93..eb4181c7 100644
--- a/www/wiki/tests/parser/fuzzTest.php
+++ b/www/wiki/tests/parser/fuzzTest.php
@@ -165,9 +165,9 @@ class ParserFuzzTest extends Maintenance {
function guessVarSize( $var ) {
$length = 0;
try {
- MediaWiki\suppressWarnings();
+ Wikimedia\suppressWarnings();
$length = strlen( serialize( $var ) );
- MediaWiki\restoreWarnings();
+ Wikimedia\restoreWarnings();
} catch ( Exception $e ) {
}
return $length;
diff --git a/www/wiki/tests/parser/parserTest.inc b/www/wiki/tests/parser/parserTest.inc
deleted file mode 100644
index cdc21c85..00000000
--- a/www/wiki/tests/parser/parserTest.inc
+++ /dev/null
@@ -1,1666 +0,0 @@
-<?php
-/**
- * Helper code for the MediaWiki parser test suite. Some code is duplicated
- * in PHPUnit's NewParserTests.php, so you'll probably want to update both
- * at the same time.
- *
- * Copyright © 2004, 2010 Brion Vibber <brion@pobox.com>
- * https://www.mediawiki.org/
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @todo Make this more independent of the configuration (and if possible the database)
- * @todo document
- * @file
- * @ingroup Testing
- */
-
-/**
- * @ingroup Testing
- */
-class ParserTest {
- /**
- * @var bool $color whereas output should be colorized
- */
- private $color;
-
- /**
- * @var bool $showOutput Show test output
- */
- private $showOutput;
-
- /**
- * @var bool $useTemporaryTables Use temporary tables for the temporary database
- */
- private $useTemporaryTables = true;
-
- /**
- * @var bool $databaseSetupDone True if the database has been set up
- */
- private $databaseSetupDone = false;
-
- /**
- * Our connection to the database
- * @var DatabaseBase
- */
- private $db;
-
- /**
- * Database clone helper
- * @var CloneDatabase
- */
- private $dbClone;
-
- /**
- * @var DjVuSupport
- */
- private $djVuSupport;
-
- /**
- * @var TidySupport
- */
- private $tidySupport;
-
- private $maxFuzzTestLength = 300;
- private $fuzzSeed = 0;
- private $memoryLimit = 50;
- private $uploadDir = null;
-
- public $regex = "";
- private $savedGlobals = [];
-
- /**
- * Sets terminal colorization and diff/quick modes depending on OS and
- * command-line options (--color and --quick).
- * @param array $options
- */
- public function __construct( $options = [] ) {
- # Only colorize output if stdout is a terminal.
- $this->color = !wfIsWindows() && Maintenance::posix_isatty( 1 );
-
- if ( isset( $options['color'] ) ) {
- switch ( $options['color'] ) {
- case 'no':
- $this->color = false;
- break;
- case 'yes':
- default:
- $this->color = true;
- break;
- }
- }
-
- $this->term = $this->color
- ? new AnsiTermColorer()
- : new DummyTermColorer();
-
- $this->showDiffs = !isset( $options['quick'] );
- $this->showProgress = !isset( $options['quiet'] );
- $this->showFailure = !(
- isset( $options['quiet'] )
- && ( isset( $options['record'] )
- || isset( $options['compare'] ) ) ); // redundant output
-
- $this->showOutput = isset( $options['show-output'] );
-
- if ( isset( $options['filter'] ) ) {
- $options['regex'] = $options['filter'];
- }
-
- if ( isset( $options['regex'] ) ) {
- if ( isset( $options['record'] ) ) {
- echo "Warning: --record cannot be used with --regex, disabling --record\n";
- unset( $options['record'] );
- }
- $this->regex = $options['regex'];
- } else {
- # Matches anything
- $this->regex = '';
- }
-
- $this->setupRecorder( $options );
- $this->keepUploads = isset( $options['keep-uploads'] );
-
- if ( $this->keepUploads ) {
- $this->uploadDir = wfTempDir() . '/mwParser-images';
- } else {
- $this->uploadDir = wfTempDir() . "/mwParser-" . mt_rand() . "-images";
- }
-
- if ( isset( $options['seed'] ) ) {
- $this->fuzzSeed = intval( $options['seed'] ) - 1;
- }
-
- $this->runDisabled = isset( $options['run-disabled'] );
- $this->runParsoid = isset( $options['run-parsoid'] );
-
- $this->djVuSupport = new DjVuSupport();
- $this->tidySupport = new TidySupport();
- if ( !$this->tidySupport->isEnabled() ) {
- echo "Warning: tidy is not installed, skipping some tests\n";
- }
-
- if ( !extension_loaded( 'gd' ) ) {
- echo "Warning: GD extension is not present, thumbnailing tests will probably fail\n";
- }
-
- $this->hooks = [];
- $this->functionHooks = [];
- $this->transparentHooks = [];
- $this->setUp();
- }
-
- function setUp() {
- global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc,
- $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory,
- $wgExtraNamespaces, $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo,
- $wgExtraInterlanguageLinkPrefixes, $wgLocalInterwikis,
- $parserMemc, $wgThumbnailScriptPath, $wgScriptPath, $wgResourceBasePath,
- $wgArticlePath, $wgScript, $wgStylePath, $wgExtensionAssetsPath,
- $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType, $wgLockManagers;
-
- $wgScriptPath = '';
- $wgScript = '/index.php';
- $wgStylePath = '/skins';
- $wgResourceBasePath = '';
- $wgExtensionAssetsPath = '/extensions';
- $wgArticlePath = '/wiki/$1';
- $wgThumbnailScriptPath = false;
- $wgLockManagers = [ [
- 'name' => 'fsLockManager',
- 'class' => 'FSLockManager',
- 'lockDirectory' => $this->uploadDir . '/lockdir',
- ], [
- 'name' => 'nullLockManager',
- 'class' => 'NullLockManager',
- ] ];
- $wgLocalFileRepo = [
- 'class' => 'LocalRepo',
- 'name' => 'local',
- 'url' => 'http://example.com/images',
- 'hashLevels' => 2,
- 'transformVia404' => false,
- 'backend' => new FSFileBackend( [
- 'name' => 'local-backend',
- 'wikiId' => wfWikiID(),
- 'containerPaths' => [
- 'local-public' => $this->uploadDir . '/public',
- 'local-thumb' => $this->uploadDir . '/thumb',
- 'local-temp' => $this->uploadDir . '/temp',
- 'local-deleted' => $this->uploadDir . '/deleted',
- ]
- ] )
- ];
- $wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface';
- $wgNamespaceAliases['Image'] = NS_FILE;
- $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
- # add a namespace shadowing a interwiki link, to test
- # proper precedence when resolving links. (bug 51680)
- $wgExtraNamespaces[100] = 'MemoryAlpha';
-
- // XXX: tests won't run without this (for CACHE_DB)
- if ( $wgMainCacheType === CACHE_DB ) {
- $wgMainCacheType = CACHE_NONE;
- }
- if ( $wgMessageCacheType === CACHE_DB ) {
- $wgMessageCacheType = CACHE_NONE;
- }
- if ( $wgParserCacheType === CACHE_DB ) {
- $wgParserCacheType = CACHE_NONE;
- }
-
- DeferredUpdates::clearPendingUpdates();
- $wgMemc = wfGetMainCache(); // checks $wgMainCacheType
- $messageMemc = wfGetMessageCacheStorage();
- $parserMemc = wfGetParserCacheStorage();
-
- RequestContext::resetMain();
- $context = new RequestContext;
- $wgUser = new User;
- $wgLang = $context->getLanguage();
- $wgOut = $context->getOutput();
- $wgRequest = $context->getRequest();
- $wgParser = new StubObject( 'wgParser', $wgParserConf['class'], [ $wgParserConf ] );
-
- if ( $wgStyleDirectory === false ) {
- $wgStyleDirectory = "$IP/skins";
- }
-
- self::setupInterwikis();
- $wgLocalInterwikis = [ 'local', 'mi' ];
- // "extra language links"
- // see https://gerrit.wikimedia.org/r/111390
- array_push( $wgExtraInterlanguageLinkPrefixes, 'mul' );
-
- // Reset namespace cache
- MWNamespace::getCanonicalNamespaces( true );
- Language::factory( 'en' )->resetNamespaces();
- }
-
- /**
- * Insert hardcoded interwiki in the lookup table.
- *
- * This function insert a set of well known interwikis that are used in
- * the parser tests. They can be considered has fixtures are injected in
- * the interwiki cache by using the 'InterwikiLoadPrefix' hook.
- * Since we are not interested in looking up interwikis in the database,
- * the hook completely replace the existing mechanism (hook returns false).
- */
- public static function setupInterwikis() {
- # Hack: insert a few Wikipedia in-project interwiki prefixes,
- # for testing inter-language links
- Hooks::register( 'InterwikiLoadPrefix', function ( $prefix, &$iwData ) {
- static $testInterwikis = [
- 'local' => [
- 'iw_url' => 'http://doesnt.matter.org/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 0 ],
- 'wikipedia' => [
- 'iw_url' => 'http://en.wikipedia.org/wiki/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 0 ],
- 'meatball' => [
- 'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 0 ],
- 'memoryalpha' => [
- 'iw_url' => 'http://www.memory-alpha.org/en/index.php/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 0 ],
- 'zh' => [
- 'iw_url' => 'http://zh.wikipedia.org/wiki/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 1 ],
- 'es' => [
- 'iw_url' => 'http://es.wikipedia.org/wiki/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 1 ],
- 'fr' => [
- 'iw_url' => 'http://fr.wikipedia.org/wiki/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 1 ],
- 'ru' => [
- 'iw_url' => 'http://ru.wikipedia.org/wiki/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 1 ],
- 'mi' => [
- 'iw_url' => 'http://mi.wikipedia.org/wiki/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 1 ],
- 'mul' => [
- 'iw_url' => 'http://wikisource.org/wiki/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 1 ],
- ];
- if ( array_key_exists( $prefix, $testInterwikis ) ) {
- $iwData = $testInterwikis[$prefix];
- }
-
- // We only want to rely on the above fixtures
- return false;
- } );// hooks::register
- }
-
- /**
- * Remove the hardcoded interwiki lookup table.
- */
- public static function tearDownInterwikis() {
- Hooks::clear( 'InterwikiLoadPrefix' );
- }
-
- public function setupRecorder( $options ) {
- if ( isset( $options['record'] ) ) {
- $this->recorder = new DbTestRecorder( $this );
- $this->recorder->version = isset( $options['setversion'] ) ?
- $options['setversion'] : SpecialVersion::getVersion();
- } elseif ( isset( $options['compare'] ) ) {
- $this->recorder = new DbTestPreviewer( $this );
- } else {
- $this->recorder = new TestRecorder( $this );
- }
- }
-
- /**
- * Remove last character if it is a newline
- * @group utility
- * @param string $s
- * @return string
- */
- public static function chomp( $s ) {
- if ( substr( $s, -1 ) === "\n" ) {
- return substr( $s, 0, -1 );
- } else {
- return $s;
- }
- }
-
- /**
- * Run a fuzz test series
- * Draw input from a set of test files
- * @param array $filenames
- */
- function fuzzTest( $filenames ) {
- $GLOBALS['wgContLang'] = Language::factory( 'en' );
- $dict = $this->getFuzzInput( $filenames );
- $dictSize = strlen( $dict );
- $logMaxLength = log( $this->maxFuzzTestLength );
- $this->setupDatabase();
- ini_set( 'memory_limit', $this->memoryLimit * 1048576 );
-
- $numTotal = 0;
- $numSuccess = 0;
- $user = new User;
- $opts = ParserOptions::newFromUser( $user );
- $title = Title::makeTitle( NS_MAIN, 'Parser_test' );
-
- while ( true ) {
- // Generate test input
- mt_srand( ++$this->fuzzSeed );
- $totalLength = mt_rand( 1, $this->maxFuzzTestLength );
- $input = '';
-
- while ( strlen( $input ) < $totalLength ) {
- $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength;
- $hairLength = min( intval( exp( $logHairLength ) ), $dictSize );
- $offset = mt_rand( 0, $dictSize - $hairLength );
- $input .= substr( $dict, $offset, $hairLength );
- }
-
- $this->setupGlobals();
- $parser = $this->getParser();
-
- // Run the test
- try {
- $parser->parse( $input, $title, $opts );
- $fail = false;
- } catch ( Exception $exception ) {
- $fail = true;
- }
-
- if ( $fail ) {
- echo "Test failed with seed {$this->fuzzSeed}\n";
- echo "Input:\n";
- printf( "string(%d) \"%s\"\n\n", strlen( $input ), $input );
- echo "$exception\n";
- } else {
- $numSuccess++;
- }
-
- $numTotal++;
- $this->teardownGlobals();
- $parser->__destruct();
-
- if ( $numTotal % 100 == 0 ) {
- $usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 );
- echo "{$this->fuzzSeed}: $numSuccess/$numTotal (mem: $usage%)\n";
- if ( $usage > 90 ) {
- echo "Out of memory:\n";
- $memStats = $this->getMemoryBreakdown();
-
- foreach ( $memStats as $name => $usage ) {
- echo "$name: $usage\n";
- }
- $this->abort();
- }
- }
- }
- }
-
- /**
- * Get an input dictionary from a set of parser test files
- * @param array $filenames
- * @return string
- */
- function getFuzzInput( $filenames ) {
- $dict = '';
-
- foreach ( $filenames as $filename ) {
- $contents = file_get_contents( $filename );
- preg_match_all(
- '/!!\s*(input|wikitext)\n(.*?)\n!!\s*(result|html|html\/\*|html\/php)/s',
- $contents,
- $matches
- );
-
- foreach ( $matches[1] as $match ) {
- $dict .= $match . "\n";
- }
- }
-
- return $dict;
- }
-
- /**
- * Get a memory usage breakdown
- * @return array
- */
- function getMemoryBreakdown() {
- $memStats = [];
-
- foreach ( $GLOBALS as $name => $value ) {
- $memStats['$' . $name] = strlen( serialize( $value ) );
- }
-
- $classes = get_declared_classes();
-
- foreach ( $classes as $class ) {
- $rc = new ReflectionClass( $class );
- $props = $rc->getStaticProperties();
- $memStats[$class] = strlen( serialize( $props ) );
- $methods = $rc->getMethods();
-
- foreach ( $methods as $method ) {
- $memStats[$class] += strlen( serialize( $method->getStaticVariables() ) );
- }
- }
-
- $functions = get_defined_functions();
-
- foreach ( $functions['user'] as $function ) {
- $rf = new ReflectionFunction( $function );
- $memStats["$function()"] = strlen( serialize( $rf->getStaticVariables() ) );
- }
-
- asort( $memStats );
-
- return $memStats;
- }
-
- function abort() {
- $this->abort();
- }
-
- /**
- * Run a series of tests listed in the given text files.
- * Each test consists of a brief description, wikitext input,
- * and the expected HTML output.
- *
- * Prints status updates on stdout and counts up the total
- * number and percentage of passed tests.
- *
- * @param array $filenames Array of strings
- * @return bool True if passed all tests, false if any tests failed.
- */
- public function runTestsFromFiles( $filenames ) {
- $ok = false;
-
- // be sure, ParserTest::addArticle has correct language set,
- // so that system messages gets into the right language cache
- $GLOBALS['wgLanguageCode'] = 'en';
- $GLOBALS['wgContLang'] = Language::factory( 'en' );
-
- $this->recorder->start();
- try {
- $this->setupDatabase();
- $ok = true;
-
- foreach ( $filenames as $filename ) {
- echo "Running parser tests from: $filename\n";
- $tests = new TestFileIterator( $filename, $this );
- $ok = $this->runTests( $tests ) && $ok;
- }
-
- $this->teardownDatabase();
- $this->recorder->report();
- } catch ( DBError $e ) {
- echo $e->getMessage();
- }
- $this->recorder->end();
-
- return $ok;
- }
-
- function runTests( $tests ) {
- $ok = true;
-
- foreach ( $tests as $t ) {
- $result =
- $this->runTest( $t['test'], $t['input'], $t['result'], $t['options'], $t['config'] );
- $ok = $ok && $result;
- $this->recorder->record( $t['test'], $t['subtest'], $result );
- }
-
- if ( $this->showProgress ) {
- print "\n";
- }
-
- return $ok;
- }
-
- /**
- * Get a Parser object
- *
- * @param string $preprocessor
- * @return Parser
- */
- function getParser( $preprocessor = null ) {
- global $wgParserConf;
-
- $class = $wgParserConf['class'];
- $parser = new $class( [ 'preprocessorClass' => $preprocessor ] + $wgParserConf );
-
- foreach ( $this->hooks as $tag => $callback ) {
- $parser->setHook( $tag, $callback );
- }
-
- foreach ( $this->functionHooks as $tag => $bits ) {
- list( $callback, $flags ) = $bits;
- $parser->setFunctionHook( $tag, $callback, $flags );
- }
-
- foreach ( $this->transparentHooks as $tag => $callback ) {
- $parser->setTransparentTagHook( $tag, $callback );
- }
-
- Hooks::run( 'ParserTestParser', [ &$parser ] );
-
- return $parser;
- }
-
- /**
- * Run a given wikitext input through a freshly-constructed wiki parser,
- * and compare the output against the expected results.
- * Prints status and explanatory messages to stdout.
- *
- * @param string $desc Test's description
- * @param string $input Wikitext to try rendering
- * @param string $result Result to output
- * @param array $opts Test's options
- * @param string $config Overrides for global variables, one per line
- * @return bool
- */
- public function runTest( $desc, $input, $result, $opts, $config ) {
- if ( $this->showProgress ) {
- $this->showTesting( $desc );
- }
-
- $opts = $this->parseOptions( $opts );
- $context = $this->setupGlobals( $opts, $config );
-
- $user = $context->getUser();
- $options = ParserOptions::newFromContext( $context );
-
- if ( isset( $opts['djvu'] ) ) {
- if ( !$this->djVuSupport->isEnabled() ) {
- return $this->showSkipped();
- }
- }
-
- if ( isset( $opts['tidy'] ) ) {
- if ( !$this->tidySupport->isEnabled() ) {
- return $this->showSkipped();
- } else {
- $options->setTidy( true );
- }
- }
-
- if ( isset( $opts['title'] ) ) {
- $titleText = $opts['title'];
- } else {
- $titleText = 'Parser test';
- }
-
- ObjectCache::getMainWANInstance()->clearProcessCache();
- $local = isset( $opts['local'] );
- $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null;
- $parser = $this->getParser( $preprocessor );
- $title = Title::newFromText( $titleText );
-
- if ( isset( $opts['pst'] ) ) {
- $out = $parser->preSaveTransform( $input, $title, $user, $options );
- } elseif ( isset( $opts['msg'] ) ) {
- $out = $parser->transformMsg( $input, $options, $title );
- } elseif ( isset( $opts['section'] ) ) {
- $section = $opts['section'];
- $out = $parser->getSection( $input, $section );
- } elseif ( isset( $opts['replace'] ) ) {
- $section = $opts['replace'][0];
- $replace = $opts['replace'][1];
- $out = $parser->replaceSection( $input, $section, $replace );
- } elseif ( isset( $opts['comment'] ) ) {
- $out = Linker::formatComment( $input, $title, $local );
- } elseif ( isset( $opts['preload'] ) ) {
- $out = $parser->getPreloadText( $input, $title, $options );
- } else {
- $output = $parser->parse( $input, $title, $options, true, true, 1337 );
- $output->setTOCEnabled( !isset( $opts['notoc'] ) );
- $out = $output->getText();
- if ( isset( $opts['tidy'] ) ) {
- $out = preg_replace( '/\s+$/', '', $out );
- }
-
- if ( isset( $opts['showtitle'] ) ) {
- if ( $output->getTitleText() ) {
- $title = $output->getTitleText();
- }
-
- $out = "$title\n$out";
- }
-
- if ( isset( $opts['showindicators'] ) ) {
- $indicators = '';
- foreach ( $output->getIndicators() as $id => $content ) {
- $indicators .= "$id=$content\n";
- }
- $out = $indicators . $out;
- }
-
- if ( isset( $opts['ill'] ) ) {
- $out = implode( ' ', $output->getLanguageLinks() );
- } elseif ( isset( $opts['cat'] ) ) {
- $outputPage = $context->getOutput();
- $outputPage->addCategoryLinks( $output->getCategories() );
- $cats = $outputPage->getCategoryLinks();
-
- if ( isset( $cats['normal'] ) ) {
- $out = implode( ' ', $cats['normal'] );
- } else {
- $out = '';
- }
- }
- }
-
- $this->teardownGlobals();
-
- $testResult = new ParserTestResult( $desc );
- $testResult->expected = $result;
- $testResult->actual = $out;
-
- return $this->showTestResult( $testResult );
- }
-
- /**
- * Refactored in 1.22 to use ParserTestResult
- * @param ParserTestResult $testResult
- * @return bool
- */
- function showTestResult( ParserTestResult $testResult ) {
- if ( $testResult->isSuccess() ) {
- $this->showSuccess( $testResult );
- return true;
- } else {
- $this->showFailure( $testResult );
- return false;
- }
- }
-
- /**
- * Use a regex to find out the value of an option
- * @param string $key Name of option val to retrieve
- * @param array $opts Options array to look in
- * @param mixed $default Default value returned if not found
- * @return mixed
- */
- private static function getOptionValue( $key, $opts, $default ) {
- $key = strtolower( $key );
-
- if ( isset( $opts[$key] ) ) {
- return $opts[$key];
- } else {
- return $default;
- }
- }
-
- private function parseOptions( $instring ) {
- $opts = [];
- // foo
- // foo=bar
- // foo="bar baz"
- // foo=[[bar baz]]
- // foo=bar,"baz quux"
- // foo={...json...}
- $defs = '(?(DEFINE)
- (?<qstr> # Quoted string
- "
- (?:[^\\\\"] | \\\\.)*
- "
- )
- (?<json>
- \{ # Open bracket
- (?:
- [^"{}] | # Not a quoted string or object, or
- (?&qstr) | # A quoted string, or
- (?&json) # A json object (recursively)
- )*
- \} # Close bracket
- )
- (?<value>
- (?:
- (?&qstr) # Quoted val
- |
- \[\[
- [^]]* # Link target
- \]\]
- |
- [\w-]+ # Plain word
- |
- (?&json) # JSON object
- )
- )
- )';
- $regex = '/' . $defs . '\b
- (?<k>[\w-]+) # Key
- \b
- (?:\s*
- = # First sub-value
- \s*
- (?<v>
- (?&value)
- (?:\s*
- , # Sub-vals 1..N
- \s*
- (?&value)
- )*
- )
- )?
- /x';
- $valueregex = '/' . $defs . '(?&value)/x';
-
- if ( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) {
- foreach ( $matches as $bits ) {
- $key = strtolower( $bits['k'] );
- if ( !isset( $bits['v'] ) ) {
- $opts[$key] = true;
- } else {
- preg_match_all( $valueregex, $bits['v'], $vmatches );
- $opts[$key] = array_map( [ $this, 'cleanupOption' ], $vmatches[0] );
- if ( count( $opts[$key] ) == 1 ) {
- $opts[$key] = $opts[$key][0];
- }
- }
- }
- }
- return $opts;
- }
-
- private function cleanupOption( $opt ) {
- if ( substr( $opt, 0, 1 ) == '"' ) {
- return stripcslashes( substr( $opt, 1, -1 ) );
- }
-
- if ( substr( $opt, 0, 2 ) == '[[' ) {
- return substr( $opt, 2, -2 );
- }
-
- if ( substr( $opt, 0, 1 ) == '{' ) {
- return FormatJson::decode( $opt, true );
- }
- return $opt;
- }
-
- /**
- * Set up the global variables for a consistent environment for each test.
- * Ideally this should replace the global configuration entirely.
- * @param string $opts
- * @param string $config
- * @return RequestContext
- */
- private function setupGlobals( $opts = '', $config = '' ) {
- global $IP;
-
- # Find out values for some special options.
- $lang =
- self::getOptionValue( 'language', $opts, 'en' );
- $variant =
- self::getOptionValue( 'variant', $opts, false );
- $maxtoclevel =
- self::getOptionValue( 'wgMaxTocLevel', $opts, 999 );
- $linkHolderBatchSize =
- self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 );
-
- $settings = [
- 'wgServer' => 'http://example.org',
- 'wgServerName' => 'example.org',
- 'wgScript' => '/index.php',
- 'wgScriptPath' => '',
- 'wgArticlePath' => '/wiki/$1',
- 'wgActionPaths' => [],
- 'wgLockManagers' => [ [
- 'name' => 'fsLockManager',
- 'class' => 'FSLockManager',
- 'lockDirectory' => $this->uploadDir . '/lockdir',
- ], [
- 'name' => 'nullLockManager',
- 'class' => 'NullLockManager',
- ] ],
- 'wgLocalFileRepo' => [
- 'class' => 'LocalRepo',
- 'name' => 'local',
- 'url' => 'http://example.com/images',
- 'hashLevels' => 2,
- 'transformVia404' => false,
- 'backend' => new FSFileBackend( [
- 'name' => 'local-backend',
- 'wikiId' => wfWikiID(),
- 'containerPaths' => [
- 'local-public' => $this->uploadDir,
- 'local-thumb' => $this->uploadDir . '/thumb',
- 'local-temp' => $this->uploadDir . '/temp',
- 'local-deleted' => $this->uploadDir . '/delete',
- ]
- ] )
- ],
- 'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
- 'wgUploadNavigationUrl' => false,
- 'wgStylePath' => '/skins',
- 'wgSitename' => 'MediaWiki',
- 'wgLanguageCode' => $lang,
- 'wgDBprefix' => $this->db->getType() != 'oracle' ? 'parsertest_' : 'pt_',
- 'wgRawHtml' => self::getOptionValue( 'wgRawHtml', $opts, false ),
- 'wgLang' => null,
- 'wgContLang' => null,
- 'wgNamespacesWithSubpages' => [ 0 => isset( $opts['subpage'] ) ],
- 'wgMaxTocLevel' => $maxtoclevel,
- 'wgCapitalLinks' => true,
- 'wgNoFollowLinks' => true,
- 'wgNoFollowDomainExceptions' => [],
- 'wgThumbnailScriptPath' => false,
- 'wgUseImageResize' => true,
- 'wgSVGConverter' => 'null',
- 'wgSVGConverters' => [ 'null' => 'echo "1">$output' ],
- 'wgLocaltimezone' => 'UTC',
- 'wgAllowExternalImages' => self::getOptionValue( 'wgAllowExternalImages', $opts, true ),
- 'wgThumbLimits' => [ self::getOptionValue( 'thumbsize', $opts, 180 ) ],
- 'wgDefaultLanguageVariant' => $variant,
- 'wgVariantArticlePath' => false,
- 'wgGroupPermissions' => [ '*' => [
- 'createaccount' => true,
- 'read' => true,
- 'edit' => true,
- 'createpage' => true,
- 'createtalk' => true,
- ] ],
- 'wgNamespaceProtection' => [ NS_MEDIAWIKI => 'editinterface' ],
- 'wgDefaultExternalStore' => [],
- 'wgForeignFileRepos' => [],
- 'wgLinkHolderBatchSize' => $linkHolderBatchSize,
- 'wgExperimentalHtmlIds' => false,
- 'wgExternalLinkTarget' => false,
- 'wgHtml5' => true,
- 'wgAdaptiveMessageCache' => true,
- 'wgDisableLangConversion' => false,
- 'wgDisableTitleConversion' => false,
- // Tidy options.
- 'wgUseTidy' => isset( $opts['tidy'] ),
- 'wgTidyConfig' => null,
- 'wgDebugTidy' => false,
- 'wgTidyConf' => $IP . '/includes/tidy/tidy.conf',
- 'wgTidyOpts' => '',
- 'wgTidyInternal' => $this->tidySupport->isInternal(),
- ];
-
- if ( $config ) {
- $configLines = explode( "\n", $config );
-
- foreach ( $configLines as $line ) {
- list( $var, $value ) = explode( '=', $line, 2 );
-
- $settings[$var] = eval( "return $value;" );
- }
- }
-
- $this->savedGlobals = [];
-
- /** @since 1.20 */
- Hooks::run( 'ParserTestGlobals', [ &$settings ] );
-
- foreach ( $settings as $var => $val ) {
- if ( array_key_exists( $var, $GLOBALS ) ) {
- $this->savedGlobals[$var] = $GLOBALS[$var];
- }
-
- $GLOBALS[$var] = $val;
- }
-
- // Must be set before $context as user language defaults to $wgContLang
- $GLOBALS['wgContLang'] = Language::factory( $lang );
- $GLOBALS['wgMemc'] = new EmptyBagOStuff;
-
- RequestContext::resetMain();
- $context = RequestContext::getMain();
- $GLOBALS['wgLang'] = $context->getLanguage();
- $GLOBALS['wgOut'] = $context->getOutput();
- $GLOBALS['wgUser'] = $context->getUser();
-
- // We (re)set $wgThumbLimits to a single-element array above.
- $context->getUser()->setOption( 'thumbsize', 0 );
-
- global $wgHooks;
-
- $wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup';
- $wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp';
-
- MagicWord::clearCache();
- MWTidy::destroySingleton();
- RepoGroup::destroySingleton();
-
- return $context;
- }
-
- /**
- * List of temporary tables to create, without prefix.
- * Some of these probably aren't necessary.
- * @return array
- */
- private function listTables() {
- $tables = [ 'user', 'user_properties', 'user_former_groups', 'page', 'page_restrictions',
- 'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks',
- 'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks',
- 'site_stats', 'ipblocks', 'image', 'oldimage',
- 'recentchanges', 'watchlist', 'interwiki', 'logging', 'log_search',
- 'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo',
- 'archive', 'user_groups', 'page_props', 'category'
- ];
-
- if ( in_array( $this->db->getType(), [ 'mysql', 'sqlite', 'oracle' ] ) ) {
- array_push( $tables, 'searchindex' );
- }
-
- // Allow extensions to add to the list of tables to duplicate;
- // may be necessary if they hook into page save or other code
- // which will require them while running tests.
- Hooks::run( 'ParserTestTables', [ &$tables ] );
-
- return $tables;
- }
-
- /**
- * Set up a temporary set of wiki tables to work with for the tests.
- * Currently this will only be done once per run, and any changes to
- * the db will be visible to later tests in the run.
- */
- public function setupDatabase() {
- global $wgDBprefix;
-
- if ( $this->databaseSetupDone ) {
- return;
- }
-
- $this->db = wfGetDB( DB_MASTER );
- $dbType = $this->db->getType();
-
- if ( $wgDBprefix === 'parsertest_' || ( $dbType == 'oracle' && $wgDBprefix === 'pt_' ) ) {
- throw new MWException( 'setupDatabase should be called before setupGlobals' );
- }
-
- $this->databaseSetupDone = true;
-
- # SqlBagOStuff broke when using temporary tables on r40209 (bug 15892).
- # It seems to have been fixed since (r55079?), but regressed at some point before r85701.
- # This works around it for now...
- ObjectCache::$instances[CACHE_DB] = new HashBagOStuff;
-
- # CREATE TEMPORARY TABLE breaks if there is more than one server
- if ( wfGetLB()->getServerCount() != 1 ) {
- $this->useTemporaryTables = false;
- }
-
- $temporary = $this->useTemporaryTables || $dbType == 'postgres';
- $prefix = $dbType != 'oracle' ? 'parsertest_' : 'pt_';
-
- $this->dbClone = new CloneDatabase( $this->db, $this->listTables(), $prefix );
- $this->dbClone->useTemporaryTables( $temporary );
- $this->dbClone->cloneTableStructure();
-
- if ( $dbType == 'oracle' ) {
- $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
- # Insert 0 user to prevent FK violations
-
- # Anonymous user
- $this->db->insert( 'user', [
- 'user_id' => 0,
- 'user_name' => 'Anonymous' ] );
- }
-
- # Update certain things in site_stats
- $this->db->insert( 'site_stats',
- [ 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ] );
-
- # Reinitialise the LocalisationCache to match the database state
- Language::getLocalisationCache()->unloadAll();
-
- # Clear the message cache
- MessageCache::singleton()->clear();
-
- // Remember to update newParserTests.php after changing the below
- // (and it uses a slightly different syntax just for teh lulz)
- $this->setupUploadDir();
- $user = User::createNew( 'WikiSysop' );
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) );
- # note that the size/width/height/bits/etc of the file
- # are actually set by inspecting the file itself; the arguments
- # to recordUpload2 have no effect. That said, we try to make things
- # match up so it is less confusing to readers of the code & tests.
- $image->recordUpload2( '', 'Upload of some lame file', 'Some lame file', [
- 'size' => 7881,
- 'width' => 1941,
- 'height' => 220,
- 'bits' => 8,
- 'media_type' => MEDIATYPE_BITMAP,
- 'mime' => 'image/jpeg',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '1', 16, 36, 31 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20010115123500' ), $user );
-
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Thumb.png' ) );
- # again, note that size/width/height below are ignored; see above.
- $image->recordUpload2( '', 'Upload of some lame thumbnail', 'Some lame thumbnail', [
- 'size' => 22589,
- 'width' => 135,
- 'height' => 135,
- 'bits' => 8,
- 'media_type' => MEDIATYPE_BITMAP,
- 'mime' => 'image/png',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '2', 16, 36, 31 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20130225203040' ), $user );
-
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.svg' ) );
- $image->recordUpload2( '', 'Upload of some lame SVG', 'Some lame SVG', [
- 'size' => 12345,
- 'width' => 240,
- 'height' => 180,
- 'bits' => 0,
- 'media_type' => MEDIATYPE_DRAWING,
- 'mime' => 'image/svg+xml',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20010115123500' ), $user );
-
- # This image will be blacklisted in [[MediaWiki:Bad image list]]
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) );
- $image->recordUpload2( '', 'zomgnotcensored', 'Borderline image', [
- 'size' => 12345,
- 'width' => 320,
- 'height' => 240,
- 'bits' => 24,
- 'media_type' => MEDIATYPE_BITMAP,
- 'mime' => 'image/jpeg',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '3', 16, 36, 31 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20010115123500' ), $user );
-
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Video.ogv' ) );
- $image->recordUpload2( '', 'A pretty movie', 'Will it play', [
- 'size' => 12345,
- 'width' => 320,
- 'height' => 240,
- 'bits' => 0,
- 'media_type' => MEDIATYPE_VIDEO,
- 'mime' => 'application/ogg',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20010115123500' ), $user );
-
- # A DjVu file
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'LoremIpsum.djvu' ) );
- $image->recordUpload2( '', 'Upload a DjVu', 'A DjVu', [
- 'size' => 3249,
- 'width' => 2480,
- 'height' => 3508,
- 'bits' => 0,
- 'media_type' => MEDIATYPE_BITMAP,
- 'mime' => 'image/vnd.djvu',
- 'metadata' => '<?xml version="1.0" ?>
-<!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
-<DjVuXML>
-<HEAD></HEAD>
-<BODY><OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-</BODY>
-</DjVuXML>',
- 'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20010115123600' ), $user );
- }
-
- public function teardownDatabase() {
- if ( !$this->databaseSetupDone ) {
- $this->teardownGlobals();
- return;
- }
- $this->teardownUploadDir( $this->uploadDir );
-
- $this->dbClone->destroy();
- $this->databaseSetupDone = false;
-
- if ( $this->useTemporaryTables ) {
- if ( $this->db->getType() == 'sqlite' ) {
- # Under SQLite the searchindex table is virtual and need
- # to be explicitly destroyed. See bug 29912
- # See also MediaWikiTestCase::destroyDB()
- wfDebug( __METHOD__ . " explicitly destroying sqlite virtual table parsertest_searchindex\n" );
- $this->db->query( "DROP TABLE `parsertest_searchindex`" );
- }
- # Don't need to do anything
- $this->teardownGlobals();
- return;
- }
-
- $tables = $this->listTables();
-
- foreach ( $tables as $table ) {
- if ( $this->db->getType() == 'oracle' ) {
- $this->db->query( "DROP TABLE pt_$table DROP CONSTRAINTS" );
- } else {
- $this->db->query( "DROP TABLE `parsertest_$table`" );
- }
- }
-
- if ( $this->db->getType() == 'oracle' ) {
- $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
- }
-
- $this->teardownGlobals();
- }
-
- /**
- * Create a dummy uploads directory which will contain a couple
- * of files in order to pass existence tests.
- *
- * @return string The directory
- */
- private function setupUploadDir() {
- global $IP;
-
- $dir = $this->uploadDir;
- if ( $this->keepUploads && is_dir( $dir ) ) {
- return;
- }
-
- // wfDebug( "Creating upload directory $dir\n" );
- if ( file_exists( $dir ) ) {
- wfDebug( "Already exists!\n" );
- return;
- }
-
- wfMkdirParents( $dir . '/3/3a', null, __METHOD__ );
- copy( "$IP/tests/phpunit/data/parser/headbg.jpg", "$dir/3/3a/Foobar.jpg" );
- wfMkdirParents( $dir . '/e/ea', null, __METHOD__ );
- copy( "$IP/tests/phpunit/data/parser/wiki.png", "$dir/e/ea/Thumb.png" );
- wfMkdirParents( $dir . '/0/09', null, __METHOD__ );
- copy( "$IP/tests/phpunit/data/parser/headbg.jpg", "$dir/0/09/Bad.jpg" );
- wfMkdirParents( $dir . '/f/ff', null, __METHOD__ );
- file_put_contents( "$dir/f/ff/Foobar.svg",
- '<?xml version="1.0" encoding="utf-8"?>' .
- '<svg xmlns="http://www.w3.org/2000/svg"' .
- ' version="1.1" width="240" height="180"/>' );
- wfMkdirParents( $dir . '/5/5f', null, __METHOD__ );
- copy( "$IP/tests/phpunit/data/parser/LoremIpsum.djvu", "$dir/5/5f/LoremIpsum.djvu" );
- wfMkdirParents( $dir . '/0/00', null, __METHOD__ );
- copy( "$IP/tests/phpunit/data/parser/320x240.ogv", "$dir/0/00/Video.ogv" );
-
- return;
- }
-
- /**
- * Restore default values and perform any necessary clean-up
- * after each test runs.
- */
- private function teardownGlobals() {
- RepoGroup::destroySingleton();
- FileBackendGroup::destroySingleton();
- LockManagerGroup::destroySingletons();
- LinkCache::singleton()->clear();
- MWTidy::destroySingleton();
-
- foreach ( $this->savedGlobals as $var => $val ) {
- $GLOBALS[$var] = $val;
- }
- }
-
- /**
- * Remove the dummy uploads directory
- * @param string $dir
- */
- private function teardownUploadDir( $dir ) {
- if ( $this->keepUploads ) {
- return;
- }
-
- // delete the files first, then the dirs.
- self::deleteFiles(
- [
- "$dir/3/3a/Foobar.jpg",
- "$dir/thumb/3/3a/Foobar.jpg/*.jpg",
- "$dir/e/ea/Thumb.png",
- "$dir/0/09/Bad.jpg",
- "$dir/5/5f/LoremIpsum.djvu",
- "$dir/thumb/5/5f/LoremIpsum.djvu/*-LoremIpsum.djvu.jpg",
- "$dir/f/ff/Foobar.svg",
- "$dir/thumb/f/ff/Foobar.svg/*-Foobar.svg.png",
- "$dir/math/f/a/5/fa50b8b616463173474302ca3e63586b.png",
- "$dir/0/00/Video.ogv",
- "$dir/thumb/0/00/Video.ogv/120px--Video.ogv.jpg",
- "$dir/thumb/0/00/Video.ogv/180px--Video.ogv.jpg",
- "$dir/thumb/0/00/Video.ogv/240px--Video.ogv.jpg",
- "$dir/thumb/0/00/Video.ogv/320px--Video.ogv.jpg",
- "$dir/thumb/0/00/Video.ogv/270px--Video.ogv.jpg",
- "$dir/thumb/0/00/Video.ogv/320px-seek=2-Video.ogv.jpg",
- "$dir/thumb/0/00/Video.ogv/320px-seek=3.3666666666667-Video.ogv.jpg",
- ]
- );
-
- self::deleteDirs(
- [
- "$dir/3/3a",
- "$dir/3",
- "$dir/thumb/3/3a/Foobar.jpg",
- "$dir/thumb/3/3a",
- "$dir/thumb/3",
- "$dir/e/ea",
- "$dir/e",
- "$dir/f/ff/",
- "$dir/f/",
- "$dir/thumb/f/ff/Foobar.svg",
- "$dir/thumb/f/ff/",
- "$dir/thumb/f/",
- "$dir/0/00/",
- "$dir/0/09/",
- "$dir/0/",
- "$dir/5/5f",
- "$dir/5",
- "$dir/thumb/0/00/Video.ogv",
- "$dir/thumb/0/00",
- "$dir/thumb/0",
- "$dir/thumb/5/5f/LoremIpsum.djvu",
- "$dir/thumb/5/5f",
- "$dir/thumb/5",
- "$dir/thumb",
- "$dir/math/f/a/5",
- "$dir/math/f/a",
- "$dir/math/f",
- "$dir/math",
- "$dir/lockdir",
- "$dir",
- ]
- );
- }
-
- /**
- * Delete the specified files, if they exist.
- * @param array $files Full paths to files to delete.
- */
- private static function deleteFiles( $files ) {
- foreach ( $files as $pattern ) {
- foreach ( glob( $pattern ) as $file ) {
- if ( file_exists( $file ) ) {
- unlink( $file );
- }
- }
- }
- }
-
- /**
- * Delete the specified directories, if they exist. Must be empty.
- * @param array $dirs Full paths to directories to delete.
- */
- private static function deleteDirs( $dirs ) {
- foreach ( $dirs as $dir ) {
- if ( is_dir( $dir ) ) {
- rmdir( $dir );
- }
- }
- }
-
- /**
- * "Running test $desc..."
- * @param string $desc
- */
- protected function showTesting( $desc ) {
- print "Running test $desc... ";
- }
-
- /**
- * Print a happy success message.
- *
- * Refactored in 1.22 to use ParserTestResult
- *
- * @param ParserTestResult $testResult
- * @return bool
- */
- protected function showSuccess( ParserTestResult $testResult ) {
- if ( $this->showProgress ) {
- print $this->term->color( '1;32' ) . 'PASSED' . $this->term->reset() . "\n";
- }
-
- return true;
- }
-
- /**
- * Print a failure message and provide some explanatory output
- * about what went wrong if so configured.
- *
- * Refactored in 1.22 to use ParserTestResult
- *
- * @param ParserTestResult $testResult
- * @return bool
- */
- protected function showFailure( ParserTestResult $testResult ) {
- if ( $this->showFailure ) {
- if ( !$this->showProgress ) {
- # In quiet mode we didn't show the 'Testing' message before the
- # test, in case it succeeded. Show it now:
- $this->showTesting( $testResult->description );
- }
-
- print $this->term->color( '31' ) . 'FAILED!' . $this->term->reset() . "\n";
-
- if ( $this->showOutput ) {
- print "--- Expected ---\n{$testResult->expected}\n";
- print "--- Actual ---\n{$testResult->actual}\n";
- }
-
- if ( $this->showDiffs ) {
- print $this->quickDiff( $testResult->expected, $testResult->actual );
- if ( !$this->wellFormed( $testResult->actual ) ) {
- print "XML error: $this->mXmlError\n";
- }
- }
- }
-
- return false;
- }
-
- /**
- * Print a skipped message.
- *
- * @return bool
- */
- protected function showSkipped() {
- if ( $this->showProgress ) {
- print $this->term->color( '1;33' ) . 'SKIPPED' . $this->term->reset() . "\n";
- }
-
- return true;
- }
-
- /**
- * Run given strings through a diff and return the (colorized) output.
- * Requires writable /tmp directory and a 'diff' command in the PATH.
- *
- * @param string $input
- * @param string $output
- * @param string $inFileTail Tailing for the input file name
- * @param string $outFileTail Tailing for the output file name
- * @return string
- */
- protected function quickDiff( $input, $output,
- $inFileTail = 'expected', $outFileTail = 'actual'
- ) {
- # Windows, or at least the fc utility, is retarded
- $slash = wfIsWindows() ? '\\' : '/';
- $prefix = wfTempDir() . "{$slash}mwParser-" . mt_rand();
-
- $infile = "$prefix-$inFileTail";
- $this->dumpToFile( $input, $infile );
-
- $outfile = "$prefix-$outFileTail";
- $this->dumpToFile( $output, $outfile );
-
- $shellInfile = wfEscapeShellArg( $infile );
- $shellOutfile = wfEscapeShellArg( $outfile );
-
- global $wgDiff3;
- // we assume that people with diff3 also have usual diff
- $shellCommand = ( wfIsWindows() && !$wgDiff3 ) ? 'fc' : 'diff -au';
-
- $diff = wfShellExec( "$shellCommand $shellInfile $shellOutfile" );
-
- unlink( $infile );
- unlink( $outfile );
-
- return $this->colorDiff( $diff );
- }
-
- /**
- * Write the given string to a file, adding a final newline.
- *
- * @param string $data
- * @param string $filename
- */
- private function dumpToFile( $data, $filename ) {
- $file = fopen( $filename, "wt" );
- fwrite( $file, $data . "\n" );
- fclose( $file );
- }
-
- /**
- * Colorize unified diff output if set for ANSI color output.
- * Subtractions are colored blue, additions red.
- *
- * @param string $text
- * @return string
- */
- protected function colorDiff( $text ) {
- return preg_replace(
- [ '/^(-.*)$/m', '/^(\+.*)$/m' ],
- [ $this->term->color( 34 ) . '$1' . $this->term->reset(),
- $this->term->color( 31 ) . '$1' . $this->term->reset() ],
- $text );
- }
-
- /**
- * Show "Reading tests from ..."
- *
- * @param string $path
- */
- public function showRunFile( $path ) {
- print $this->term->color( 1 ) .
- "Reading tests from \"$path\"..." .
- $this->term->reset() .
- "\n";
- }
-
- /**
- * Insert a temporary test article
- * @param string $name The title, including any prefix
- * @param string $text The article text
- * @param int|string $line The input line number, for reporting errors
- * @param bool|string $ignoreDuplicate Whether to silently ignore duplicate pages
- * @throws Exception
- * @throws MWException
- */
- public static function addArticle( $name, $text, $line = 'unknown', $ignoreDuplicate = '' ) {
- global $wgCapitalLinks;
-
- $oldCapitalLinks = $wgCapitalLinks;
- $wgCapitalLinks = true; // We only need this from SetupGlobals() See r70917#c8637
-
- $text = self::chomp( $text );
- $name = self::chomp( $name );
-
- $title = Title::newFromText( $name );
-
- if ( is_null( $title ) ) {
- throw new MWException( "invalid title '$name' at line $line\n" );
- }
-
- $page = WikiPage::factory( $title );
- $page->loadPageData( 'fromdbmaster' );
-
- if ( $page->exists() ) {
- if ( $ignoreDuplicate == 'ignoreduplicate' ) {
- return;
- } else {
- throw new MWException( "duplicate article '$name' at line $line\n" );
- }
- }
-
- $page->doEditContent( ContentHandler::makeContent( $text, $title ), '', EDIT_NEW );
-
- $wgCapitalLinks = $oldCapitalLinks;
- }
-
- /**
- * Steal a callback function from the primary parser, save it for
- * application to our scary parser. If the hook is not installed,
- * abort processing of this file.
- *
- * @param string $name
- * @return bool True if tag hook is present
- */
- public function requireHook( $name ) {
- global $wgParser;
-
- $wgParser->firstCallInit(); // make sure hooks are loaded.
-
- if ( isset( $wgParser->mTagHooks[$name] ) ) {
- $this->hooks[$name] = $wgParser->mTagHooks[$name];
- } else {
- echo " This test suite requires the '$name' hook extension, skipping.\n";
- return false;
- }
-
- return true;
- }
-
- /**
- * Steal a callback function from the primary parser, save it for
- * application to our scary parser. If the hook is not installed,
- * abort processing of this file.
- *
- * @param string $name
- * @return bool True if function hook is present
- */
- public function requireFunctionHook( $name ) {
- global $wgParser;
-
- $wgParser->firstCallInit(); // make sure hooks are loaded.
-
- if ( isset( $wgParser->mFunctionHooks[$name] ) ) {
- $this->functionHooks[$name] = $wgParser->mFunctionHooks[$name];
- } else {
- echo " This test suite requires the '$name' function hook extension, skipping.\n";
- return false;
- }
-
- return true;
- }
-
- /**
- * Steal a callback function from the primary parser, save it for
- * application to our scary parser. If the hook is not installed,
- * abort processing of this file.
- *
- * @param string $name
- * @return bool True if function hook is present
- */
- public function requireTransparentHook( $name ) {
- global $wgParser;
-
- $wgParser->firstCallInit(); // make sure hooks are loaded.
-
- if ( isset( $wgParser->mTransparentTagHooks[$name] ) ) {
- $this->transparentHooks[$name] = $wgParser->mTransparentTagHooks[$name];
- } else {
- echo " This test suite requires the '$name' transparent hook extension, skipping.\n";
- return false;
- }
-
- return true;
- }
-
- private function wellFormed( $text ) {
- $html =
- Sanitizer::hackDocType() .
- '<html>' .
- $text .
- '</html>';
-
- $parser = xml_parser_create( "UTF-8" );
-
- # case folding violates XML standard, turn it off
- xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
-
- if ( !xml_parse( $parser, $html, true ) ) {
- $err = xml_error_string( xml_get_error_code( $parser ) );
- $position = xml_get_current_byte_index( $parser );
- $fragment = $this->extractFragment( $html, $position );
- $this->mXmlError = "$err at byte $position:\n$fragment";
- xml_parser_free( $parser );
-
- return false;
- }
-
- xml_parser_free( $parser );
-
- return true;
- }
-
- private function extractFragment( $text, $position ) {
- $start = max( 0, $position - 10 );
- $before = $position - $start;
- $fragment = '...' .
- $this->term->color( 34 ) .
- substr( $text, $start, $before ) .
- $this->term->color( 0 ) .
- $this->term->color( 31 ) .
- $this->term->color( 1 ) .
- substr( $text, $position, 1 ) .
- $this->term->color( 0 ) .
- $this->term->color( 34 ) .
- substr( $text, $position + 1, 9 ) .
- $this->term->color( 0 ) .
- '...';
- $display = str_replace( "\n", ' ', $fragment );
- $caret = ' ' .
- str_repeat( ' ', $before ) .
- $this->term->color( 31 ) .
- '^' .
- $this->term->color( 0 );
-
- return "$display\n$caret";
- }
-
- static function getFakeTimestamp( &$parser, &$ts ) {
- $ts = 123; // parsed as '1970-01-01T00:02:03Z'
- return true;
- }
-}
diff --git a/www/wiki/tests/parser/parserTests.php b/www/wiki/tests/parser/parserTests.php
index 2735f93e..6a423d5c 100644
--- a/www/wiki/tests/parser/parserTests.php
+++ b/www/wiki/tests/parser/parserTests.php
@@ -30,6 +30,8 @@ define( 'MW_PARSER_TEST', true );
require __DIR__ . '/../../maintenance/Maintenance.php';
+use MediaWiki\MediaWikiServices;
+
class ParserTestsMaintenance extends Maintenance {
function __construct() {
parent::__construct();
@@ -145,7 +147,8 @@ class ParserTestsMaintenance extends Maintenance {
$recorderLB = false;
if ( $record || $compare ) {
- $recorderLB = wfGetLBFactory()->newMainLB();
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $recorderLB = $lbFactory->newMainLB();
// This connection will have the wiki's table prefix, not parsertest_
$recorderDB = $recorderLB->getConnection( DB_MASTER );
diff --git a/www/wiki/tests/parser/parserTests.txt b/www/wiki/tests/parser/parserTests.txt
index 50560f84..451c50f5 100644
--- a/www/wiki/tests/parser/parserTests.txt
+++ b/www/wiki/tests/parser/parserTests.txt
@@ -280,12 +280,6 @@ Template:EmptyTRWithHTMLAttrTest
!!endarticle
!! article
-Template:CircularRef
-!! text
-<ref>{{CircularRef}}</ref>
-!! endarticle
-
-!! article
Template:With: Colon
!! text
Template with colon
@@ -294,6 +288,7 @@ Template with colon
###
### Basic tests
###
+
!! test
Blank input
!! wikitext
@@ -301,16 +296,6 @@ Blank input
!! end
!! test
-CircularRef
-!! wikitext
-{{CircularRef}}
-<references />
-!! html/parsoid
-<p><span about="#mwt1" class="mw-ref" id="cite_ref-1" rel="dc:references" typeof="mw:Transclusion mw:Extension/ref" data-parsoid='{"pi":[[]]}' data-mw='{"parts":[{"template":{"target":{"wt":"CircularRef","href":"./Template:CircularRef"},"params":{},"i":0}}]}'><a href="./Main_Page#cite_note-1" style="counter-reset: mw-Ref 1;"><span class="mw-reflink-text">[1]</span></a></span></p>
-<ol class="mw-references references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><a href="./Main_Page#cite_ref-1" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-1" class="mw-reference-text" data-parsoid="{}">Error: Expansion loop detected at <a data-parsoid='{"a":{"href":null},"sa":{"href":"Template:CircularRef"}}'>Template:CircularRef</a></span></li></ol>
-!! end
-
-!! test
Simple paragraph
!! wikitext
This is a simple paragraph.
@@ -546,16 +531,20 @@ Extra newlines between heading and content are swallowed
Heading with line break in nowiki
!! options
parsoid=wt2html
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
!! wikitext
-== A <nowiki>B
-C</nowiki> ==
-!! html
-<h2><span class="mw-headline" id="A_B.0AC">A B
+==A <nowiki>B
+C</nowiki>==
+!! html/php
+<h2><span id="A_B.0AC"></span><span class="mw-headline" id="A_B
+C">A B
C</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: A B&#10;C">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
!! html/parsoid
-<h2 id="A_B.0AC">A <span typeof="mw:Nowiki">B
-C</span> </h2>
+<h2 id="A_B
+C"><span id="A_B.0AC" typeof="mw:FallbackId"></span>A <span typeof="mw:Nowiki">B
+C</span></h2>
!! end
!! test
@@ -568,58 +557,51 @@ http://fr.wikipedia.org/wiki/🍺
</p>
!! end
-# Note that the html+tidy output removes the spaces after the <li>,
-# which is a bug (https://sourceforge.net/p/tidy/bugs/945/, etc).
-# This is an issue for all tests with lists. We intentionally do
-# *not* add html+tidy clauses for these, as we don't want to
-# document/test the broken behavior. (Parsoid matches the non-tidy
-# output in these cases.)
-
!! test
Simple list
!! wikitext
-* Item 1
-* Item 2
+*Item 1
+*Item 2
!! html
-<ul><li> Item 1</li>
-<li> Item 2</li></ul>
+<ul><li>Item 1</li>
+<li>Item 2</li></ul>
!! end
!! test
Italics and bold
!! wikitext
-* plain
-* plain''italic''plain
-* plain''italic''plain''italic''plain
-* plain'''bold'''plain
-* plain'''bold'''plain'''bold'''plain
-* plain''italic''plain'''bold'''plain
-* plain'''bold'''plain''italic''plain
-* plain''italic'''bold-italic'''italic''plain
-* plain'''bold''bold-italic''bold'''plain
-* plain'''''bold-italic'''italic''plain
-* plain'''''bold-italic''bold'''plain
-* plain''italic'''bold-italic'''''plain
-* plain'''bold''bold-italic'''''plain
-* plain l'''italic''plain
-* plain l''''bold''' plain
-!! html
-<ul><li> plain</li>
-<li> plain<i>italic</i>plain</li>
-<li> plain<i>italic</i>plain<i>italic</i>plain</li>
-<li> plain<b>bold</b>plain</li>
-<li> plain<b>bold</b>plain<b>bold</b>plain</li>
-<li> plain<i>italic</i>plain<b>bold</b>plain</li>
-<li> plain<b>bold</b>plain<i>italic</i>plain</li>
-<li> plain<i>italic<b>bold-italic</b>italic</i>plain</li>
-<li> plain<b>bold<i>bold-italic</i>bold</b>plain</li>
-<li> plain<i><b>bold-italic</b>italic</i>plain</li>
-<li> plain<b><i>bold-italic</i>bold</b>plain</li>
-<li> plain<i>italic<b>bold-italic</b></i>plain</li>
-<li> plain<b>bold<i>bold-italic</i></b>plain</li>
-<li> plain l'<i>italic</i>plain</li>
-<li> plain l'<b>bold</b> plain</li></ul>
+*plain
+*plain''italic''plain
+*plain''italic''plain''italic''plain
+*plain'''bold'''plain
+*plain'''bold'''plain'''bold'''plain
+*plain''italic''plain'''bold'''plain
+*plain'''bold'''plain''italic''plain
+*plain''italic'''bold-italic'''italic''plain
+*plain'''bold''bold-italic''bold'''plain
+*plain'''''bold-italic'''italic''plain
+*plain'''''bold-italic''bold'''plain
+*plain''italic'''bold-italic'''''plain
+*plain'''bold''bold-italic'''''plain
+*plain l'''italic''plain
+*plain l''''bold''' plain
+!! html
+<ul><li>plain</li>
+<li>plain<i>italic</i>plain</li>
+<li>plain<i>italic</i>plain<i>italic</i>plain</li>
+<li>plain<b>bold</b>plain</li>
+<li>plain<b>bold</b>plain<b>bold</b>plain</li>
+<li>plain<i>italic</i>plain<b>bold</b>plain</li>
+<li>plain<b>bold</b>plain<i>italic</i>plain</li>
+<li>plain<i>italic<b>bold-italic</b>italic</i>plain</li>
+<li>plain<b>bold<i>bold-italic</i>bold</b>plain</li>
+<li>plain<i><b>bold-italic</b>italic</i>plain</li>
+<li>plain<b><i>bold-italic</i>bold</b>plain</li>
+<li>plain<i>italic<b>bold-italic</b></i>plain</li>
+<li>plain<b>bold<i>bold-italic</i></b>plain</li>
+<li>plain l'<i>italic</i>plain</li>
+<li>plain l'<b>bold</b> plain</li></ul>
!! end
@@ -1145,8 +1127,7 @@ The ''[[Main Page]]'''s talk page.
!! end
!! test
-Parsoid only: Quote balancing context should be restricted to td/th cells on the same wikitext line
-(Requires tidy for PHP parser output to be fixed up)
+Quote balancing context should be restricted to td/th cells on the same wikitext line
!! options
parsoid=wt2html,wt2wt
!! wikitext
@@ -1154,20 +1135,15 @@ parsoid=wt2html,wt2wt
!''a!!''b
|''a||''b
|}
-!! html/php+tidy
+!! html+tidy
<table>
-<tr>
+<tbody><tr>
<th><i>a</i></th>
-<th><i>b</i></th>
+<th><i>b</i>
+</th>
<td><i>a</i></td>
-<td><i>b</i></td>
-</tr>
-</table>
-!! html/parsoid
-<table>
-<tbody><tr><th><i>a</i></th><th><i>b</i></th>
-<td><i>a</i></td><td><i>b</i></td></tr>
-</tbody></table>
+<td><i>b</i>
+</td></tr></tbody></table>
!! end
###
@@ -1271,32 +1247,33 @@ Text-level semantic html elements in wikitext
!! test
Ruby markup (W3C-style)
!! wikitext
-; Mono-ruby for individual base characters
-: <ruby>日<rt>に</rt>本<rt>ほん</rt>語<rt>ご</rt></ruby>
-; Group ruby
-: <ruby>今日<rt>きょう</rt></ruby>
-; Jukugo ruby
-: <ruby>法<rb>華</rb><rb>経</rb><rt>ほ</rt><rt>け</rt><rt>きょう</rt></ruby>
-; Inline ruby
-: <ruby>東<rb>京</rb><rp>(</rp><rt>とう</rt><rt>きょう</rt><rp>)</rp></ruby>
-; Double-sided ruby
-: <ruby><rb>旧</rb><rb>金</rb><rb>山</rb><rt>jiù</rt><rt>jīn</rt><rt>shān</rt><rtc>San Francisco</rtc></ruby>
+;Mono-ruby for individual base characters
+:<ruby>日<rt>に</rt>本<rt>ほん</rt>語<rt>ご</rt></ruby>
+;Group ruby
+:<ruby>今日<rt>きょう</rt></ruby>
+;Jukugo ruby
+:<ruby>法<rb>華</rb><rb>経</rb><rt>ほ</rt><rt>け</rt><rt>きょう</rt></ruby>
+;Inline ruby
+:<ruby>東<rb>京</rb><rp>(</rp><rt>とう</rt><rt>きょう</rt><rp>)</rp></ruby>
+;Double-sided ruby
+:<ruby><rb>旧</rb><rb>金</rb><rb>山</rb><rt>jiù</rt><rt>jīn</rt><rt>shān</rt><rtc>San Francisco</rtc></ruby>
+
<ruby>
<rb>♥</rb><rtc><rt>Heart</rt></rtc><rtc lang="fr"><rt>Cœur</rt></rtc>
<rb>☘</rb><rtc><rt>Shamrock</rt></rtc><rtc lang="fr"><rt>Trèfle</rt></rtc>
<rb>✶</rb><rtc><rt>Star</rt></rtc><rtc lang="fr"><rt>Étoile</rt></rtc>
</ruby>
!! html
-<dl><dt> Mono-ruby for individual base characters</dt>
-<dd> <ruby>日<rt>に</rt>本<rt>ほん</rt>語<rt>ご</rt></ruby></dd>
-<dt> Group ruby</dt>
-<dd> <ruby>今日<rt>きょう</rt></ruby></dd>
-<dt> Jukugo ruby</dt>
-<dd> <ruby>法<rb>華</rb><rb>経</rb><rt>ほ</rt><rt>け</rt><rt>きょう</rt></ruby></dd>
-<dt> Inline ruby</dt>
-<dd> <ruby>東<rb>京</rb><rp>(</rp><rt>とう</rt><rt>きょう</rt><rp>)</rp></ruby></dd>
-<dt> Double-sided ruby</dt>
-<dd> <ruby><rb>旧</rb><rb>金</rb><rb>山</rb><rt>jiù</rt><rt>jīn</rt><rt>shān</rt><rtc>San Francisco</rtc></ruby></dd></dl>
+<dl><dt>Mono-ruby for individual base characters</dt>
+<dd><ruby>日<rt>に</rt>本<rt>ほん</rt>語<rt>ご</rt></ruby></dd>
+<dt>Group ruby</dt>
+<dd><ruby>今日<rt>きょう</rt></ruby></dd>
+<dt>Jukugo ruby</dt>
+<dd><ruby>法<rb>華</rb><rb>経</rb><rt>ほ</rt><rt>け</rt><rt>きょう</rt></ruby></dd>
+<dt>Inline ruby</dt>
+<dd><ruby>東<rb>京</rb><rp>(</rp><rt>とう</rt><rt>きょう</rt><rp>)</rp></ruby></dd>
+<dt>Double-sided ruby</dt>
+<dd><ruby><rb>旧</rb><rb>金</rb><rb>山</rb><rt>jiù</rt><rt>jīn</rt><rt>shān</rt><rtc>San Francisco</rtc></ruby></dd></dl>
<p><ruby>
<rb>♥</rb><rtc><rt>Heart</rt></rtc><rtc lang="fr"><rt>Cœur</rt></rtc>
<rb>☘</rb><rtc><rt>Shamrock</rt></rtc><rtc lang="fr"><rt>Trèfle</rt></rtc>
@@ -1330,11 +1307,8 @@ Non-word characters don't terminate tag names (T19663, T42670, T54022)
</p>
!! end
-# There is a tidy bug here: https://sourceforge.net/p/tidy/bugs/946/
-# If the non-word-character tag made it through the sanitizer, tidy
-# would munge it up.
!! test
-Non-word characters don't terminate tag names + tidy
+Non-word characters don't terminate tag names
!! wikitext
<blockquote|>a</blockquote>
@@ -1348,12 +1322,13 @@ Non-word characters don't terminate tag names + tidy
<sub-ID#1>
!! html+tidy
-<p>&lt;blockquote|&gt;a</p>
-<p>&lt;b→&gt; doesn't terminate &lt;/b→&gt;</p>
-<p>&lt;bä&gt; doesn't terminate &lt;/bä&gt;</p>
-<p>&lt;boo&gt; doesn't terminate &lt;/boo&gt;</p>
-<p>&lt;s.foo&gt; doesn't terminate &lt;/s.foo&gt;</p>
-<p>&lt;sub-ID#1&gt;</p>
+<p>&lt;blockquote|&gt;a
+</p><p>&lt;b→&gt; doesn't terminate &lt;/b→&gt;
+</p><p>&lt;bä&gt; doesn't terminate &lt;/bä&gt;
+</p><p>&lt;boo&gt; doesn't terminate &lt;/boo&gt;
+</p><p>&lt;s.foo&gt; doesn't terminate &lt;/s.foo&gt;
+</p><p>&lt;sub-ID#1&gt;
+</p>
!! end
###
@@ -1386,7 +1361,9 @@ parsoid=wt2html
<s.foo>s</s>
!! html/php+tidy
-<p>&lt;s.foo&gt;s</p>
+<p class="mw-empty-elt">
+</p><p>&lt;s.foo&gt;s
+</p>
!! html/parsoid
<p>&lt;s.foo&gt;s</p>
!! end
@@ -1514,7 +1491,8 @@ Entities inside template parameters
!! wikitext
{{echo|&ndash;}}
!! html/php+tidy
-<p>–</p>
+<p>&#8211;
+</p>
!! html/parsoid
<p><span typeof="mw:Transclusion mw:Entity" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&amp;ndash;"}},"i":0}}]}'>&ndash;</span></p>
!! end
@@ -1884,6 +1862,7 @@ IE conditional comments
###
### paragraph wrapping tests
###
+
!! test
No block tags
!! wikitext
@@ -1907,9 +1886,38 @@ a <div>foo</div>
<p>b
</p>
!! html+tidy
-<p>a</p>
-<div>foo</div>
-<p>b</p>
+<p>a </p><div>foo</div>
+<p>b
+</p>
+!! end
+
+# Remex wraps empty tag runs with p-tags.
+# Parsoid strips them out during p-wrapping.
+!! test
+No p-wrappable content
+!! wikitext
+<span><div>x</div></span>
+<span><s><div>x</div></s></span>
+<small><em></em></small><span><s><div>x</div></s></span>
+!! html/php+tidy
+<span><div>x</div></span>
+<span><s><div>x</div></s></span>
+<p><small><em></em></small></p><span><s><div>x</div></s></span>
+!! html/parsoid
+<span><div>x</div></span>
+<span><s><div>x</div></s></span>
+<small><em></em></small><span><s><div>x</div></s></span>
+!! end
+
+# T177612: Parsoid-only test
+!! test
+Transclusion meta tags shouldn't trip Parsoid's useless p-wrapper stripping code
+!! wikitext
+{{echo|<span><div>x</div></span>}}
+x
+!! html/parsoid
+<span about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"stx":"html","pi":[[{"k":"1"}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;span>&lt;div>x&lt;/div>&lt;/span>"}},"i":0}}]}'><div>x</div></span>
+<p>x</p>
!! end
!! test
@@ -1923,11 +1931,9 @@ a <blockquote>foo</blockquote>
<p>b
</p>
!! html+tidy
-<p>a</p>
-<blockquote>
-<p>foo</p>
-</blockquote>
-<p>b</p>
+<p>a </p><blockquote><p>foo</p></blockquote>
+<p>b
+</p>
!! end
!! test
@@ -1941,10 +1947,8 @@ a <div>foo</div>
b <div>foo</div>
!! html+tidy
-<p>a</p>
-<div>foo</div>
-<p>b</p>
-<div>foo</div>
+<p>a </p><div>foo</div><p>
+b </p><div>foo</div>
!! end
!! test
@@ -1958,14 +1962,8 @@ a <blockquote>foo</blockquote>
b <blockquote>foo</blockquote>
!! html+tidy
-<p>a</p>
-<blockquote>
-<p>foo</p>
-</blockquote>
-<p>b</p>
-<blockquote>
-<p>foo</p>
-</blockquote>
+<p>a </p><blockquote><p>foo</p></blockquote><p>
+b </p><blockquote><p>foo</p></blockquote>
!! end
!! test
@@ -1985,19 +1983,21 @@ d e
x <div>foo</div> z
!! html+tidy
-<div>foo</div>
-<p>a</p>
-<p>b c d e</p>
-<p>x</p>
-<div>foo</div>
-<p>z</p>
+<div>foo</div><p> a
+</p><p>b
+c
+d e
+</p><p>
+x </p><div>foo</div><p> z
+</p>
!! end
-# Tidy strips out the empty <div> tags. Parsoid doesn't.
-# So, we have a separate section for Parsoid. We don't want
-# to mimic this stripping behavior in Parsoid. It affects
-# editing experience and also requires us to maintain additional
-# info for RT-ing.
+# The difference between Parsoid & Remex here
+# is because of Parsoid's Tidy-emulation code
+# for p-wrapping. We'll start work to remove this
+# emulation code in Parsoid sooner than later.
+# Remex wraps empty tag runs with p-tags.
+# Parsoid strips them out in a separate pass.
!! test
Empty lines between lines with block tags
!! wikitext
@@ -2027,14 +2027,16 @@ b
<div>e</div>
!! html+tidy
-<p><br /></p>
-<p>a</p>
-<p>b</p>
-<div>a</div>
-<p>b</p>
-<div>b</div>
-<p>d</p>
-<p><br /></p>
+<div></div>
+<p><br />
+</p>
+<div></div><p>a
+</p><p>b
+</p>
+<div>a</div><p>b
+</p><div>b</div><p>d
+</p><p><br />
+</p>
<div>e</div>
!! html/parsoid
<div data-parsoid='{"stx":"html"}'></div>
@@ -2051,7 +2053,6 @@ b
<div data-parsoid='{"stx":"html"}'>e</div>
!! end
-## PHP parser emits output which is broken
!! test
Unclosed HTML p-tags should be handled properly
!! wikitext
@@ -2060,11 +2061,10 @@ a
b
!! html/php+tidy
-<div>
-<p>foo</p>
-</div>
-<p>a</p>
-<p>b</p>
+<div><p>foo</p></div>
+<p>a
+</p><p>b
+</p>
!! html/parsoid
<div data-parsoid='{"stx":"html"}'><p data-parsoid='{"stx":"html", "autoInsertedEnd":true}'>foo</p></div>
<p>a</p>
@@ -2097,9 +2097,55 @@ parsoid=wt2html
<link rel="mw:PageProp/Category" href="./Category:A1"/><p>a</p>
!! end
+!! test
+No paragraph necessary for SOL transparent template
+!! wikitext
+<span><div>foo</div></span>
+[[Category:Foo]]
+
+<span><div>foo</div></span>
+{{echo|[[Category:Foo]]}}
+!! html/php
+<span><div>foo</div></span>
+<span><div>foo</div></span>
+
+!! html/parsoid
+<span data-parsoid='{"stx":"html"}'><div data-parsoid='{"stx":"html"}'>foo</div></span>
+<link rel="mw:PageProp/Category" href="./Category:Foo"/>
+
+<span data-parsoid='{"stx":"html"}'><div data-parsoid='{"stx":"html"}'>foo</div></span>
+<link rel="mw:PageProp/Category" href="./Category:Foo" about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[[Category:Foo]]"}},"i":0}}]}'/>
+!! end
+
+!! test
+Avoid expanding multiline sol transparent template ranges unnecessarily
+!! wikitext
+hi
+
+
+{{echo|<br/>
+}}
+
+[[Category:Ho]]
+!! html/php
+<p>hi
+</p><p><br />
+<br />
+</p>
+!! html/parsoid
+<p>hi</p>
+
+<p><br />
+<br about="#mwt1" typeof="mw:Transclusion" data-parsoid='{}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;br/>\n"}},"i":0}}]}'/><span about="#mwt1">
+</span></p>
+
+<link rel="mw:PageProp/Category" href="./Category:Ho" />
+!! end
+
###
### Preformatted text
###
+
!! test
Preformatted text
!! wikitext
@@ -2243,11 +2289,13 @@ Foo <del>bar</del> <ins>baz</ins> quux
</p>
</blockquote>
+!! html+tidy
+<blockquote>
+<p>Foo <del>bar</del> <ins>baz</ins> quux
+</p>
+</blockquote>
!! end
-# Note that the p-wrapping is newline sensitive, which could be
-# considered a bug: tidy will wrap only the 'Foo' in the example
-# below in a <p> tag. (see comment 23-25 of T8200)
!! test
T17491: <ins>/<del> in blockquote (2)
!! wikitext
@@ -2258,9 +2306,8 @@ T17491: <ins>/<del> in blockquote (2)
</blockquote>
!! html+tidy
-<blockquote>
-<p>Foo</p>
-<del>bar</del> <ins>baz</ins> quux</blockquote>
+<blockquote><p>Foo <del>bar</del> <ins>baz</ins> quux
+</p></blockquote>
!! end
!! test
@@ -2395,7 +2442,6 @@ parsoid=wt2html
</p>
!! end
-# Parsoid doesn't strip empty tags, like Tidy does.
!! test
Empty pre; pre inside other HTML tags (T56946)
!! wikitext
@@ -2405,20 +2451,12 @@ a
foo
</pre></div>
<pre></pre>
-!! html/php
+!! html/php+tidy
<p>a
</p>
-<div><pre>
-foo
+<div><pre>foo
</pre></div>
<pre></pre>
-
-!! html/php+tidy
-<p>a</p>
-<div>
-<pre>
-foo
-</pre></div>
!! html/parsoid
<p>a</p>
@@ -2438,16 +2476,12 @@ HTML pre followed by indent-pre
</pre>
!! end
-# Note that tidy removes the empty <p> tags from the start and end.
-# Parsoid does not, by design.
!! test
Block tag pre
!! wikitext
<p><pre>foo</pre></p>
!! html/php+tidy
-<pre>
-foo
-</pre>
+<p class="mw-empty-elt"></p><pre>foo</pre><p class="mw-empty-elt"></p>
!! html/parsoid
<p class='mw-empty-elt' data-parsoid='{"stx":"html","autoInsertedEnd":true}'></p><pre typeof="mw:Extension/pre" about="#mwt2" data-parsoid='{"stx":"html"}' data-mw='{"name":"pre","attrs":{},"body":{"extsrc":"foo"}}'>foo</pre><p class='mw-empty-elt' data-parsoid='{"autoInsertedStart":true,"stx":"html"}'></p>
!! end
@@ -2610,10 +2644,8 @@ parsoid=wt2html
<table>&lt;pre </table>
!! html/php+tidy
-<pre>
-x
-</pre>
-<p>&lt;pre</p>
+<pre>x</pre>
+&lt;pre <table></table>
!! html/parsoid
<pre about="#mwt1" typeof="mw:Transclusion mw:Extension/pre" data-parsoid='{"a":{"&lt;pre":null},"sa":{"&lt;pre":""},"stx":"html","pi":[[{"k":"1"}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;pre &lt;pre>x&lt;/pre>"}},"i":0}}]}'>x</pre>
@@ -2664,6 +2696,17 @@ parsoid=wt2html
!! end
!! test
+Self-closed pre
+!! wikitext
+<pre />
+!! html/php
+<pre></pre>
+
+!! html/parsoid
+<pre typeof="mw:Extension/pre" about="#mwt2" data-mw='{"name":"pre","attrs":{},"body":null}'></pre>
+!! end
+
+!! test
Parsoid: Don't paragraph-wrap fosterable content even if table syntax is unbalanced
!! options
parsoid=wt2html
@@ -2714,7 +2757,7 @@ Templates: Strip leading and trailing whitespace from named-param values
</p><p>b
</p><p>c
</p>
-<ul><li> d</li></ul>
+<ul><li>d</li></ul>
!! end
@@ -2735,7 +2778,7 @@ Templates: Don't strip whitespace from positional-param values
e}}
{{echo|
-* f}}
+*f}}
{{echo|
}}g
@@ -2755,7 +2798,7 @@ Templates: Don't strip whitespace from positional-param values
</pre>
<p><br />
</p>
-<ul><li> f</li></ul>
+<ul><li>f</li></ul>
<p><br />
</p>
<pre>g
@@ -2907,7 +2950,8 @@ Templates: Parsoid parameter escaping test 1
!! wikitext
{{echo|[foo]|{{echo|[bar]}}}}
!! html/php+tidy
-<p>[foo]</p>
+<p>[foo]
+</p>
!! html/parsoid
<p about="#mwt1" typeof="mw:Transclusion"
data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[foo]"},"2":{"wt":"{{echo|[bar]}}"}},"i":0}}]}'>[foo]</p>
@@ -2918,9 +2962,10 @@ Parsoid: Pipes in external links in template parameter
!! wikitext
{{echo|[{{echo|http://example.com}} link]}}
!! html/php+tidy
-<p><a rel="nofollow" class="external text" href="http://example.com">link</a></p>
+<p><a rel="nofollow" class="external text" href="http://example.com">link</a>
+</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com" about="#mwt31" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[{{echo|http://example.com}} link]"}},"i":0}}]}'>link</a></p>
+<p><a rel="mw:ExtLink" class="external text" href="http://example.com" about="#mwt31" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[{{echo|http://example.com}} link]"}},"i":0}}]}'>link</a></p>
!! end
!! test
@@ -2928,11 +2973,10 @@ Parsoid: pipe in transclusion parameter
!! wikitext
{{echo|http://foo.com/a&#124;b}}
!! html/php+tidy
-<p><a rel="nofollow" class="external free" href="http://foo.com/a%7Cb">http://foo.com/a%7Cb</a></p>
+<p><a rel="nofollow" class="external free" href="http://foo.com/a%7Cb">http://foo.com/a%7Cb</a>
+</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://foo.com/a%7Cb" about="#mwt1"
-typeof="mw:Transclusion"
-data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"http://foo.com/a&amp;#124;b"}},"i":0}}]}'>http://foo.com/a%7Cb</a></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://foo.com/a%7Cb" about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"http://foo.com/a&amp;#124;b"}},"i":0}}]}'>http://foo.com/a%7Cb</a></p>
!! end
!! test
@@ -2942,7 +2986,8 @@ parsoid=html2wt,wt2wt
!! wikitext
{{echo|[http://foo.com/a&#124;b a&#124;b]}}
!! html/php+tidy
-<p><a rel="nofollow" class="external text" href="http://foo.com/a%7Cb">a|b</a></p>
+<p><a rel="nofollow" class="external text" href="http://foo.com/a%7Cb">a&#124;b</a>
+</p>
!! html/parsoid
<p><a rel="mw:ExtLink" href="http://foo.com/a|b" about="#mwt1"
typeof="mw:Transclusion"
@@ -2969,7 +3014,10 @@ parsoid=html2wt,wt2wt
{{echo|<nowiki>&lt;div&gt;</nowiki>}}
{{echo|<nowiki></nowiki>}}
!! html/php+tidy
-<p>foo|bar &lt;div&gt;</p>
+<p>foo|bar
+&lt;div&gt;
+
+</p>
!! html/parsoid
<p><span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo<nowiki>|</nowiki>bar"}},"i":0}}]}'}'>foo</span><span typeof="mw:Nowiki" about="#mwt1">|</span><span about="#mwt1">bar</span>
<span typeof="mw:Transclusion mw:Nowiki" about="#mwt2" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"<nowiki>&amp;lt;div&amp;gt;</nowiki>"}},"i":0}}]}'><span typeof="mw:Entity">&lt;</span>div<span typeof="mw:Entity">&gt;</span></span>
@@ -2985,7 +3033,8 @@ parsoid=html2wt,wt2wt
!! wikitext
{{echo|{{echo|1=bar}}}}
!! html/php+tidy
-<p>bar</p>
+<p>bar
+</p>
!! html/parsoid
<p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"{{echo|1=bar}}"}},"i":0}}]}'>bar</p>
!! end
@@ -2996,7 +3045,8 @@ Templates parameters with special tokenizing behavior dont get modified because
!! wikitext
{{echo|a : b}}
!! html/php+tidy
-<p>a&#160;: b</p>
+<p>a&#160;: b
+</p>
!! html/parsoid
<p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"a : b"}},"i":0}}]}'>a<span typeof="mw:DisplaySpace mw:Placeholder" data-parsoid='{"isDisplayHack":true}'> </span>: b</p>
!! end
@@ -3007,7 +3057,8 @@ Templates: Preserve blank parameter names
!! wikitext
{{echo|=foo}}
!! html/php+tidy
-<p>{{{1}}}</p>
+<p>{{{1}}}
+</p>
!! html/parsoid
<p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"":{"wt":"foo"}},"i":0}}]}'>{{{1}}}</p>
!! end
@@ -3017,7 +3068,9 @@ Templates: Preserve blank parameter names in other positions
!! wikitext
{{blank_param|bar|=foo}}
!! html/php+tidy
-<p>bar foo</p>
+<p>bar
+foo
+</p>
!! html/parsoid
<p about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[{"k":"1"},{"k":"","named":true}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"blank_param","href":"./Template:Blank_param"},"params":{"1":{"wt":"bar"},"":{"wt":"foo"}},"i":0}}]}'>bar
foo</p>
@@ -3121,15 +3174,15 @@ c
2c. Indent-Pre and tables (T44252)
!! wikitext
{|
- |+ foo
- ! | bar
+ |+foo
+ ! |bar
|}
!! html
<table>
-<caption> foo
+<caption>foo
</caption>
<tr>
-<th> bar
+<th>bar
</th></tr></table>
!!end
@@ -3139,14 +3192,14 @@ c
!! wikitext
a
{|
- | b
+ |b
|}
!! html/php
<pre>a
</pre>
<table>
<tr>
-<td> b
+<td>b
</td></tr></table>
!! html/parsoid
@@ -3229,17 +3282,11 @@ a
<p>c </p><blockquote data-parsoid='{"stx":"html"}'> foo </blockquote>
<pre><span> foo </span>
</pre>
-!! html+tidy
-<p>a</p>
-<p>foo</p>
-<p>b</p>
-<div>foo</div>
-<p>c</p>
-<blockquote>
-<p>foo</p>
-</blockquote>
-<pre>
-<span> foo </span>
+!! html/php+tidy
+<p> a </p><p> foo </p><p>
+ b </p><div> foo </div><p>
+ c </p><blockquote><p> foo </p></blockquote>
+<pre><span> foo </span>
</pre>
!! end
@@ -3256,12 +3303,10 @@ a
!! html/parsoid
<pre>a <span data-parsoid='{"stx":"html"}'>foo</span></pre>
b <div data-parsoid='{"stx":"html"}'> foo </div>
-!! html+tidy
-<pre>
-a <span>foo</span>
-</pre>
-<p>b</p>
-<div>foo</div>
+!! html/php+tidy
+<pre>a <span>foo</span>
+</pre><p>
+ b </p><div> foo </div>
!!end
!!test
@@ -3496,7 +3541,8 @@ a <!--
foo
--> b
!! html/php+tidy
-<p>a b</p>
+<p>a b
+</p>
!! html/parsoid
<p>a <!--
foo
@@ -3527,11 +3573,8 @@ foo
foo
</pre>
!! html/php+tidy
-<pre>
-foo
-</pre>
-<pre>
-foo
+<pre>foo</pre>
+<pre>foo
</pre>
<pre>
@@ -3662,19 +3705,19 @@ HTML-pre: 3: other wikitext
!! test
Simple definition
!! wikitext
-; name : Definition
+;name :Definition
!! html
-<dl><dt> name&#160;</dt>
-<dd> Definition</dd></dl>
+<dl><dt>name&#160;</dt>
+<dd>Definition</dd></dl>
!! end
!! test
Definition list for indentation only
!! wikitext
-: Indented text
+:Indented text
!! html
-<dl><dd> Indented text</dd></dl>
+<dl><dd>Indented text</dd></dl>
!! end
@@ -3691,10 +3734,10 @@ Definition list with no space
!! test
Definition list with URL link
!! wikitext
-; http://example.com/ : definition
+;http://example.com/ :definition
!! html
-<dl><dt> <a rel="nofollow" class="external free" href="http://example.com/">http://example.com/</a>&#160;</dt>
-<dd> definition</dd></dl>
+<dl><dt><a rel="nofollow" class="external free" href="http://example.com/">http://example.com/</a>&#160;</dt>
+<dd>definition</dd></dl>
!! end
@@ -3711,10 +3754,10 @@ Definition list with bracketed URL link
!! test
Definition list with wikilink containing colon
!! wikitext
-; [[Help:FAQ]]: The least-read page on Wikipedia
+; [[Help:FAQ]]:The least-read page on Wikipedia
!! html
-<dl><dt> <a href="/index.php?title=Help:FAQ&amp;action=edit&amp;redlink=1" class="new" title="Help:FAQ (page does not exist)">Help:FAQ</a></dt>
-<dd> The least-read page on Wikipedia</dd></dl>
+<dl><dt><a href="/index.php?title=Help:FAQ&amp;action=edit&amp;redlink=1" class="new" title="Help:FAQ (page does not exist)">Help:FAQ</a></dt>
+<dd>The least-read page on Wikipedia</dd></dl>
!! end
@@ -3722,13 +3765,13 @@ Definition list with wikilink containing colon
!! test
Definition list with news link containing colon
!! wikitext
-; news:alt.wikipedia.rox: This isn't even a real newsgroup!
+;news:alt.wikipedia.rox: This isn't even a real newsgroup!
!! html/php
-<dl><dt> <a rel="nofollow" class="external free" href="news:alt.wikipedia.rox">news:alt.wikipedia.rox</a></dt>
-<dd> This isn't even a real newsgroup!</dd></dl>
+<dl><dt><a rel="nofollow" class="external free" href="news:alt.wikipedia.rox">news:alt.wikipedia.rox</a></dt>
+<dd>This isn't even a real newsgroup!</dd></dl>
!! html/parsoid
-<dl><dt> <a rel="mw:ExtLink" href="news:alt.wikipedia.rox" data-parsoid='{"stx":"url"}'>news:alt.wikipedia.rox</a></dt><dd data-parsoid='{"stx":"row"}'> This isn't even a real newsgroup!</dd></dl>
+<dl><dt> <a rel="mw:ExtLink" class="external free" href="news:alt.wikipedia.rox" data-parsoid='{"stx":"url"}'>news:alt.wikipedia.rox</a></dt><dd data-parsoid='{"stx":"row"}'>This isn't even a real newsgroup!</dd></dl>
!! end
!! test
@@ -3736,17 +3779,17 @@ Malformed definition list with colon
!! wikitext
; news:alt.wikipedia.rox -- don't crash or enter an infinite loop
!! html
-<dl><dt> <a rel="nofollow" class="external free" href="news:alt.wikipedia.rox">news:alt.wikipedia.rox</a> -- don't crash or enter an infinite loop</dt></dl>
+<dl><dt><a rel="nofollow" class="external free" href="news:alt.wikipedia.rox">news:alt.wikipedia.rox</a> -- don't crash or enter an infinite loop</dt></dl>
!! end
!! test
Definition lists: colon in external link text
!! wikitext
-; [http://www.wikipedia2.org/ Wikipedia : The Next Generation]: OK, I made that up
+;[http://www.wikipedia2.org/ Wikipedia :The Next Generation] :OK, I made that up
!! html
-<dl><dt> <a rel="nofollow" class="external text" href="http://www.wikipedia2.org/">Wikipedia&#160;: The Next Generation</a></dt>
-<dd> OK, I made that up</dd></dl>
+<dl><dt><a rel="nofollow" class="external text" href="http://www.wikipedia2.org/">Wikipedia&#160;:The Next Generation</a>&#160;</dt>
+<dd>OK, I made that up</dd></dl>
!! end
@@ -3762,32 +3805,30 @@ Definition lists: colon in HTML attribute
!! test
Definition lists: self-closed tag
!! wikitext
-;one<br/>two : two-line fun
+;one<br/>two :two-line fun
!! html
<dl><dt>one<br />two&#160;</dt>
-<dd> two-line fun</dd></dl>
+<dd>two-line fun</dd></dl>
!! end
!! test
Definition lists: ignore colons inside tags
!! wikitext
-;one <b>two : tag <i>fun:</i>:</b>: def
+;one <b>two : tag <i>fun:</i>:</b>:def
!! html
<dl><dt>one <b>two&#160;: tag <i>fun:</i>:</b></dt>
-<dd> def</dd></dl>
+<dd>def</dd></dl>
!! end
!! test
Definition lists: excess closed tags
!! wikitext
-;one</b>two : bad tag fun
+;one</b>two :bad tag fun
!! html/php+tidy
-<dl>
-<dt>onetwo&#160;</dt>
-<dd>bad tag fun</dd>
-</dl>
+<dl><dt>onetwo&#160;</dt>
+<dd>bad tag fun</dd></dl>
!! html/parsoid
<dl>
<dt>onetwo</dt>
@@ -3818,14 +3859,14 @@ T13748: Literal closing tags
Definition and unordered list using wiki syntax nested in unordered list using html tags.
!! wikitext
<ul><li>
-; term : description
-* unordered
+;term :description
+*unordered
</li></ul>
!! html
<ul><li>
-<dl><dt> term&#160;</dt>
-<dd> description</dd></dl>
-<ul><li> unordered</li></ul>
+<dl><dt>term&#160;</dt>
+<dd>description</dd></dl>
+<ul><li>unordered</li></ul>
</li></ul>
!! end
@@ -3833,10 +3874,11 @@ Definition and unordered list using wiki syntax nested in unordered list using h
!! test
Definition list with empty definition and following paragraph
!! wikitext
-; term:
+;term:
+
Paragraph text
!! html
-<dl><dt> term</dt>
+<dl><dt>term</dt>
<dd></dd></dl>
<p>Paragraph text
</p>
@@ -3923,6 +3965,29 @@ should be left alone
!! end
!! test
+Definition Lists: Hacky use to indent tables (with content following table)
+!! wikitext
+:{|
+|foo
+|bar
+|} <!--c1--> this text should be part of the dl
+!! html/php+tidy
+<dl><dd><table>
+<tbody><tr>
+<td>foo
+</td>
+<td>bar
+</td></tr></tbody></table> this text should be part of the dl</dd></dl>
+!! html/parsoid
+<dl><dd><table>
+<tbody><tr>
+<td>foo
+</td>
+<td>bar
+</td></tr></tbody></table> <!--c1--> this text should be part of the dl</dd></dl>
+!! end
+
+!! test
Definition Lists: Hacky use to indent tables, with comments (T65979)
!! wikitext
<!-- foo -->
@@ -4014,22 +4079,24 @@ Table / list interaction: indented table with lists in table contents
!! wikitext
:{|
|-
-| a
-* b
+|a
+
+*b
|-
-| c
-* d
+|c
+
+*d
|}
!! html
<dl><dd><table>
<tr>
-<td> a
-<ul><li> b</li></ul>
+<td>a
+<ul><li>b</li></ul>
</td></tr>
<tr>
-<td> c
-<ul><li> d</li></ul>
+<td>c
+<ul><li>d</li></ul>
</td></tr></table></dd></dl>
!! end
@@ -4066,13 +4133,11 @@ Table / list interaction: lists nested in tables nested in indented lists
!! test
Definition Lists: Nesting: Multi-level (Parsoid only)
-!! options
-parsoid
!! wikitext
;t1 :d1
;;t2 ::d2
;;;t3 :::d3
-!! html
+!! html/parsoid
<dl>
<dt>t1 </dt>
<dd>d1</dd>
@@ -4095,72 +4160,26 @@ parsoid
!! test
-Definition Lists: Nesting: Test 2 (Parsoid only)
+Definition Lists: Nesting: Test 2
!! wikitext
;t1
::d2
-!! html/php+tidy
-<dl>
-<dt>t1</dt>
+!! html+tidy
+<dl><dt>t1</dt>
<dd>
-<dl>
-<dd>d2</dd>
-</dl>
-</dd>
-</dl>
-!! html/parsoid
-<dl>
- <dt>t1</dt>
- <dd>
- <dl>
- <dd>d2</dd>
- </dl>
- </dd>
-</dl>
-
+<dl><dd>d2</dd></dl></dd></dl>
!! end
!! test
-Definition Lists: Nesting: Test 3 (Parsoid only)
+Definition Lists: Nesting: Test 3
!! wikitext
:;t1
::::d2
-!! html/php+tidy
-<dl>
-<dd>
-<dl>
-<dt>t1</dt>
-<dd>
-<dl>
+!! html+tidy
+<dl><dd><dl><dt>t1</dt>
<dd>
-<dl>
-<dd>d2</dd>
-</dl>
-</dd>
-</dl>
-</dd>
-</dl>
-</dd>
-</dl>
-!! html/parsoid
-<dl>
- <dd>
- <dl>
- <dt>t1</dt>
- <dd>
- <dl>
- <dd>
- <dl>
- <dd>d2</dd>
- </dl>
- </dd>
- </dl>
- </dd>
- </dl>
- </dd>
-</dl>
-
+<dl><dd><dl><dd>d2</dd></dl></dd></dl></dd></dl></dd></dl>
!! end
@@ -4185,42 +4204,30 @@ Definition Lists: Nesting: Test 4
!! test
Definition Lists: Mixed Lists: Test 1
!! wikitext
-:;* foo
-::* bar
-:; baz
+:;*foo
+::*bar
+:;baz
!! html/php
-<dl><dd><dl><dt><ul><li> foo</li>
-<li> bar</li></ul></dt></dl>
-<dl><dt> baz</dt></dl></dd></dl>
+<dl><dd><dl><dt><ul><li>foo</li>
+<li>bar</li></ul></dt></dl>
+<dl><dt>baz</dt></dl></dd></dl>
!! html/php+tidy
-<dl>
-<dd>
-<dl>
-<dd>
-<ul>
-<li>foo</li>
-<li>bar</li>
-</ul>
-</dd>
-</dl>
-<dl>
-<dt>baz</dt>
-</dl>
-</dd>
-</dl>
+<dl><dd><dl><dt><ul><li>foo</li>
+<li>bar</li></ul></dt></dl>
+<dl><dt>baz</dt></dl></dd></dl>
!! html/parsoid
<dl>
<dd><dl>
<dt><ul>
-<li> foo
+<li>foo
</li>
</ul></dt>
<dd><ul>
-<li> bar
+<li>bar
</li>
</ul></dd>
-<dt> baz</dt>
+<dt>baz</dt>
</dl></dd>
</dl>
!! end
@@ -4228,11 +4235,11 @@ Definition Lists: Mixed Lists: Test 1
!! test
Definition Lists: Mixed Lists: Test 2
!! wikitext
-*: d1
-*: d2
+*:d1
+*:d2
!! html
-<ul><li><dl><dd> d1</dd>
-<dd> d2</dd></dl></li></ul>
+<ul><li><dl><dd>d1</dd>
+<dd>d2</dd></dl></li></ul>
!! end
@@ -4240,11 +4247,11 @@ Definition Lists: Mixed Lists: Test 2
!! test
Definition Lists: Mixed Lists: Test 3
!! wikitext
-*::: d1
-*::: d2
+*:::d1
+*:::d2
!! html
-<ul><li><dl><dd><dl><dd><dl><dd> d1</dd>
-<dd> d2</dd></dl></dd></dl></dd></dl></li></ul>
+<ul><li><dl><dd><dl><dd><dl><dd>d1</dd>
+<dd>d2</dd></dl></dd></dl></dd></dl></li></ul>
!! end
@@ -4267,10 +4274,10 @@ Definition Lists: Mixed Lists: Test 4
Definition Lists: Mixed Lists: Test 5
!! wikitext
*:d1
-*:: d2
+*::d2
!! html
<ul><li><dl><dd>d1
-<dl><dd> d2</dd></dl></dd></dl></li></ul>
+<dl><dd>d2</dd></dl></dd></dl></li></ul>
!! end
@@ -4279,10 +4286,10 @@ Definition Lists: Mixed Lists: Test 5
Definition Lists: Mixed Lists: Test 6
!! wikitext
#*:d1
-#*::: d3
+#*:::d3
!! html
<ol><li><ul><li><dl><dd>d1
-<dl><dd><dl><dd> d3</dd></dl></dd></dl></dd></dl></li></ul></li></ol>
+<dl><dd><dl><dd>d3</dd></dl></dd></dl></dd></dl></li></ul></li></ol>
!! end
@@ -4290,11 +4297,11 @@ Definition Lists: Mixed Lists: Test 6
!! test
Definition Lists: Mixed Lists: Test 7
!! wikitext
-:* d1
-:* d2
+:*d1
+:*d2
!! html
-<dl><dd><ul><li> d1</li>
-<li> d2</li></ul></dd></dl>
+<dl><dd><ul><li>d1</li>
+<li>d2</li></ul></dd></dl>
!! end
@@ -4302,11 +4309,11 @@ Definition Lists: Mixed Lists: Test 7
!! test
Definition Lists: Mixed Lists: Test 8
!! wikitext
-:* d1
-::* d2
+:*d1
+::*d2
!! html
-<dl><dd><ul><li> d1</li></ul>
-<dl><dd><ul><li> d2</li></ul></dd></dl></dd></dl>
+<dl><dd><ul><li>d1</li></ul>
+<dl><dd><ul><li>d2</li></ul></dd></dl></dd></dl>
!! end
@@ -4332,27 +4339,28 @@ Definition Lists: Mixed Lists: Test 10
!! end
+# The Parsoid team disagrees with the PHP parser's seemingly-random
+# rules regarding dd/dt on the next few tests. Parsoid is more
+# consistent, and recognizes the shared nesting and keeps the
+# still-open tags around until the nesting is complete.
+
# This is a regression test for T175099
-# html/php+tidy is insufficient since Tidy covers up the bug.
-# But once Tidy is replaced with RemexHTML, html/php+tidy is good enough
!! test
Definition Lists: Mixed Lists: Test 11
!! wikitext
-; a
-:* b
-!! html/*
-<dl><dt> a</dt>
+;a
+:*b
+!! html/php
+<dl><dt>a</dt>
<dd>
-<ul><li> b</li></ul></dd></dl>
+<ul><li>b</li></ul></dd></dl>
+!! html/parsoid
+<dl><dt>a
+<dd><ul><li>b</li></ul></dd></dl>
!! end
-# The Parsoid team disagrees with the PHP parser's seemingly-random
-# rules regarding dd/dt on the next two tests. Parsoid is more
-# consistent, and recognizes the shared nesting and keeps the
-# still-open tags around until the nesting is complete.
-# (And tidy again converts <dt> to <dd> before 'bar'.)
-
+# FIXME: Maybe get rid of this test?
!! test
Definition Lists: Mixed Lists: Test 12
!! wikitext
@@ -4365,42 +4373,10 @@ Definition Lists: Mixed Lists: Test 12
<dd>baz</dd></dl></li></ol></li></ul></li></ol></li></ul>
!! html/php+tidy
-<ul>
-<li>
-<ol>
-<li>
-<ul>
-<li>
-<ol>
-<li>
-<dl>
-<dt>foo&#160;</dt>
-<dd>
-<ul>
-<li>
-<dl>
-<dd>
-<dl>
-<dt>bar</dt>
-</dl>
-</dd>
-</dl>
-</li>
-</ul>
-</dd>
-</dl>
-<dl>
-<dt>boo&#160;</dt>
-<dd>baz</dd>
-</dl>
-</li>
-</ol>
-</li>
-</ul>
-</li>
-</ol>
-</li>
-</ul>
+<ul><li><ol><li><ul><li><ol><li><dl><dt>foo&#160;</dt>
+<dd><ul><li><dl><dt><dl><dt>bar</dt></dl></dt></dl></li></ul></dd></dl></li></ol></li></ul>
+<dl><dt>boo&#160;</dt>
+<dd>baz</dd></dl></li></ol></li></ul>
!! html/parsoid
<ul>
<li>
@@ -4431,52 +4407,17 @@ Definition Lists: Mixed Lists: Test 12
</ul>
!! end
-
-# Another case where tidy converts a <dt> to a <dd> (but Parsoid doesn't).
+# FIXME: Maybe get rid of this test?
# From whitelist:
# * The test is wrong, there are two colons where there should be :;
# * The PHP parser is wrong to close the <dl> after the <dt> containing the <ul>.
!! test
Definition Lists: Weird Ones: Test 1
!! wikitext
-*#;*::;; foo : bar (who uses this?)
-!! html/php
-<ul><li><ol><li><dl><dt> foo&#160;</dt>
-<dd><ul><li><dl><dd><dl><dd><dl><dt><dl><dt> bar (who uses this?)</dt></dl></dd></dl></dd></dl></dd></dl></li></ul></dd></dl></li></ol></li></ul>
-
+*#;*::;;foo :bar (who uses this?)
!! html/php+tidy
-<ul>
-<li>
-<ol>
-<li>
-<dl>
-<dt>foo&#160;</dt>
-<dd>
-<ul>
-<li>
-<dl>
-<dd>
-<dl>
-<dd>
-<dl>
-<dd>
-<dl>
-<dt>bar (who uses this?)</dt>
-</dl>
-</dd>
-</dl>
-</dd>
-</dl>
-</dd>
-</dl>
-</li>
-</ul>
-</dd>
-</dl>
-</li>
-</ol>
-</li>
-</ul>
+<ul><li><ol><li><dl><dt>foo&#160;</dt>
+<dd><ul><li><dl><dd><dl><dd><dl><dt><dl><dt>bar (who uses this?)</dt></dl></dt></dl></dd></dl></dd></dl></li></ul></dd></dl></li></ol></li></ul>
!! html/parsoid
<ul>
<li>
@@ -4493,8 +4434,8 @@ Definition Lists: Weird Ones: Test 1
<dl>
<dt>
<dl>
-<dt> foo<span typeof="mw:DisplaySpace mw:Placeholder" data-parsoid='{"src":" ","isDisplayHack":true}'> </span></dt>
-<dd data-parsoid='{"stx":"row"}'> bar (who uses this?)</dd>
+<dt>foo<span typeof="mw:DisplaySpace mw:Placeholder" data-parsoid='{"src":" ","isDisplayHack":true}'> </span></dt>
+<dd data-parsoid='{"stx":"row"}'>bar (who uses this?)</dd>
</dl></dt>
</dl></dd>
</dl></dd>
@@ -4519,37 +4460,18 @@ Definition Lists: colons occurring in tags
;{{echo|''a:b''}}
;;;''a:b''
!! html+tidy
-<dl>
-<dt>a</dt>
+<dl><dt>a</dt>
<dd>b</dd>
<dt><b>a:b</b></dt>
<dt><i>a:b</i></dt>
<dt><span>a:b</span></dt>
-<dd>
-<div>a:b</div>
-</dd>
-<dd>
-<div>a
-<dl>
+<dt><div>a:b</div></dt>
+<dt><div>a</div></dt>
<dd>b</dd>
-</dl>
-</div>
-</dd>
<dt>a</dt>
<dd>b</dd>
-<dt><i>a:b</i></dt>
-</dl>
-<dl>
-<dd>
-<dl>
-<dd>
-<dl>
-<dt><i>a:b</i></dt>
-</dl>
-</dd>
-</dl>
-</dd>
-</dl>
+<dt><i>a:b</i></dt></dl>
+<dl><dt><dl><dt><dl><dt><i>a:b</i></dt></dl></dt></dl></dt></dl>
!! html/parsoid
<dl><dt>a</dt><dd data-parsoid='{"stx":"row"}'>b</dd>
<dt><b>a:b</b></dt>
@@ -4563,52 +4485,40 @@ Definition Lists: colons occurring in tags
<dl><dt><dl><dt><i>a:b</i></dt></dl></dt></dl></dt></dl>
!! end
+# Parsoid's output differs here again because it shares
+# nesting between the two lists unlike the PHP parser.
+# Unsure which is more desirable.
!! test
Definition Lists: colons and tables 1
!! wikitext
:{|
-| x
+|x
|}
:{|
-| y
+|y
|}
-!! html
+!! html/php
<dl><dd><table>
<tr>
-<td> x
+<td>x
</td></tr></table></dd></dl>
<dl><dd><table>
<tr>
-<td> y
+<td>y
</td></tr></table></dd></dl>
-!! end
-
-# Parsoid's output (as documented below) differs from php's in this case.
-# This is probably a bug. If we fixup parsoid to match php's output, the
-# above test should pass and the below test case can be removed. It is
-# unclear which output is more desirable.
-
-!! test
-Definition Lists: colons and tables 2
-!! wikitext
-:{|
-| x
-|}
-:{|
-| y
-|}
!! html/parsoid
<dl><dd><table>
<tr>
-<td> x
+<td>x
</td></tr></table></dd>
<dd><table>
<tr>
-<td> y
+<td>y
</td></tr></table></dd></dl>
!! end
+# FIXME: Does this need a html/php section?
!! test
Definition Lists: template interaction
!! wikitext
@@ -4657,9 +4567,9 @@ Numbered: <a rel="nofollow" class="external autonumber" href="http://example.net
Numbered: <a rel="nofollow" class="external autonumber" href="http://example.com">[3]</a>
</p>
!! html/parsoid
-<p>Numbered: <a rel="mw:ExtLink" href="http://example.com"></a>
-Numbered: <a rel="mw:ExtLink" href="http://example.net"></a>
-Numbered: <a rel="mw:ExtLink" href="http://example.com"></a></p>
+<p>Numbered: <a rel="mw:ExtLink" class="external autonumber" href="http://example.com"></a>
+Numbered: <a rel="mw:ExtLink" class="external autonumber" href="http://example.net"></a>
+Numbered: <a rel="mw:ExtLink" class="external autonumber" href="http://example.com"></a></p>
!!end
!! test
@@ -4698,7 +4608,7 @@ External links: dollar sign in URL (autonumber)
<p><a rel="nofollow" class="external autonumber" href="http://example.com/1$2345">[1]</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com/1$2345"></a></p>
+<p><a rel="mw:ExtLink" class="external autonumber" href="http://example.com/1$2345"></a></p>
!!end
!! test
@@ -4711,7 +4621,7 @@ http://example.com/1[2345
<p><a rel="nofollow" class="external free" href="http://example.com/1">http://example.com/1</a>[2345
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com/1">http://example.com/1</a>[2345</p>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com/1">http://example.com/1</a>[2345</p>
!! end
!! test
@@ -4724,7 +4634,7 @@ parsoid=wt2html,html2html
<p><a rel="nofollow" class="external text" href="http://example.com/1">[2345</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com/1">[2345</a></p>
+<p><a rel="mw:ExtLink" class="external text" href="http://example.com/1">[2345</a></p>
!!end
# parsoid adds a space before the link name
@@ -4785,7 +4695,7 @@ External links: protocol-relative URL in brackets without text
<p><a rel="nofollow" class="external autonumber" href="//example.com">[1]</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="//example.com"></a></p>
+<p><a rel="mw:ExtLink" class="external autonumber" href="//example.com"></a></p>
!! end
!! test
@@ -4806,8 +4716,11 @@ foo//example.com/Foo
</p>
!! end
+## html2wt and html2html will fail because we will prefer the :en: interwiki prefix over wikipedia:
!! test
External links: with no contents
+!! options
+parsoid=wt2html,wt2wt
!! wikitext
[http://en.wikipedia.org/wiki/Foo]
@@ -4820,9 +4733,9 @@ External links: with no contents
</p><p><a href="http://en.wikipedia.org/wiki/Foo" class="extiw" title="wikipedia:Foo"><span>Bar</span></a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://en.wikipedia.org/wiki/Foo"></a></p>
-<p><a rel="mw:ExtLink" href="http://en.wikipedia.org/wiki/Foo" title="wikipedia:Foo">Bar</a></p>
-<p><a rel="mw:ExtLink" href="http://en.wikipedia.org/wiki/Foo" title="wikipedia:Foo"><span>Bar</span></a></p>
+<p><a rel="mw:ExtLink" class="external autonumber" href="http://en.wikipedia.org/wiki/Foo"></a></p>
+<p><a rel="mw:WikiLink/Interwiki" href="http://en.wikipedia.org/wiki/Foo" title="wikipedia:Foo">Bar</a></p>
+<p><a rel="mw:WikiLink/Interwiki" href="http://en.wikipedia.org/wiki/Foo" title="wikipedia:Foo"><span>Bar</span></a></p>
!! end
!! test
@@ -4869,25 +4782,25 @@ http://example.com/url_with_entity&#60;
<a rel="nofollow" class="external free" href="http://example.com/url_with_entity">http://example.com/url_with_entity</a>&#60;
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com">http://example.com</a>,
-<a rel="mw:ExtLink" href="http://example.com">http://example.com</a>;
-<a rel="mw:ExtLink" href="http://example.com">http://example.com</a>\
-<a rel="mw:ExtLink" href="http://example.com">http://example.com</a>.
-<a rel="mw:ExtLink" href="http://example.com">http://example.com</a>:
-<a rel="mw:ExtLink" href="http://example.com">http://example.com</a>!
-<a rel="mw:ExtLink" href="http://example.com">http://example.com</a>?
-<a rel="mw:ExtLink" href="http://example.com">http://example.com</a>)
-<a rel="mw:ExtLink" href="http://example.com/url_with_(brackets)">http://example.com/url_with_(brackets)</a>
-(<a rel="mw:ExtLink" href="http://example.com/url_without_brackets">http://example.com/url_without_brackets</a>)
-<a rel="mw:ExtLink" href="http://example.com/url_with_entity&amp;">http://example.com/url_with_entity&amp;</a>
-<a rel="mw:ExtLink" href="http://example.com/url_with_entity&amp;">http://example.com/url_with_entity&amp;</a>
-<a rel="mw:ExtLink" href="http://example.com/url_with_entity&amp;">http://example.com/url_with_entity&amp;</a>
-<a rel="mw:ExtLink" href="http://example.com/url_with_entity">http://example.com/url_with_entity</a><span typeof="mw:Entity" data-parsoid='{"src":"&amp;nbsp;","srcContent":" "}'> </span>
-<a rel="mw:ExtLink" href="http://example.com/url_with_entity">http://example.com/url_with_entity</a><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#xA0;","srcContent":" "}'> </span>
-<a rel="mw:ExtLink" href="http://example.com/url_with_entity">http://example.com/url_with_entity</a><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#160;","srcContent":" "}'> </span>
-<a rel="mw:ExtLink" href="http://example.com/url_with_entity">http://example.com/url_with_entity</a><span typeof="mw:Entity" data-parsoid='{"src":"&amp;lt;","srcContent":"&lt;"}'>&lt;</span>
-<a rel="mw:ExtLink" href="http://example.com/url_with_entity">http://example.com/url_with_entity</a><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#x3C;","srcContent":"&lt;"}'>&lt;</span>
-<a rel="mw:ExtLink" href="http://example.com/url_with_entity">http://example.com/url_with_entity</a><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#60;","srcContent":"&lt;"}'>&lt;</span></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a>,
+<a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a>;
+<a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a>\
+<a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a>.
+<a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a>:
+<a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a>!
+<a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a>?
+<a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a>)
+<a rel="mw:ExtLink" class="external free" href="http://example.com/url_with_(brackets)">http://example.com/url_with_(brackets)</a>
+(<a rel="mw:ExtLink" class="external free" href="http://example.com/url_without_brackets">http://example.com/url_without_brackets</a>)
+<a rel="mw:ExtLink" class="external free" href="http://example.com/url_with_entity&amp;">http://example.com/url_with_entity&amp;</a>
+<a rel="mw:ExtLink" class="external free" href="http://example.com/url_with_entity&amp;">http://example.com/url_with_entity&amp;</a>
+<a rel="mw:ExtLink" class="external free" href="http://example.com/url_with_entity&amp;">http://example.com/url_with_entity&amp;</a>
+<a rel="mw:ExtLink" class="external free" href="http://example.com/url_with_entity">http://example.com/url_with_entity</a><span typeof="mw:Entity" data-parsoid='{"src":"&amp;nbsp;","srcContent":" "}'> </span>
+<a rel="mw:ExtLink" class="external free" href="http://example.com/url_with_entity">http://example.com/url_with_entity</a><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#xA0;","srcContent":" "}'> </span>
+<a rel="mw:ExtLink" class="external free" href="http://example.com/url_with_entity">http://example.com/url_with_entity</a><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#160;","srcContent":" "}'> </span>
+<a rel="mw:ExtLink" class="external free" href="http://example.com/url_with_entity">http://example.com/url_with_entity</a><span typeof="mw:Entity" data-parsoid='{"src":"&amp;lt;","srcContent":"&lt;"}'>&lt;</span>
+<a rel="mw:ExtLink" class="external free" href="http://example.com/url_with_entity">http://example.com/url_with_entity</a><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#x3C;","srcContent":"&lt;"}'>&lt;</span>
+<a rel="mw:ExtLink" class="external free" href="http://example.com/url_with_entity">http://example.com/url_with_entity</a><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#60;","srcContent":"&lt;"}'>&lt;</span></p>
!! end
!! test
@@ -4900,7 +4813,7 @@ http://example.com/url_with_entity&amp;amp;
<p><a rel="nofollow" class="external free" href="http://example.com/url_with_entity&amp;amp">http://example.com/url_with_entity&amp;amp</a>;
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com/url_with_entity&amp;amp">http://example.com/url_with_entity&amp;amp</a>;</p>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com/url_with_entity&amp;amp">http://example.com/url_with_entity&amp;amp</a>;</p>
!! end
!! test
@@ -4915,7 +4828,7 @@ news:'a'b''c''d e
</p>
!! html/parsoid
<p><b>News:</b> Stuff here</p>
-<p><a rel="mw:ExtLink" href="news:'a'b">news:'a'b</a><i>c</i>d e</p>
+<p><a rel="mw:ExtLink" class="external free" href="news:'a'b">news:'a'b</a><i>c</i>d e</p>
!! end
!! test
@@ -4926,7 +4839,7 @@ External links: with entity
<p><a rel="nofollow" class="external text" href="http://+www.librarieswithoutborders.org">Libraries without borders</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://+www.librarieswithoutborders.org" data-parsoid='{"a":{"href":"http://+www.librarieswithoutborders.org"},"sa":{"href":"http://&amp;#x20;www.librarieswithoutborders.org"}}'>Libraries without borders</a></p>
+<p><a rel="mw:ExtLink" class="external text" href="http://+www.librarieswithoutborders.org" data-parsoid='{"a":{"href":"http://+www.librarieswithoutborders.org"},"sa":{"href":"http://&amp;#x20;www.librarieswithoutborders.org"}}'>Libraries without borders</a></p>
!! end
!! test
@@ -5056,10 +4969,10 @@ parsoid=wt2html
!! wikitext
URL in text: [http://example.com http://example.com]
!! html/php
-<p>URL in text: <a rel="nofollow" class="external free" href="http://example.com">http://example.com</a>
+<p>URL in text: <a rel="nofollow" class="external text" href="http://example.com">http://example.com</a>
</p>
!! html/parsoid
-<p>URL in text: <a rel="mw:ExtLink" href="http://example.com">http://example.com</a></p>
+<p>URL in text: <a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a></p>
!! end
!! test
@@ -5070,7 +4983,7 @@ ja-style clickable images: [http://example.com http://meta.wikimedia.org/upload/
<p>ja-style clickable images: <a rel="nofollow" class="external text" href="http://example.com"><img src="http://meta.wikimedia.org/upload/f/f1/Ncwikicol.png" alt="Ncwikicol.png"/></a>
</p>
!! html/parsoid
-<p>ja-style clickable images: <a rel="mw:ExtLink" href="http://example.com"><img src="http://meta.wikimedia.org/upload/f/f1/Ncwikicol.png" alt="Ncwikicol.png" data-parsoid='{"type":"extlink"}'/></a></p>
+<p>ja-style clickable images: <a rel="mw:ExtLink" class="external text" href="http://example.com"><img src="http://meta.wikimedia.org/upload/f/f1/Ncwikicol.png" alt="Ncwikicol.png" data-parsoid='{"type":"extlink"}'/></a></p>
!! end
!! test
@@ -5090,7 +5003,7 @@ Old &amp; use: http://x&amp;y
<p>Old &amp; use: <a rel="nofollow" class="external free" href="http://x&amp;y">http://x&amp;y</a>
</p>
!! html/parsoid
-<p>Old <span typeof="mw:Entity">&amp;</span> use: <a rel="mw:ExtLink" href="http://x&amp;y">http://x&amp;y</a></p>
+<p>Old <span typeof="mw:Entity">&amp;</span> use: <a rel="mw:ExtLink" class="external free" href="http://x&amp;y">http://x&amp;y</a></p>
!! end
!! test
@@ -5101,7 +5014,7 @@ http://example.com/?foo&#61;bar
<p><a rel="nofollow" class="external free" href="http://example.com/?foo=bar">http://example.com/?foo=bar</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com/?foo=bar">http://example.com/?foo=bar</a></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com/?foo=bar">http://example.com/?foo=bar</a></p>
!! end
##
@@ -5118,7 +5031,7 @@ Old &amp; use: [http://x&y]
<p>Old &amp; use: <a rel="nofollow" class="external autonumber" href="http://x&amp;y">[1]</a>
</p>
!! html/parsoid
-<p>Old <span typeof="mw:Entity">&amp;</span> use: <a rel="mw:ExtLink" href="http://x&amp;y"></a></p>
+<p>Old <span typeof="mw:Entity">&amp;</span> use: <a rel="mw:ExtLink" class="external autonumber" href="http://x&amp;y"></a></p>
!! end
# note that parsoid html is identical to [raw ampersand] case; so html2wt
@@ -5133,7 +5046,7 @@ Old &amp; use: [http://x&amp;y]
<p>Old &amp; use: <a rel="nofollow" class="external autonumber" href="http://x&amp;y">[1]</a>
</p>
!! html/parsoid
-<p>Old <span typeof="mw:Entity">&amp;</span> use: <a rel="mw:ExtLink" href="http://x&amp;y"></a></p>
+<p>Old <span typeof="mw:Entity">&amp;</span> use: <a rel="mw:ExtLink" class="external autonumber" href="http://x&amp;y"></a></p>
!! end
!! test
@@ -5144,7 +5057,7 @@ External links: [raw equals]
<p><a rel="nofollow" class="external autonumber" href="http://example.com/?foo=bar">[1]</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com/?foo=bar"></a></p>
+<p><a rel="mw:ExtLink" class="external autonumber" href="http://example.com/?foo=bar"></a></p>
!! end
# note that parsoid html is identical to [raw equals] case; so html2wt
@@ -5159,7 +5072,7 @@ parsoid=wt2html,wt2wt,html2html
<p><a rel="nofollow" class="external autonumber" href="http://example.com/?foo=bar">[1]</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com/?foo=bar"></a></p>
+<p><a rel="mw:ExtLink" class="external autonumber" href="http://example.com/?foo=bar"></a></p>
!! end
# xxx parsoid strips the IDN character, so the round-trip tests will
@@ -5174,7 +5087,7 @@ parsoid=wt2html,wt2wt,html2html
<p><a rel="nofollow" class="external autonumber" href="http://example.com/">[1]</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com/"></a></p>
+<p><a rel="mw:ExtLink" class="external autonumber" href="http://example.com/"></a></p>
!! end
# FIXME: This test (the IDN characters in the text of a link) is an inconsistency.
@@ -5206,7 +5119,7 @@ http://e&zwnj;xample.com/
<p><a rel="nofollow" class="external free" href="http://example.com/">http://example.com/</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com/">http://example.com/</a></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com/">http://example.com/</a></p>
!! end
!! test
@@ -5227,7 +5140,7 @@ External links: URL within URL (T2002)
<p><a rel="nofollow" class="external autonumber" href="http://www.unausa.org/newindex.asp?place=http://www.unausa.org/programs/mun.asp">[1]</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://www.unausa.org/newindex.asp?place=http://www.unausa.org/programs/mun.asp"></a></p>
+<p><a rel="mw:ExtLink" class="external autonumber" href="http://www.unausa.org/newindex.asp?place=http://www.unausa.org/programs/mun.asp"></a></p>
!! end
!! test
@@ -5265,7 +5178,7 @@ http://www.example.com/<b>html</b>
<p><a rel="nofollow" class="external free" href="http://www.example.com/">http://www.example.com/</a><b>html</b>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://www.example.com/" data-parsoid='{"stx":"url"}'>http://www.example.com/</a><b data-parsoid='{"stx":"html"}'>html</b></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://www.example.com/" data-parsoid='{"stx":"url"}'>http://www.example.com/</a><b data-parsoid='{"stx":"html"}'>html</b></p>
!! end
!! test
@@ -5324,17 +5237,22 @@ External links: link text with spaces
</p>
!! end
+# Note edge case difference between PHP and Parsoid here.
!! test
External links: wiki links within external link (T5695)
!! options
parsoid=wt2html,html2html
!! wikitext
[http://example.com [[wikilink]] embedded in ext link]
+
+[http://example.com test [[wikilink]] embedded in ext link]
!! html/php
<p><a rel="nofollow" class="external text" href="http://example.com"></a><a href="/index.php?title=Wikilink&amp;action=edit&amp;redlink=1" class="new" title="Wikilink (page does not exist)">wikilink</a><a rel="nofollow" class="external text" href="http://example.com"> embedded in ext link</a>
+</p><p><a rel="nofollow" class="external text" href="http://example.com">test </a><a href="/index.php?title=Wikilink&amp;action=edit&amp;redlink=1" class="new" title="Wikilink (page does not exist)">wikilink</a><a rel="nofollow" class="external text" href="http://example.com"> embedded in ext link</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com"></a><a rel="mw:WikiLink" href="./Wikilink" title="Wikilink">wikilink</a><span> embedded in ext link</span></p>
+<p><a rel="mw:ExtLink" class="external autonumber" href="http://example.com"></a><a rel="mw:WikiLink" href="./Wikilink" title="Wikilink">wikilink</a><span> embedded in ext link</span></p>
+<p><a rel="mw:ExtLink" class="external text" href="http://example.com">test </a><a rel="mw:WikiLink" href="./Wikilink" title="Wikilink">wikilink</a><span> embedded in ext link</span></p>
!! end
!! test
@@ -5378,8 +5296,8 @@ parsoid=wt2html
</p><p>{{echo|[[Foo}}
</p>
!! html/parsoid
-<p>[<a rel="mw:ExtLink" href="http://example.com">http://example.com</a> x</p>
-<p typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[http://example.com x"}},"i":0}}]}'>[<a rel="mw:ExtLink" href="http://example.com">http://example.com</a> x</p>
+<p>[<a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a> x</p>
+<p typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[http://example.com x"}},"i":0}}]}'>[<a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a> x</p>
<p>[[Foo</p>
<p>{{echo|[[Foo}}</p>
!! end
@@ -5442,7 +5360,7 @@ http://www.example.com/?title=AT%26T
<p><a rel="nofollow" class="external free" href="http://www.example.com/?title=AT%26T">http://www.example.com/?title=AT%26T</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://www.example.com/?title=AT%26T">http://www.example.com/?title=AT%26T</a></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://www.example.com/?title=AT%26T">http://www.example.com/?title=AT%26T</a></p>
!! end
# According to https://www.w3.org/TR/2011/WD-html5-20110525/Overview.html#parsing-urls a plain
@@ -5455,7 +5373,7 @@ http://www.example.com/?title=100%25_Bran
<p><a rel="nofollow" class="external free" href="http://www.example.com/?title=100%25_Bran">http://www.example.com/?title=100%25_Bran</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://www.example.com/?title=100%25_Bran">http://www.example.com/?title=100%25_Bran</a></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://www.example.com/?title=100%25_Bran">http://www.example.com/?title=100%25_Bran</a></p>
!! end
!! test
@@ -5466,7 +5384,7 @@ http://www.example.com/?title=Ben-Hur_%281959_film%29
<p><a rel="nofollow" class="external free" href="http://www.example.com/?title=Ben-Hur_%281959_film%29">http://www.example.com/?title=Ben-Hur_%281959_film%29</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://www.example.com/?title=Ben-Hur_%281959_film%29">http://www.example.com/?title=Ben-Hur_%281959_film%29</a></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://www.example.com/?title=Ben-Hur_%281959_film%29">http://www.example.com/?title=Ben-Hur_%281959_film%29</a></p>
!! end
@@ -5478,7 +5396,7 @@ T6781: %26 in autonumber URL
<p><a rel="nofollow" class="external autonumber" href="http://www.example.com/?title=AT%26T">[1]</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://www.example.com/?title=AT%26T"></a></p>
+<p><a rel="mw:ExtLink" class="external autonumber" href="http://www.example.com/?title=AT%26T"></a></p>
!! end
!! test
@@ -5489,7 +5407,7 @@ T6781, T7267: %26 in autonumber URL
<p><a rel="nofollow" class="external autonumber" href="http://www.example.com/?title=100%25_Bran">[1]</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://www.example.com/?title=100%25_Bran"></a></p>
+<p><a rel="mw:ExtLink" class="external autonumber" href="http://www.example.com/?title=100%25_Bran"></a></p>
!! end
!! test
@@ -5500,7 +5418,7 @@ T6781, T7267: %28, %29 in autonumber URL
<p><a rel="nofollow" class="external autonumber" href="http://www.example.com/?title=Ben-Hur_%281959_film%29">[1]</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://www.example.com/?title=Ben-Hur_%281959_film%29"></a></p>
+<p><a rel="mw:ExtLink" class="external autonumber" href="http://www.example.com/?title=Ben-Hur_%281959_film%29"></a></p>
!! end
@@ -5512,7 +5430,7 @@ T6781: %26 in bracketed URL
<p><a rel="nofollow" class="external text" href="http://www.example.com/?title=AT%26T">link</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://www.example.com/?title=AT%26T">link</a></p>
+<p><a rel="mw:ExtLink" class="external text" href="http://www.example.com/?title=AT%26T">link</a></p>
!! end
!! test
@@ -5532,7 +5450,7 @@ T6781, T7267: %28, %29 in bracketed URL
<p><a rel="nofollow" class="external text" href="http://www.example.com/?title=Ben-Hur_%281959_film%29">link</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://www.example.com/?title=Ben-Hur_%281959_film%29">link</a></p>
+<p><a rel="mw:ExtLink" class="external text" href="http://www.example.com/?title=Ben-Hur_%281959_film%29">link</a></p>
!! end
!! test
@@ -5546,8 +5464,8 @@ External link containing a period in the anchor. (T65947)
</p><p><a rel="nofollow" class="external text" href="//foo.org/bar.">bang</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="//foo.org/bar#baz.">bang</a></p>
-<p><a rel="mw:ExtLink" href="//foo.org/bar.">bang</a></p>
+<p><a rel="mw:ExtLink" class="external text" href="//foo.org/bar#baz.">bang</a></p>
+<p><a rel="mw:ExtLink" class="external text" href="//foo.org/bar.">bang</a></p>
!! end
!! test
@@ -5561,8 +5479,8 @@ External link containing a single quote. (T65947)
</p><p><a rel="nofollow" class="external text" href="//foo.org/bar&#39;baz">bang</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="//foo.org/bar'baz"></a></p>
-<p><a rel="mw:ExtLink" href="//foo.org/bar'baz">bang</a></p>
+<p><a rel="mw:ExtLink" class="external autonumber" href="//foo.org/bar'baz"></a></p>
+<p><a rel="mw:ExtLink" class="external text" href="//foo.org/bar'baz">bang</a></p>
!! end
!! test
@@ -5583,17 +5501,16 @@ External link containing double-single-quotes in text embedded in italics (T6598
</p>
!! end
+# Don't add the html/php section since the output is broken and there isn't any reason to spec it
!! test
External link containing double-single-quotes with no space separating the url from text in italics
!! wikitext
[http://www.musee-picasso.fr/pages/page_id18528_u1l2.htm''La muerte de Casagemas'' (1901) en el sitio de [[Museo Picasso (París)|Museo Picasso]].]
-!! html/php
-<p><a rel="nofollow" class="external text" href="http://www.musee-picasso.fr/pages/page_id18528_u1l2.htm"><i>La muerte de Casagemas</i> (1901) en el sitio de <a href="/index.php?title=Museo_Picasso_(Par%C3%ADs)&amp;action=edit&amp;redlink=1" class="new" title="Museo Picasso (París) (page does not exist)">Museo Picasso</a>.</a>
-</p>
!! html/php+tidy
-<p><a rel="nofollow" class="external text" href="http://www.musee-picasso.fr/pages/page_id18528_u1l2.htm"><i>La muerte de Casagemas</i> (1901) en el sitio de</a> <a href="/index.php?title=Museo_Picasso_(Par%C3%ADs)&amp;action=edit&amp;redlink=1" class="new" title="Museo Picasso (París) (page does not exist)">Museo Picasso</a>.</p>
+<p><a rel="nofollow" class="external text" href="http://www.musee-picasso.fr/pages/page_id18528_u1l2.htm"><i>La muerte de Casagemas</i> (1901) en el sitio de </a><a href="/index.php?title=Museo_Picasso_(Par%C3%ADs)&amp;action=edit&amp;redlink=1" class="new" title="Museo Picasso (París) (page does not exist)">Museo Picasso</a>.
+</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://www.musee-picasso.fr/pages/page_id18528_u1l2.htm"><i>La muerte de Casagemas</i> (1901) en el sitio de </a><a rel="mw:WikiLink" href="./Museo_Picasso_(París)" title="Museo Picasso (París)">Museo Picasso</a><span>.</span></p>
+<p><a rel="mw:ExtLink" class="external text" href="http://www.musee-picasso.fr/pages/page_id18528_u1l2.htm"><i>La muerte de Casagemas</i> (1901) en el sitio de </a><a rel="mw:WikiLink" href="./Museo_Picasso_(París)" title="Museo Picasso (París)">Museo Picasso</a><span>.</span></p>
!! end
!! test
@@ -5604,7 +5521,7 @@ External link with comments in link text
<p><a rel="nofollow" class="external text" href="http://www.google.com">Google </a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://www.google.com">Google <!-- comment --></a></p>
+<p><a rel="mw:ExtLink" class="external text" href="http://www.google.com">Google <!-- comment --></a></p>
!! end
!! test
@@ -5615,7 +5532,7 @@ External link to bare IPv4 address
<p><a rel="nofollow" class="external text" href="http://192.168.0.1">Link</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://192.168.0.1">Link</a></p>
+<p><a rel="mw:ExtLink" class="external text" href="http://192.168.0.1">Link</a></p>
!! end
!! test
@@ -5647,9 +5564,9 @@ http://example.com/index.php?foozoid&#x5B;&#x5D;=bar
</p><p><a rel="nofollow" class="external free" href="http://example.com/index.php?foozoid%5B%5D=bar">http://example.com/index.php?foozoid%5B%5D=bar</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com/index.php?foozoid%5B%5D=bar">http://example.com/index.php?foozoid%5B%5D=bar</a></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com/index.php?foozoid%5B%5D=bar">http://example.com/index.php?foozoid%5B%5D=bar</a></p>
-<p><a rel="mw:ExtLink" href="http://example.com/index.php?foozoid%5B%5D=bar" data-parsoid='{"stx":"url","a":{"href":"http://example.com/index.php?foozoid%5B%5D=bar"},"sa":{"href":"http://example.com/index.php?foozoid&amp;#x5B;&amp;#x5D;=bar"}}'>http://example.com/index.php?foozoid%5B%5D=bar</a></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com/index.php?foozoid%5B%5D=bar" data-parsoid='{"stx":"url","a":{"href":"http://example.com/index.php?foozoid%5B%5D=bar"},"sa":{"href":"http://example.com/index.php?foozoid&amp;#x5B;&amp;#x5D;=bar"}}'>http://example.com/index.php?foozoid%5B%5D=bar</a></p>
!! end
!! test
@@ -5658,61 +5575,62 @@ IPv6 urls, autolink format (T23261)
http://[2404:130:0:1000::187:2]/index.php
Examples from RFC 2373, section 2.2:
-* http://[1080::8:800:200C:417A]/unicast
-* http://[FF01::101]/multicast
-* http://[::1]/loopback
-* http://[::]/unspecified
-* http://[::13.1.68.3]/ipv4compat
-* http://[::FFFF:129.144.52.38]/ipv4compat
+
+*http://[1080::8:800:200C:417A]/unicast
+*http://[FF01::101]/multicast
+*http://[::1]/loopback
+*http://[::]/unspecified
+*http://[::13.1.68.3]/ipv4compat
+*http://[::FFFF:129.144.52.38]/ipv4compat
Examples from RFC 2732, section 2:
-* http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html
-* http://[1080:0:0:0:8:800:200C:417A]/index.html
-* http://[3ffe:2a00:100:7031::1]
-* http://[1080::8:800:200C:417A]/foo
-* http://[::192.9.5.5]/ipng
-* http://[::FFFF:129.144.52.38]:80/index.html
-* http://[2010:836B:4179::836B:4179]
+*http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html
+*http://[1080:0:0:0:8:800:200C:417A]/index.html
+*http://[3ffe:2a00:100:7031::1]
+*http://[1080::8:800:200C:417A]/foo
+*http://[::192.9.5.5]/ipng
+*http://[::FFFF:129.144.52.38]:80/index.html
+*http://[2010:836B:4179::836B:4179]
!! html/php
<p><a rel="nofollow" class="external free" href="http://[2404:130:0:1000::187:2]/index.php">http://[2404:130:0:1000::187:2]/index.php</a>
-</p><p>Examples from <a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc2373">RFC 2373</a>, section 2.2:
-</p>
-<ul><li> <a rel="nofollow" class="external free" href="http://[1080::8:800:200C:417A]/unicast">http://[1080::8:800:200C:417A]/unicast</a></li>
-<li> <a rel="nofollow" class="external free" href="http://[FF01::101]/multicast">http://[FF01::101]/multicast</a></li>
-<li> <a rel="nofollow" class="external free" href="http://[::1]/loopback">http://[::1]/loopback</a></li>
-<li> <a rel="nofollow" class="external free" href="http://[::]/unspecified">http://[::]/unspecified</a></li>
-<li> <a rel="nofollow" class="external free" href="http://[::13.1.68.3]/ipv4compat">http://[::13.1.68.3]/ipv4compat</a></li>
-<li> <a rel="nofollow" class="external free" href="http://[::FFFF:129.144.52.38]/ipv4compat">http://[::FFFF:129.144.52.38]/ipv4compat</a></li></ul>
-<p>Examples from <a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc2732">RFC 2732</a>, section 2:
-</p>
-<ul><li> <a rel="nofollow" class="external free" href="http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html">http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html</a></li>
-<li> <a rel="nofollow" class="external free" href="http://[1080:0:0:0:8:800:200C:417A]/index.html">http://[1080:0:0:0:8:800:200C:417A]/index.html</a></li>
-<li> <a rel="nofollow" class="external free" href="http://[3ffe:2a00:100:7031::1]">http://[3ffe:2a00:100:7031::1]</a></li>
-<li> <a rel="nofollow" class="external free" href="http://[1080::8:800:200C:417A]/foo">http://[1080::8:800:200C:417A]/foo</a></li>
-<li> <a rel="nofollow" class="external free" href="http://[::192.9.5.5]/ipng">http://[::192.9.5.5]/ipng</a></li>
-<li> <a rel="nofollow" class="external free" href="http://[::FFFF:129.144.52.38]:80/index.html">http://[::FFFF:129.144.52.38]:80/index.html</a></li>
-<li> <a rel="nofollow" class="external free" href="http://[2010:836B:4179::836B:4179]">http://[2010:836B:4179::836B:4179]</a></li></ul>
-
-!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://[2404:130:0:1000::187:2]/index.php">http://[2404:130:0:1000::187:2]/index.php</a></p>
-
-<p>Examples from <a href="//tools.ietf.org/html/rfc2373" rel="mw:ExtLink">RFC 2373</a>, section 2.2:</p>
-<ul><li> <a rel="mw:ExtLink" href="http://[1080::8:800:200C:417A]/unicast">http://[1080::8:800:200C:417A]/unicast</a></li>
-<li> <a rel="mw:ExtLink" href="http://[FF01::101]/multicast">http://[FF01::101]/multicast</a></li>
-<li> <a rel="mw:ExtLink" href="http://[::1]/loopback">http://[::1]/loopback</a></li>
-<li> <a rel="mw:ExtLink" href="http://[::]/unspecified">http://[::]/unspecified</a></li>
-<li> <a rel="mw:ExtLink" href="http://[::13.1.68.3]/ipv4compat">http://[::13.1.68.3]/ipv4compat</a></li>
-<li> <a rel="mw:ExtLink" href="http://[::FFFF:129.144.52.38]/ipv4compat">http://[::FFFF:129.144.52.38]/ipv4compat</a></li></ul>
-
-<p>Examples from <a href="//tools.ietf.org/html/rfc2732" rel="mw:ExtLink">RFC 2732</a>, section 2:</p>
-<ul><li> <a rel="mw:ExtLink" href="http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html">http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html</a></li>
-<li> <a rel="mw:ExtLink" href="http://[1080:0:0:0:8:800:200C:417A]/index.html">http://[1080:0:0:0:8:800:200C:417A]/index.html</a></li>
-<li> <a rel="mw:ExtLink" href="http://[3ffe:2a00:100:7031::1]">http://[3ffe:2a00:100:7031::1]</a></li>
-<li> <a rel="mw:ExtLink" href="http://[1080::8:800:200C:417A]/foo">http://[1080::8:800:200C:417A]/foo</a></li>
-<li> <a rel="mw:ExtLink" href="http://[::192.9.5.5]/ipng">http://[::192.9.5.5]/ipng</a></li>
-<li> <a rel="mw:ExtLink" href="http://[::FFFF:129.144.52.38]:80/index.html">http://[::FFFF:129.144.52.38]:80/index.html</a></li>
-<li> <a rel="mw:ExtLink" href="http://[2010:836B:4179::836B:4179]">http://[2010:836B:4179::836B:4179]</a></li></ul>
+</p><p>Examples from <a class="external mw-magiclink-rfc" rel="nofollow" href="https://tools.ietf.org/html/rfc2373">RFC 2373</a>, section 2.2:
+</p>
+<ul><li><a rel="nofollow" class="external free" href="http://[1080::8:800:200C:417A]/unicast">http://[1080::8:800:200C:417A]/unicast</a></li>
+<li><a rel="nofollow" class="external free" href="http://[FF01::101]/multicast">http://[FF01::101]/multicast</a></li>
+<li><a rel="nofollow" class="external free" href="http://[::1]/loopback">http://[::1]/loopback</a></li>
+<li><a rel="nofollow" class="external free" href="http://[::]/unspecified">http://[::]/unspecified</a></li>
+<li><a rel="nofollow" class="external free" href="http://[::13.1.68.3]/ipv4compat">http://[::13.1.68.3]/ipv4compat</a></li>
+<li><a rel="nofollow" class="external free" href="http://[::FFFF:129.144.52.38]/ipv4compat">http://[::FFFF:129.144.52.38]/ipv4compat</a></li></ul>
+<p>Examples from <a class="external mw-magiclink-rfc" rel="nofollow" href="https://tools.ietf.org/html/rfc2732">RFC 2732</a>, section 2:
+</p>
+<ul><li><a rel="nofollow" class="external free" href="http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html">http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html</a></li>
+<li><a rel="nofollow" class="external free" href="http://[1080:0:0:0:8:800:200C:417A]/index.html">http://[1080:0:0:0:8:800:200C:417A]/index.html</a></li>
+<li><a rel="nofollow" class="external free" href="http://[3ffe:2a00:100:7031::1]">http://[3ffe:2a00:100:7031::1]</a></li>
+<li><a rel="nofollow" class="external free" href="http://[1080::8:800:200C:417A]/foo">http://[1080::8:800:200C:417A]/foo</a></li>
+<li><a rel="nofollow" class="external free" href="http://[::192.9.5.5]/ipng">http://[::192.9.5.5]/ipng</a></li>
+<li><a rel="nofollow" class="external free" href="http://[::FFFF:129.144.52.38]:80/index.html">http://[::FFFF:129.144.52.38]:80/index.html</a></li>
+<li><a rel="nofollow" class="external free" href="http://[2010:836B:4179::836B:4179]">http://[2010:836B:4179::836B:4179]</a></li></ul>
+
+!! html/parsoid
+<p><a rel="mw:ExtLink" class="external free" href="http://[2404:130:0:1000::187:2]/index.php">http://[2404:130:0:1000::187:2]/index.php</a></p>
+
+<p>Examples from <a href="https://tools.ietf.org/html/rfc2373" rel="mw:ExtLink" class="external text">RFC 2373</a>, section 2.2:</p>
+<ul><li><a rel="mw:ExtLink" class="external free" href="http://[1080::8:800:200C:417A]/unicast">http://[1080::8:800:200C:417A]/unicast</a></li>
+<li><a rel="mw:ExtLink" class="external free" href="http://[FF01::101]/multicast">http://[FF01::101]/multicast</a></li>
+<li><a rel="mw:ExtLink" class="external free" href="http://[::1]/loopback">http://[::1]/loopback</a></li>
+<li><a rel="mw:ExtLink" class="external free" href="http://[::]/unspecified">http://[::]/unspecified</a></li>
+<li><a rel="mw:ExtLink" class="external free" href="http://[::13.1.68.3]/ipv4compat">http://[::13.1.68.3]/ipv4compat</a></li>
+<li><a rel="mw:ExtLink" class="external free" href="http://[::FFFF:129.144.52.38]/ipv4compat">http://[::FFFF:129.144.52.38]/ipv4compat</a></li></ul>
+
+<p>Examples from <a href="https://tools.ietf.org/html/rfc2732" rel="mw:ExtLink" class="external text">RFC 2732</a>, section 2:</p>
+<ul><li><a rel="mw:ExtLink" class="external free" href="http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html">http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html</a></li>
+<li><a rel="mw:ExtLink" class="external free" href="http://[1080:0:0:0:8:800:200C:417A]/index.html">http://[1080:0:0:0:8:800:200C:417A]/index.html</a></li>
+<li><a rel="mw:ExtLink" class="external free" href="http://[3ffe:2a00:100:7031::1]">http://[3ffe:2a00:100:7031::1]</a></li>
+<li><a rel="mw:ExtLink" class="external free" href="http://[1080::8:800:200C:417A]/foo">http://[1080::8:800:200C:417A]/foo</a></li>
+<li><a rel="mw:ExtLink" class="external free" href="http://[::192.9.5.5]/ipng">http://[::192.9.5.5]/ipng</a></li>
+<li><a rel="mw:ExtLink" class="external free" href="http://[::FFFF:129.144.52.38]:80/index.html">http://[::FFFF:129.144.52.38]:80/index.html</a></li>
+<li><a rel="mw:ExtLink" class="external free" href="http://[2010:836B:4179::836B:4179]">http://[2010:836B:4179::836B:4179]</a></li></ul>
!! end
!! test
@@ -5721,61 +5639,62 @@ IPv6 urls, bracketed format (T23261)
[http://[2404:130:0:1000::187:2]/index.php test]
Examples from RFC 2373, section 2.2:
-* [http://[1080::8:800:200C:417A] unicast]
-* [http://[FF01::101] multicast]
-* [http://[::1]/ loopback]
-* [http://[::] unspecified]
-* [http://[::13.1.68.3] ipv4compat]
-* [http://[::FFFF:129.144.52.38] ipv4compat]
+
+*[http://[1080::8:800:200C:417A] unicast]
+*[http://[FF01::101] multicast]
+*[http://[::1]/ loopback]
+*[http://[::] unspecified]
+*[http://[::13.1.68.3] ipv4compat]
+*[http://[::FFFF:129.144.52.38] ipv4compat]
Examples from RFC 2732, section 2:
-* [http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html 1]
-* [http://[1080:0:0:0:8:800:200C:417A]/index.html 2]
-* [http://[3ffe:2a00:100:7031::1] 3]
-* [http://[1080::8:800:200C:417A]/foo 4]
-* [http://[::192.9.5.5]/ipng 5]
-* [http://[::FFFF:129.144.52.38]:80/index.html 6]
-* [http://[2010:836B:4179::836B:4179] 7]
+*[http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html 1]
+*[http://[1080:0:0:0:8:800:200C:417A]/index.html 2]
+*[http://[3ffe:2a00:100:7031::1] 3]
+*[http://[1080::8:800:200C:417A]/foo 4]
+*[http://[::192.9.5.5]/ipng 5]
+*[http://[::FFFF:129.144.52.38]:80/index.html 6]
+*[http://[2010:836B:4179::836B:4179] 7]
!! html/php
<p><a rel="nofollow" class="external text" href="http://[2404:130:0:1000::187:2]/index.php">test</a>
-</p><p>Examples from <a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc2373">RFC 2373</a>, section 2.2:
-</p>
-<ul><li> <a rel="nofollow" class="external text" href="http://[1080::8:800:200C:417A]">unicast</a></li>
-<li> <a rel="nofollow" class="external text" href="http://[FF01::101]">multicast</a></li>
-<li> <a rel="nofollow" class="external text" href="http://[::1]/">loopback</a></li>
-<li> <a rel="nofollow" class="external text" href="http://[::]">unspecified</a></li>
-<li> <a rel="nofollow" class="external text" href="http://[::13.1.68.3]">ipv4compat</a></li>
-<li> <a rel="nofollow" class="external text" href="http://[::FFFF:129.144.52.38]">ipv4compat</a></li></ul>
-<p>Examples from <a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc2732">RFC 2732</a>, section 2:
-</p>
-<ul><li> <a rel="nofollow" class="external text" href="http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html">1</a></li>
-<li> <a rel="nofollow" class="external text" href="http://[1080:0:0:0:8:800:200C:417A]/index.html">2</a></li>
-<li> <a rel="nofollow" class="external text" href="http://[3ffe:2a00:100:7031::1]">3</a></li>
-<li> <a rel="nofollow" class="external text" href="http://[1080::8:800:200C:417A]/foo">4</a></li>
-<li> <a rel="nofollow" class="external text" href="http://[::192.9.5.5]/ipng">5</a></li>
-<li> <a rel="nofollow" class="external text" href="http://[::FFFF:129.144.52.38]:80/index.html">6</a></li>
-<li> <a rel="nofollow" class="external text" href="http://[2010:836B:4179::836B:4179]">7</a></li></ul>
-
-!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://[2404:130:0:1000::187:2]/index.php">test</a></p>
-
-<p>Examples from <a href="//tools.ietf.org/html/rfc2373" rel="mw:ExtLink">RFC 2373</a>, section 2.2:</p>
-<ul><li> <a rel="mw:ExtLink" href="http://[1080::8:800:200C:417A]">unicast</a></li>
-<li> <a rel="mw:ExtLink" href="http://[FF01::101]">multicast</a></li>
-<li> <a rel="mw:ExtLink" href="http://[::1]/">loopback</a></li>
-<li> <a rel="mw:ExtLink" href="http://[::]">unspecified</a></li>
-<li> <a rel="mw:ExtLink" href="http://[::13.1.68.3]">ipv4compat</a></li>
-<li> <a rel="mw:ExtLink" href="http://[::FFFF:129.144.52.38]">ipv4compat</a></li></ul>
-
-<p>Examples from <a href="//tools.ietf.org/html/rfc2732" rel="mw:ExtLink">RFC 2732</a>, section 2:</p>
-<ul><li> <a rel="mw:ExtLink" href="http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html">1</a></li>
-<li> <a rel="mw:ExtLink" href="http://[1080:0:0:0:8:800:200C:417A]/index.html">2</a></li>
-<li> <a rel="mw:ExtLink" href="http://[3ffe:2a00:100:7031::1]">3</a></li>
-<li> <a rel="mw:ExtLink" href="http://[1080::8:800:200C:417A]/foo">4</a></li>
-<li> <a rel="mw:ExtLink" href="http://[::192.9.5.5]/ipng">5</a></li>
-<li> <a rel="mw:ExtLink" href="http://[::FFFF:129.144.52.38]:80/index.html">6</a></li>
-<li> <a rel="mw:ExtLink" href="http://[2010:836B:4179::836B:4179]">7</a></li></ul>
+</p><p>Examples from <a class="external mw-magiclink-rfc" rel="nofollow" href="https://tools.ietf.org/html/rfc2373">RFC 2373</a>, section 2.2:
+</p>
+<ul><li><a rel="nofollow" class="external text" href="http://[1080::8:800:200C:417A]">unicast</a></li>
+<li><a rel="nofollow" class="external text" href="http://[FF01::101]">multicast</a></li>
+<li><a rel="nofollow" class="external text" href="http://[::1]/">loopback</a></li>
+<li><a rel="nofollow" class="external text" href="http://[::]">unspecified</a></li>
+<li><a rel="nofollow" class="external text" href="http://[::13.1.68.3]">ipv4compat</a></li>
+<li><a rel="nofollow" class="external text" href="http://[::FFFF:129.144.52.38]">ipv4compat</a></li></ul>
+<p>Examples from <a class="external mw-magiclink-rfc" rel="nofollow" href="https://tools.ietf.org/html/rfc2732">RFC 2732</a>, section 2:
+</p>
+<ul><li><a rel="nofollow" class="external text" href="http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html">1</a></li>
+<li><a rel="nofollow" class="external text" href="http://[1080:0:0:0:8:800:200C:417A]/index.html">2</a></li>
+<li><a rel="nofollow" class="external text" href="http://[3ffe:2a00:100:7031::1]">3</a></li>
+<li><a rel="nofollow" class="external text" href="http://[1080::8:800:200C:417A]/foo">4</a></li>
+<li><a rel="nofollow" class="external text" href="http://[::192.9.5.5]/ipng">5</a></li>
+<li><a rel="nofollow" class="external text" href="http://[::FFFF:129.144.52.38]:80/index.html">6</a></li>
+<li><a rel="nofollow" class="external text" href="http://[2010:836B:4179::836B:4179]">7</a></li></ul>
+
+!! html/parsoid
+<p><a rel="mw:ExtLink" class="external text" href="http://[2404:130:0:1000::187:2]/index.php">test</a></p>
+
+<p>Examples from <a href="https://tools.ietf.org/html/rfc2373" rel="mw:ExtLink" class="external text">RFC 2373</a>, section 2.2:</p>
+<ul><li><a rel="mw:ExtLink" class="external text" href="http://[1080::8:800:200C:417A]">unicast</a></li>
+<li><a rel="mw:ExtLink" class="external text" href="http://[FF01::101]">multicast</a></li>
+<li><a rel="mw:ExtLink" class="external text" href="http://[::1]/">loopback</a></li>
+<li><a rel="mw:ExtLink" class="external text" href="http://[::]">unspecified</a></li>
+<li><a rel="mw:ExtLink" class="external text" href="http://[::13.1.68.3]">ipv4compat</a></li>
+<li><a rel="mw:ExtLink" class="external text" href="http://[::FFFF:129.144.52.38]">ipv4compat</a></li></ul>
+
+<p>Examples from <a href="https://tools.ietf.org/html/rfc2732" rel="mw:ExtLink" class="external text">RFC 2732</a>, section 2:</p>
+<ul><li><a rel="mw:ExtLink" class="external text" href="http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html">1</a></li>
+<li><a rel="mw:ExtLink" class="external text" href="http://[1080:0:0:0:8:800:200C:417A]/index.html">2</a></li>
+<li><a rel="mw:ExtLink" class="external text" href="http://[3ffe:2a00:100:7031::1]">3</a></li>
+<li><a rel="mw:ExtLink" class="external text" href="http://[1080::8:800:200C:417A]/foo">4</a></li>
+<li><a rel="mw:ExtLink" class="external text" href="http://[::192.9.5.5]/ipng">5</a></li>
+<li><a rel="mw:ExtLink" class="external text" href="http://[::FFFF:129.144.52.38]:80/index.html">6</a></li>
+<li><a rel="mw:ExtLink" class="external text" href="http://[2010:836B:4179::836B:4179]">7</a></li></ul>
!! end
!! test
@@ -5815,13 +5734,13 @@ Non-extlinks in brackets
[foo <i>bar</i>]
[fool's] errand
[fool's errand]
-[<span typeof="mw:Placeholder" data-parsoid='{"src":"{{echo|foo}}"}'>foo</span>]
-[<span typeof="mw:Placeholder" data-parsoid='{"src":"{{echo|foo}}"}'>foo</span> bar]
-[<span typeof="mw:Placeholder" data-parsoid='{"src":"{{echo|foo}}"}'>foo</span> <i>bar</i>]
-[<span typeof="mw:Placeholder" data-parsoid='{"src":"{{echo|foo}}l&#39;s"}'>fool's</span>] errand
-[<span typeof="mw:Placeholder" data-parsoid='{"src":"{{echo|foo}}l&#39;s"}'>fool's</span> errand]
-[<span typeof="mw:Placeholder" data-parsoid='{"src":"url={{echo|foo}}"}'>url=foo</span>]
-[url=<a rel="mw:ExtLink" href="http://example.com">http://example.com</a>]
+[<span about="#mwt19" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo"}},"i":0}}]}'>foo</span>]
+[<span about="#mwt20" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo"}},"i":0}}]}'>foo</span> bar]
+[<span about="#mwt21" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo"}},"i":0}}]}'>foo</span> <i>bar</i>]
+[<span about="#mwt22" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo"}},"i":0}}]}'>foo</span>l's] errand
+[<span about="#mwt23" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo"}},"i":0}}]}'>foo</span>l's errand]
+[url=<span about="#mwt24" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo"}},"i":0}}]}'>foo</span>]
+[url=<a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a>]
[http:// bare protocols don't count]</p>
!! end
@@ -5833,8 +5752,7 @@ Percent encoding in external links
<p><a rel="nofollow" class="external text" href="https://github.com/search?l=&amp;q=ResourceLoader+%40wikimedia">Search</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink"
-href="https://github.com/search?l=&amp;q=ResourceLoader+%40wikimedia">Search</a></p>
+<p><a rel="mw:ExtLink" class="external text" href="https://github.com/search?l=&amp;q=ResourceLoader+%40wikimedia">Search</a></p>
!! end
!! test
@@ -5845,7 +5763,7 @@ http://example.com
<p><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com">http://example.com</a></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a></p>
!! end
!! test
@@ -5877,14 +5795,14 @@ http://example.com/a)b
</p><p><a rel="nofollow" class="external text" href="http://example.com)">foo</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com">http://example.com</a>)</p>
-<p><a rel="mw:ExtLink" href="http://example.com/test">http://example.com/test</a>)</p>
-<p><a rel="mw:ExtLink" href="http://example.com/(test)">http://example.com/(test)</a></p>
-<p><a rel="mw:ExtLink" href="http://example.com/((test)">http://example.com/((test)</a></p>
-<p>(<a rel="mw:ExtLink" href="http://example.com/(test))">http://example.com/(test))</a></p>
-<p>(<a rel="mw:ExtLink" href="http://example.com/(test)))))">http://example.com/(test)))))</a></p>
-<p><a rel="mw:ExtLink" href="http://example.com/a)b">http://example.com/a)b</a></p>
-<p><a rel="mw:ExtLink" href="http://example.com)">foo</a></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a>)</p>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com/test">http://example.com/test</a>)</p>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com/(test)">http://example.com/(test)</a></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com/((test)">http://example.com/((test)</a></p>
+<p>(<a rel="mw:ExtLink" class="external free" href="http://example.com/(test))">http://example.com/(test))</a></p>
+<p>(<a rel="mw:ExtLink" class="external free" href="http://example.com/(test)))))">http://example.com/(test)))))</a></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com/a)b">http://example.com/a)b</a></p>
+<p><a rel="mw:ExtLink" class="external text" href="http://example.com)">foo</a></p>
!! end
!! test
@@ -5898,9 +5816,9 @@ Parenthesis in external links, w/ transclusion or comment
</p><p>(<a rel="nofollow" class="external free" href="http://example.com">http://example.com</a>)
</p>
!! html/parsoid
-<p>(<a typeof="mw:ExpandedAttrs" about="#mwt2" rel="mw:ExtLink" href="http://example.com/hi" data-parsoid='{"stx":"url","a":{"href":"http://example.com/hi"},"sa":{"href":"http://example.com/{{echo|hi}}"}}' data-mw='{"attribs":[[{"txt":"href"},{"html":"http://example.com/&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=&#39;{\"pi\":[[{\"k\":\"1\"}]],\"dsr\":[20,31,null,null]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"hi\"}},\"i\":0}}]}&#39;>hi&lt;/span>"}]]}'>http://example.com/hi</a>)</p>
+<p>(<a typeof="mw:ExpandedAttrs" about="#mwt2" rel="mw:ExtLink" class="external free" href="http://example.com/hi" data-parsoid='{"stx":"url","a":{"href":"http://example.com/hi"},"sa":{"href":"http://example.com/{{echo|hi}}"}}' data-mw='{"attribs":[[{"txt":"href"},{"html":"http://example.com/&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=&#39;{\"pi\":[[{\"k\":\"1\"}]],\"dsr\":[20,31,null,null]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"hi\"}},\"i\":0}}]}&#39;>hi&lt;/span>"}]]}'>http://example.com/hi</a>)</p>
-<p>(<a rel="mw:ExtLink" href="http://example.com" data-parsoid='{"stx":"url","a":{"href":"http://example.com"},"sa":{"href":"http://example.com&lt;!-- hi -->"}}'>http://example.com</a>)</p>
+<p>(<a rel="mw:ExtLink" class="external free" href="http://example.com" data-parsoid='{"stx":"url","a":{"href":"http://example.com"},"sa":{"href":"http://example.com&lt;!-- hi -->"}}'>http://example.com</a>)</p>
!! end
!! test
@@ -5935,11 +5853,11 @@ parsoid=html2wt
!! wikitext
[[Foo|Bar]]
[[Foo|Bar]]
-[[wikipedia:Foo|Bar]]
-[[wikipedia:Foo|Bar]]
+[[:en:Foo|Bar]]
+[[:en:Foo|Bar]]
-[[wikipedia:European_Robin|European Robin]]
-[[wikipedia:European_Robin|European Robin]]
+[[:en:European_Robin|European Robin]]
+[[:en:European_Robin|European Robin]]
!! end
!! test
@@ -6104,15 +6022,15 @@ A table with no data (take 2)
A table with nothing but a caption
!! wikitext
{|
-|+ caption
+|+caption
|}
!! html/php
<table>
-<caption> caption
+<caption>caption
</caption><tr><td></td></tr></table>
!! html/parsoid
-<table><caption> caption</caption></table>
+<table><caption>caption</caption></table>
!! end
!! test
@@ -6121,14 +6039,14 @@ A table with caption with default-spaced attributes and a table row
{|
|+ style="color: red;" | caption1
|-
-| foo
+|foo
|}
!! html
<table>
-<caption style="color: red;"> caption1
+<caption style="color: red;">caption1
</caption>
<tr>
-<td> foo
+<td>foo
</td></tr></table>
!! end
@@ -6138,18 +6056,18 @@ A table with captions with non-default spaced attributes and a table row
!! wikitext
{|
|+style="color: red;"|caption2
-|+ style="color: red;"| caption3
+|+ style="color: red;"|caption3
|-
-| foo
+|foo
|}
!! html
<table>
<caption style="color: red;">caption2
</caption>
-<caption style="color: red;"> caption3
+<caption style="color: red;">caption3
</caption>
<tr>
-<td> foo
+<td>foo
</td></tr></table>
!! end
@@ -6158,23 +6076,23 @@ A table with captions with non-default spaced attributes and a table row
Table td-cell syntax variations
!! wikitext
{|
-| foo bar foo | baz
-| foo bar foo || baz
-| style='color:red;' | baz
-| style='color:red;' || baz
+|foo bar foo|baz
+|foo bar foo||baz
+|style='color:red;'|baz
+|style='color:red;'||baz
|}
!! html
<table>
<tr>
-<td> baz
+<td>baz
</td>
-<td> foo bar foo </td>
-<td> baz
+<td>foo bar foo</td>
+<td>baz
</td>
-<td style="color:red;"> baz
+<td style="color:red;">baz
</td>
-<td> style='color:red;' </td>
-<td> baz
+<td>style='color:red;'</td>
+<td>baz
</td></tr></table>
!! end
@@ -6183,19 +6101,19 @@ Table td-cell syntax variations
Simple table
!! wikitext
{|
-| 1 || 2
+|1||2
|-
-| 3 || 4
+|3||4
|}
!! html
<table>
<tr>
-<td> 1 </td>
-<td> 2
+<td>1</td>
+<td>2
</td></tr>
<tr>
-<td> 3 </td>
-<td> 4
+<td>3</td>
+<td>4
</td></tr></table>
!! end
@@ -6204,17 +6122,17 @@ Simple table
Simple table but with multiple dashes for row wikitext
!! wikitext
{|
-| foo
+|foo
|-----
-| bar
+|bar
|}
!! html
<table>
<tr>
-<td> foo
+<td>foo
</td></tr>
<tr>
-<td> bar
+<td>bar
</td></tr></table>
!! end
@@ -6225,67 +6143,67 @@ Multiplication table
{| border="1" cellpadding="2"
|+Multiplication table
|-
-! &times; !! 1 !! 2 !! 3
+!&times;!!1!!2!!3
|-
-! 1
-| 1 || 2 || 3
+!1
+|1||2||3
|-
-! 2
-| 2 || 4 || 6
+!2
+|2||4||6
|-
-! 3
-| 3 || 6 || 9
+!3
+|3||6||9
|-
-! 4
-| 4 || 8 || 12
+!4
+|4||8||12
|-
-! 5
-| 5 || 10 || 15
+!5
+|5||10||15
|}
!! html
<table border="1" cellpadding="2">
<caption>Multiplication table
</caption>
<tr>
-<th> &#215; </th>
-<th> 1 </th>
-<th> 2 </th>
-<th> 3
+<th>&#215;</th>
+<th>1</th>
+<th>2</th>
+<th>3
</th></tr>
<tr>
-<th> 1
+<th>1
</th>
-<td> 1 </td>
-<td> 2 </td>
-<td> 3
+<td>1</td>
+<td>2</td>
+<td>3
</td></tr>
<tr>
-<th> 2
+<th>2
</th>
-<td> 2 </td>
-<td> 4 </td>
-<td> 6
+<td>2</td>
+<td>4</td>
+<td>6
</td></tr>
<tr>
-<th> 3
+<th>3
</th>
-<td> 3 </td>
-<td> 6 </td>
-<td> 9
+<td>3</td>
+<td>6</td>
+<td>9
</td></tr>
<tr>
-<th> 4
+<th>4
</th>
-<td> 4 </td>
-<td> 8 </td>
-<td> 12
+<td>4</td>
+<td>8</td>
+<td>12
</td></tr>
<tr>
-<th> 5
+<th>5
</th>
-<td> 5 </td>
-<td> 10 </td>
-<td> 15
+<td>5</td>
+<td>10</td>
+<td>15
</td></tr></table>
!! end
@@ -6294,13 +6212,13 @@ Multiplication table
Accept "||" in table headings
!! wikitext
{|
-!h1 || h2
+!h1||h2
|}
!! html
<table>
<tr>
-<th>h1 </th>
-<th> h2
+<th>h1</th>
+<th>h2
</th></tr></table>
!! end
@@ -6309,18 +6227,18 @@ Accept "||" in table headings
Accept "!!" in table data
!! wikitext
{|
-| Foo!! ||
+|Foo!!||
|}
!! html
<table>
<tr>
-<td> Foo!! </td>
+<td>Foo!!</td>
<td>
</td></tr></table>
!! html/parsoid
<table>
-<tbody><tr data-parsoid='{"autoInsertedEnd":true,"autoInsertedStart":true}'><td data-parsoid='{"autoInsertedEnd":true}'> Foo!! </td><td data-parsoid='{"stx_v":"row","autoInsertedEnd":true}'></td></tr>
+<tbody><tr data-parsoid='{"autoInsertedEnd":true,"autoInsertedStart":true}'><td data-parsoid='{"autoInsertedEnd":true}'> Foo!! </td><td data-parsoid='{"stx":"row","autoInsertedEnd":true}'></td></tr>
</tbody></table>
!! end
@@ -6328,13 +6246,13 @@ Accept "!!" in table data
Accept "||" in indented table headings
!! wikitext
:{|
-!h1 || h2
+!h1||h2
|}
!! html
<dl><dd><table>
<tr>
-<th>h1 </th>
-<th> h2
+<th>h1</th>
+<th>h2
</th></tr></table></dd></dl>
!! end
@@ -6386,15 +6304,12 @@ Accept "!!" in table data of mixed wikitext / html syntax
!a
<tr><td>b!!c</td></tr>
|}
-!! html+tidy
+!! html/php+tidy
<table>
-<tr>
-<th>a</th>
-</tr>
-<tr>
-<td>b!!c</td>
-</tr>
-</table>
+<tbody><tr>
+<th>a
+</th></tr><tr><td>b!!c</td></tr>
+</tbody></table>
!! html/parsoid
<table>
<tbody><tr><th>a</th></tr>
@@ -6412,9 +6327,9 @@ Accept empty attributes in td/th cells (td/th cells starting with leading ||)
!! html
<table>
<tr>
-<th> h1
+<th>h1
</th>
-<td> a
+<td>a
</td></tr></table>
!! end
@@ -6424,13 +6339,13 @@ Accept "| !" at start of line in tables (ignore !-attribute)
!! wikitext
{|
|-
-| !style="color:red" | bar
+|!style="color:red"|bar
|}
!! html
<table>
<tr>
-<td> bar
+<td>bar
</td></tr></table>
!!end
@@ -6443,8 +6358,8 @@ Allow +/- in 2nd and later cells in a row, in 1st cell when td-attrs are present
|style='color:red;'|+1
|style='color:blue;'|-1
|-
-| 1 || 2 || 3
-| 1 ||+2 ||-3
+|1||2||3
+|1||+2||-3
|-
| +1
| -1
@@ -6458,18 +6373,18 @@ Allow +/- in 2nd and later cells in a row, in 1st cell when td-attrs are present
<td style="color:blue;">-1
</td></tr>
<tr>
-<td> 1 </td>
-<td> 2 </td>
-<td> 3
+<td>1</td>
+<td>2</td>
+<td>3
</td>
-<td> 1 </td>
-<td>+2 </td>
+<td>1</td>
+<td>+2</td>
<td>-3
</td></tr>
<tr>
-<td> +1
+<td>+1
</td>
-<td> -1
+<td>-1
</td></tr></table>
!!end
@@ -6478,26 +6393,26 @@ Allow +/- in 2nd and later cells in a row, in 1st cell when td-attrs are present
Table rowspan
!! wikitext
{| border=1
-| Cell 1, row 1
-|rowspan=2| Cell 2, row 1 (and 2)
-| Cell 3, row 1
+|Cell 1, row 1
+|rowspan=2|Cell 2, row 1 (and 2)
+|Cell 3, row 1
|-
-| Cell 1, row 2
-| Cell 3, row 2
+|Cell 1, row 2
+|Cell 3, row 2
|}
!! html
<table border="1">
<tr>
-<td> Cell 1, row 1
+<td>Cell 1, row 1
</td>
-<td rowspan="2"> Cell 2, row 1 (and 2)
+<td rowspan="2">Cell 2, row 1 (and 2)
</td>
-<td> Cell 3, row 1
+<td>Cell 3, row 1
</td></tr>
<tr>
-<td> Cell 1, row 2
+<td>Cell 1, row 2
</td>
-<td> Cell 3, row 2
+<td>Cell 3, row 2
</td></tr></table>
!! end
@@ -6518,7 +6433,7 @@ Nested table
!! html
<table border="1">
<tr>
-<td> &#945;
+<td>&#945;
</td>
<td>
<table bgcolor="#ABCDEF" border="2">
@@ -6563,7 +6478,7 @@ Table cell attributes: Pipes protected by nowikis should be treated as a plain c
</td>
<td title="foo&#124;">bar
</td>
-<td> title="foo|" bar
+<td>title="foo|" bar
</td></tr></table>
!! html/parsoid
@@ -6596,24 +6511,24 @@ parsoid=wt2html,html2html
!! html/parsoid
<table><tbody>
<tr>
-<td data-parsoid='{"startTagSrc":"| ","attrSepSrc":"|","autoInsertedEnd":true}'>[<a rel="mw:ExtLink" href="ftp://%7Cx" data-parsoid='{"stx":"url","a":{"href":"ftp://%7Cx"},"sa":{"href":"ftp://|x"}}'>ftp://%7Cx</a></td><td data-parsoid='{"stx_v":"row","autoInsertedEnd":true}'>]" onmouseover="alert(document.cookie)">test</td></tr></tbody></table>
+<td data-parsoid='{"startTagSrc":"| ","attrSepSrc":"|","autoInsertedEnd":true}'>[<a rel="mw:ExtLink" class="external free" href="ftp://%7Cx" data-parsoid='{"stx":"url","a":{"href":"ftp://%7Cx"},"sa":{"href":"ftp://|x"}}'>ftp://%7Cx</a></td><td data-parsoid='{"stx":"row","autoInsertedEnd":true}'>]" onmouseover="alert(document.cookie)">test</td></tr></tbody></table>
!! end
!! test
Element attributes with double ! should not be broken up by <th>
!! wikitext
{|
-! hi <div class="!!">ha</div> ho
+!hi <div class="!!">ha</div> ho
|}
!! html/php
<table>
<tr>
-<th> hi <div class="!!">ha</div> ho
+<th>hi <div class="!!">ha</div> ho
</th></tr></table>
!! html/parsoid
<table>
-<tbody><tr><th> hi <div class="!!" data-parsoid='{"stx":"html"}'>ha</div> ho</th></tr>
+<tbody><tr><th>hi <div class="!!" data-parsoid='{"stx":"html"}'>ha</div> ho</th></tr>
</tbody></table>
!! end
@@ -6621,17 +6536,17 @@ Element attributes with double ! should not be broken up by <th>
! and || in element attributes should not be parsed as <th>/<td>
!! wikitext
{|
-| <div style="color: red !important;" data-contrived="put this here ||">hi</div>
+|<div style="color: red !important;" data-contrived="put this here ||">hi</div>
|}
!! html/php
<table>
<tr>
-<td> <div style="color: red !important;" data-contrived="put this here &#124;&#124;">hi</div>
+<td><div style="color: red !important;" data-contrived="put this here &#124;&#124;">hi</div>
</td></tr></table>
!! html/parsoid
<table>
-<tbody><tr><td> <div style="color: red !important;" data-contrived="put this here ||" data-parsoid='{"stx":"html"}'>hi</div></td></tr>
+<tbody><tr><td><div style="color: red !important;" data-contrived="put this here ||" data-parsoid='{"stx":"html"}'>hi</div></td></tr>
</tbody></table>
!! end
@@ -6642,18 +6557,18 @@ Element attributes with double ! should not be broken up by <th>
parsoid=wt2html
!! wikitext
{|
-| style="color: red !important;" data-contrived="put this here ||" | foo
+|style="color: red !important;" data-contrived="put this here ||"|foo
|}
!! html/php
<table>
<tr>
-<td> style="color: red !important;" data-contrived="put this here </td>
-<td> foo
+<td>style="color: red !important;" data-contrived="put this here</td>
+<td>foo
</td></tr></table>
!! html/parsoid
<table>
-<tbody><tr><td> style="color: red !important;" data-contrived="put this here </td><td data-parsoid='{"stx_v":"row","a":{"\"":null},"sa":{"\"":""},"autoInsertedEnd":true}'> foo</td></tr>
+<tbody><tr><td>style="color: red !important;" data-contrived="put this here</td><td data-parsoid='{"stx":"row","a":{"\"":null},"sa":{"\"":""},"autoInsertedEnd":true}'>foo</td></tr>
</tbody></table>
!! end
@@ -6685,9 +6600,9 @@ Don't break on | in extension attribute in template
<references />
!! html/parsoid
-<p><span about="#mwt2" class="mw-ref" id="cite_ref-hi.7Cho_1-0" rel="dc:references" typeof="mw:Transclusion mw:Extension/ref" data-parsoid='{"pi":[[{"k":"1"}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;ref name=\"hi|ho\">ha&lt;/ref>"}},"i":0}}]}'><a href="./Main_Page#cite_note-hi.7Cho-1" style="counter-reset: mw-Ref 1;"><span class="mw-reflink-text">[1]</span></a></span></p>
+<p><sup about="#mwt2" class="mw-ref" id="cite_ref-hi|ho_1-0" rel="dc:references" typeof="mw:Transclusion mw:Extension/ref" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;ref name=\"hi|ho\">ha&lt;/ref>"}},"i":0}}]}'><a href="./Main_Page#cite_note-hi|ho-1" style="counter-reset: mw-Ref 1;"><span class="mw-reflink-text">[1]</span></a></sup></p>
-<ol class="mw-references references" typeof="mw:Extension/references" about="#mwt5" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-hi.7Cho-1" id="cite_note-hi.7Cho-1"><a href="./Main_Page#cite_ref-hi.7Cho_1-0" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-hi.7Cho-1" class="mw-reference-text">ha</span></li></ol>
+<ol class="mw-references references" typeof="mw:Extension/references" about="#mwt5" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-hi|ho-1" id="cite_note-hi|ho-1"><a href="./Main_Page#cite_ref-hi|ho_1-0" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-hi|ho-1" class="mw-reference-text">ha</span></li></ol>
!! end
## We don't support roundtripping of these attributes in Parsoid.
@@ -6699,22 +6614,22 @@ Invalid text in table attributes should be discarded
parsoid=wt2html
!! wikitext
{| <span>boo</span> style='border:1px solid black'
-| <span>boo</span> style='color:blue' | 1
-|<span>boo</span> style='color:blue'| 2
+| <span>boo</span> style='color:blue' |1
+|<span>boo</span> style='color:blue'|2
|}
!! html/php
<table style="border:1px solid black">
<tr>
-<td style="color:blue"> 1
+<td style="color:blue">1
</td>
-<td style="color:blue"> 2
+<td style="color:blue">2
</td></tr></table>
!! html/parsoid
<table style="border:1px solid black">
<tr>
-<td style="color:blue"> 1</td>
-<td style="color:blue"> 2</td>
+<td style="color:blue">1</td>
+<td style="color:blue">2</td>
</tr>
</table>
!! end
@@ -6759,7 +6674,7 @@ parsoid={
</td>
<td style="color:red;">Foo
</td>
-<td> style="color:red;"</td>
+<td>style="color:red;"</td>
<td>Bar
</td>
<td style="color:red;">Foo
@@ -6860,7 +6775,7 @@ T107652: <ref>s in templates that also generate table cell attributes should be
<references />
!! html/parsoid
<table>
-<tbody><tr><td style="background:#f9f9f9;" typeof="mw:Transclusion" about="#mwt1" data-mw='{"parts":["|",{"template":{"target":{"wt":"table_attribs_7","href":"./Template:Table_attribs_7"},"params":{},"i":0}}]}'>Foo<span class="mw-ref" id="cite_ref-1" rel="dc:references" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-1"},"attrs":{}}'><a href="./Main_Page#cite_note-1" style="counter-reset: mw-Ref 1;"><span class="mw-reflink-text">[1]</span></a></span></td></tr>
+<tbody><tr><td style="background:#f9f9f9;" typeof="mw:Transclusion" about="#mwt1" data-mw='{"parts":["|",{"template":{"target":{"wt":"table_attribs_7","href":"./Template:Table_attribs_7"},"params":{},"i":0}}]}'>Foo<sup class="mw-ref" id="cite_ref-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-1"},"attrs":{}}'><a href="./Main_Page#cite_note-1" style="counter-reset: mw-Ref 1;"><span class="mw-reflink-text">[1]</span></a></s></td></tr>
</tbody></table>
<ol class="mw-references references" typeof="mw:Extension/references" about="#mwt5" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><a href="./Main_Page#cite_ref-1" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-1" class="mw-reference-text" data-parsoid="{}">foo</span></li></ol>
!! end
@@ -6873,14 +6788,14 @@ parsoid=wt2html,html2html
{|
|-
-! foo
+!foo
|}
!! html/*
<table>
<tr>
-<th> foo
+<th>foo
</th></tr></table>
!! end
@@ -6893,14 +6808,14 @@ parsoid=wt2html,html2html
{|
|-
-| foo
+|foo
|}
!! html/*
<table>
<tr>
-<td> foo
+<td>foo
</td></tr></table>
!! end
@@ -6911,17 +6826,17 @@ Table attributes with empty value
parsoid=wt2html,html2html
!! wikitext
{|
-| style=| hello
+| style=|hello
|}
!! html/php
<table>
<tr>
-<td style=""> hello
+<td style="">hello
</td></tr></table>
!! html/parsoid
<table>
-<tbody><tr><td style=""> hello</td></tr>
+<tbody><tr><td style="">hello</td></tr>
</tbody></table>
!! end
@@ -6930,7 +6845,7 @@ Wikitext table with a lot of comments
!! wikitext
{|
<!-- c0 -->
-| foo
+|foo
<!-- c1 -->
|-<!-- c2 -->
<!-- c3 -->
@@ -6940,7 +6855,7 @@ Wikitext table with a lot of comments
!! html
<table>
<tr>
-<td> foo
+<td>foo
</td></tr>
<tr>
<td>
@@ -6953,18 +6868,18 @@ Wikitext table comments represented in parsoid dom
!! wikitext
{|<!--c1--><!--c2-->
|-<!--c3-->
-| x
+|x
|}
!! html/php+tidy
<table>
-<tr>
-<td>x</td>
-</tr>
-</table>
+
+<tbody><tr>
+<td>x
+</td></tr></tbody></table>
!! html/parsoid
<table><!--c1--><!--c2-->
<tbody><tr data-parsoid='{"startTagSrc":"|-","autoInsertedEnd":true}'><!--c3-->
-<td data-parsoid='{"autoInsertedEnd":true}'> x</td></tr>
+<td data-parsoid='{"autoInsertedEnd":true}'>x</td></tr>
</tbody></table>
!! end
@@ -6990,14 +6905,14 @@ Table cell with a single comment
!! wikitext
{|
| <!-- c1 -->
-| a
+|a
|}
!! html
<table>
<tr>
<td>
</td>
-<td> a
+<td>a
</td></tr></table>
!! end
@@ -7008,21 +6923,21 @@ Table-cell after a comment-only-empty-line
{|
|a
<!--c1-->
-<!--c2-->| b
+<!--c2-->|b
|}
!! html
<table>
<tr>
<td>a
</td>
-<td> b
+<td>b
</td></tr></table>
!! html/parsoid
<table>
<tbody><tr data-parsoid='{"autoInsertedEnd":true,"autoInsertedStart":true}'><td data-parsoid='{"autoInsertedEnd":true}'>a</td>
<!--c1-->
-<!--c2--><td data-parsoid='{"autoInsertedEnd":true}'> b</td></tr>
+<!--c2--><td data-parsoid='{"autoInsertedEnd":true}'>b</td></tr>
</tbody></table>
!! end
@@ -7031,21 +6946,21 @@ Table-cell after a comment-only-empty-line
Build table with {{!}}
!! wikitext
{{{!}} class="wikitable"
-! header
-! second header
+!header
+!second header
{{!}}- style="color:red;"
-{{!}} data {{!}}{{!}} style="color:red;" {{!}} second data
+{{!}}data{{!}}{{!}} style="color:red;" {{!}}second data
{{!}}}
!! html
<table class="wikitable">
<tr>
-<th> header
+<th>header
</th>
-<th> second header
+<th>second header
</th></tr>
<tr style="color:red;">
-<td> data </td>
-<td style="color:red;"> second data
+<td>data</td>
+<td style="color:red;">second data
</td></tr></table>
!! end
@@ -7054,33 +6969,33 @@ Build table with {{!}}
Build table with pipe as data
!! wikitext
{| class="wikitable"
-! header
-! second header
+!header
+!second header
|- style="color:red;"
-| data || style="color:red;" | second data
+|data|| style="color:red;" |second data
|-
-| style="color:red;" | data with | || style="color:red;" | second data with |
+| style="color:red;" |data with | || style="color:red;" | second data with |
|-
-|| data with | ||| second data with |
+||data with | |||second data with |
|}
!! html
<table class="wikitable">
<tr>
-<th> header
+<th>header
</th>
-<th> second header
+<th>second header
</th></tr>
<tr style="color:red;">
-<td> data </td>
-<td style="color:red;"> second data
+<td>data</td>
+<td style="color:red;">second data
</td></tr>
<tr>
-<td style="color:red;"> data with | </td>
-<td style="color:red;"> second data with |
+<td style="color:red;">data with |</td>
+<td style="color:red;">second data with |
</td></tr>
<tr>
-<td> data with | </td>
-<td> second data with |
+<td>data with |</td>
+<td>second data with |
</td></tr></table>
!! end
@@ -7089,25 +7004,25 @@ Build table with pipe as data
Build table with wikilink
!! wikitext
{| class="wikitable"
-! header || second header
+!header||second header
|- style="color:red;"
-| data [[Main Page|linktext]] || second data [[Main Page|linktext]]
+|data [[Main Page|linktext]]||second data [[Main Page|linktext]]
|-
-| data || second data [[Main Page|link|text with pipe]]
+|data||second data [[Main Page|link|text with pipe]]
|}
!! html
<table class="wikitable">
<tr>
-<th> header </th>
-<th> second header
+<th>header</th>
+<th>second header
</th></tr>
<tr style="color:red;">
-<td> data <a href="/wiki/Main_Page" title="Main Page">linktext</a> </td>
-<td> second data <a href="/wiki/Main_Page" title="Main Page">linktext</a>
+<td>data <a href="/wiki/Main_Page" title="Main Page">linktext</a></td>
+<td>second data <a href="/wiki/Main_Page" title="Main Page">linktext</a>
</td></tr>
<tr>
-<td> data </td>
-<td> second data <a href="/wiki/Main_Page" title="Main Page">link|text with pipe</a>
+<td>data</td>
+<td>second data <a href="/wiki/Main_Page" title="Main Page">link|text with pipe</a>
</td></tr></table>
!! end
@@ -7130,7 +7045,7 @@ Wikitext table with html-syntax row
!! end
!! test
-Implicit <td> after a |-
+Fostered content in tables: Plain text
!! options
parsoid=wt2html,html2html
!! wikitext
@@ -7145,7 +7060,10 @@ a
</table>
!! html/php+tidy
-<p>a</p>
+
+
+a
+<table></table>
!! html/parsoid
<p data-parsoid='{"fostered":true,"autoInsertedEnd":true}'>a</p><table>
<tbody><tr data-parsoid='{"startTagSrc":"|-","autoInsertedEnd":true}'>
@@ -7154,7 +7072,7 @@ a
!! end
!! test
-Lists should be recognized in an implicit <td> context
+Fostered content in tables: Lists
!! options
parsoid=wt2html,html2html
!! wikitext
@@ -7169,9 +7087,10 @@ parsoid=wt2html,html2html
</table>
!! html/php+tidy
-<ul>
-<li>a</li>
-</ul>
+<ul><li>a</li></ul><table>
+
+
+</table>
!! html/parsoid
<ul data-parsoid='{"fostered":true,"autoInsertedEnd":true}'><li>a</li></ul><table>
<tbody><tr data-parsoid='{"startTagSrc":"|-","autoInsertedEnd":true}'>
@@ -7180,24 +7099,24 @@ parsoid=wt2html,html2html
!! end
!! test
-Table cells not properly parsed in an implicit-td context
+Template generated table cell with attributes
!! wikitext
{|
|-
-{{table_attribs_4}} || a || b
+{{table_attribs_4}} ||a||b
|}
!! html/php+tidy
<table>
-<tr>
+
+<tbody><tr>
<td style="background-color:#DC241f;" width="10px"></td>
<td>a</td>
-<td>b</td>
-</tr>
-</table>
+<td>b
+</td></tr></tbody></table>
!! html/parsoid
<table>
<tbody><tr data-parsoid='{"startTagSrc":"|-","autoInsertedEnd":true}'>
-<td style="background-color:#DC241f;" width="10px" about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"autoInsertedEnd":true,"pi":[[]]}' data-mw='{"parts":[{"template":{"target":{"wt":"table_attribs_4","href":"./Template:Table_attribs_4"},"params":{},"i":0}}," || a || b"]}'> </td><td about="#mwt1"> a </td><td about="#mwt1"> b</td></tr>
+<td style="background-color:#DC241f;" width="10px" about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"autoInsertedEnd":true,"pi":[[]]}' data-mw='{"parts":[{"template":{"target":{"wt":"table_attribs_4","href":"./Template:Table_attribs_4"},"params":{},"i":0}}," ||a||b"]}'></td><td about="#mwt1">a</td><td about="#mwt1">b</td></tr>
!! end
!! test
@@ -7214,17 +7133,14 @@ parsoid=wt2html,wt2wt
|}<b>quux</b>
!! html+tidy
<table>
-<tr>
-<td>foo</td>
-</tr>
-</table>
-<p>bar</p>
-<table>
-<tr>
-<td>baz</td>
-</tr>
-</table>
-<p><b>quux</b></p>
+<tbody><tr>
+<td>foo
+</td></tr></tbody></table><p> bar
+</p><table>
+<tbody><tr>
+<td>baz
+</td></tr></tbody></table><p><b>quux</b>
+</p>
!! end
!! test
@@ -7265,17 +7181,17 @@ Parsoid: Row-syntax table headings followed by comment & table cells
parsoid=wt2html,wt2wt
!! wikitext
{|
-! foo || bar
-<!-- foo --> || baz || quux
+!foo||bar
+<!-- foo --> ||baz||quux
|}
!! html/php
<table>
<tr>
-<th> foo </th>
-<th> bar
+<th>foo</th>
+<th>bar
</th>
-<td> baz </td>
-<td> quux
+<td>baz</td>
+<td>quux
</td></tr></table>
!! html/parsoid
@@ -7296,12 +7212,11 @@ foo
|}
!!html/php+tidy
<table class="foo">
-<tr>
+<tbody><tr>
<td class="bar">
-<p>foo</p>
-</td>
-</tr>
-</table>
+<p>foo
+</p>
+</td></tr></tbody></table>
!!html/parsoid
<table class="foo">
<tr>
@@ -7375,24 +7290,28 @@ parsoid=html2wt
|}
!! html/php+tidy
<table>
-<caption>Test</caption>
-<tr>
-<th>Month</th>
-<th>Savings</th>
-</tr>
+<caption>Test
+</caption>
+<tbody><tr>
+<th>Month
+</th>
+<th>Savings
+</th></tr>
<tr>
-<td>January</td>
-<td>$100</td>
-</tr>
+<td>January
+</td>
+<td>$100
+</td></tr>
<tr>
-<td>February</td>
-<td>$80</td>
-</tr>
+<td>February
+</td>
+<td>$80
+</td></tr>
<tr>
-<td>Sum</td>
-<td>$180</td>
-</tr>
-</table>
+<td>Sum
+</td>
+<td>$180
+</td></tr></tbody></table>
!! end
# T137406: No whitespace in the HTML
@@ -7795,13 +7714,15 @@ Link with multiple pipes
!! test
Anchor containing a #. (T65430)
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
!! wikitext
[[Main Page#And#Link]]
!! html/php
-<p><a href="/wiki/Main_Page#And.23Link" title="Main Page">Main Page#And#Link</a>
+<p><a href="/wiki/Main_Page#And#Link" title="Main Page">Main Page#And#Link</a>
</p>
!! html/parsoid
-<p><a rel="mw:WikiLink" href="./Main_Page#And.23Link" title="Main Page" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#And.23Link"},"sa":{"href":"Main Page#And#Link"}}'>Main Page#And#Link</a></p>
+<p><a rel="mw:WikiLink" href="./Main_Page#And#Link" title="Main Page" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#And#Link"},"sa":{"href":"Main Page#And#Link"}}'>Main Page#And#Link</a></p>
!! end
!! test
@@ -7919,13 +7840,27 @@ Link containing % as a double hex sequence interpreted to hex sequence
## Example for such a section: == < ==
!! test
Link containing "#<" and "#>" % as a hex sequences- these are valid section anchors
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
!! wikitext
[[%23%3c]][[%23%3e]]
!! html/php
-<p><a href="#.3C">#&lt;</a><a href="#.3E">#&gt;</a>
+<p><a href="#&lt;">#&lt;</a><a href="#&gt;">#&gt;</a>
</p>
!! html/parsoid
-<p><a rel="mw:WikiLink" href="./Main_Page#.3C" title="Main Page" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#.3C"},"sa":{"href":"%23%3c"}}'>#&lt;</a><a rel="mw:WikiLink" href="./Main_Page#.3E" title="Main Page" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#.3E"},"sa":{"href":"%23%3e"}}'>#></a></p>
+<p><a rel="mw:WikiLink" href="./Main_Page#&lt;" title="Main Page" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#&lt;"},"sa":{"href":"%23%3c"}}'>#&lt;</a><a rel="mw:WikiLink" href="./Main_Page#>" title="Main Page" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#>"},"sa":{"href":"%23%3e"}}'>#></a></p>
+!! end
+
+## Example for such a section: == < ==
+!! test
+Link containing "#<" and "#>" % as a hex sequences- these are valid section anchors (legacy)
+!! config
+wgFragmentMode=[ 'legacy' ]
+!! wikitext
+[[%23%3c]][[%23%3e]]
+!! html/php
+<p><a href="#.3C">#&lt;</a><a href="#.3E">#&gt;</a>
+</p>
!! end
!! test
@@ -7987,7 +7922,7 @@ Link containing double quotes and spaces
<p><a href="/index.php?title=Cool_%22Gator%22&amp;action=edit&amp;redlink=1" class="new" title="Cool &quot;Gator&quot; (page does not exist)">Cool "Gator"</a>
</p>
!! html/parsoid
-<p><a rel="mw:WikiLink" href="./Cool_%22Gator%22" title='Cool "Gator"'>Cool "Gator"</a></p>
+<p><a rel="mw:WikiLink" href='./Cool_"Gator"' title='Cool "Gator"'>Cool "Gator"</a></p>
!! end
!! test
@@ -7995,7 +7930,7 @@ File containing double quotes and spaces
!! wikitext
[[File:Cool "Gator".png]]
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[]}' data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}]}'><a href="./File:Cool_%22Gator%22.png" data-parsoid='{"a":{"href":"./File:Cool_%22Gator%22.png"},"sa":{"href":"File:Cool \"Gator\".png"}}'><img resource='./File:Cool_"Gator".png' src="./Special:FilePath/Cool_%22Gator%22.png" height="220" width="220" data-parsoid='{"a":{"resource":"./File:Cool_\"Gator\".png","height":"220","width":"220","src":"./Special:FilePath/Cool_%22Gator%22.png"},"sa":{"resource":"File:Cool \"Gator\".png","src":"./Special:FilePath/Cool_\"Gator\".png"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[]}' data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}]}'><a href="./File:Cool_%22Gator%22.png" data-parsoid='{"a":{"href":"./File:Cool_%22Gator%22.png"},"sa":{"href":"File:Cool \"Gator\".png"}}'><img resource='./File:Cool_"Gator".png' src="./Special:FilePath/Cool_%22Gator%22.png" height="220" width="220" data-parsoid='{"a":{"resource":"./File:Cool_\"Gator\".png","height":"220","width":"220","src":"./Special:FilePath/Cool_%22Gator%22.png"},"sa":{"resource":"File:Cool \"Gator\".png","src":"./Special:FilePath/Cool_\"Gator\".png"}}'/></a></figure-inline></p>
!! end
!! test
@@ -8043,7 +7978,7 @@ Link with double quotes in title part (literal) and alternate part (interpreted)
</p><p><a href="/index.php?title=%27%27Pentecoste%27%27&amp;action=edit&amp;redlink=1" class="new" title="&#39;&#39;Pentecoste&#39;&#39; (page does not exist)"><i>Pentecoste</i></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}]}'><a href="./File:Denys_Savchenko_''Pentecoste''.jpg"><img resource="./File:Denys_Savchenko_''Pentecoste''.jpg" src="./Special:FilePath/Denys_Savchenko_''Pentecoste''.jpg" height="220" width="220"/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}]}'><a href="./File:Denys_Savchenko_''Pentecoste''.jpg"><img resource="./File:Denys_Savchenko_''Pentecoste''.jpg" src="./Special:FilePath/Denys_Savchenko_''Pentecoste''.jpg" height="220" width="220"/></a></figure-inline></p>
<p><a rel="mw:WikiLink" href="./''Pentecoste''" title="''Pentecoste''">''Pentecoste''</a></p>
<p><a rel="mw:WikiLink" href="./''Pentecoste''" title="''Pentecoste''">Pentecoste</a></p>
<p><a rel="mw:WikiLink" href="./''Pentecoste''" title="''Pentecoste''"><i>Pentecoste</i></a></p>
@@ -8063,10 +7998,10 @@ Broken image links with HTML captions (T41700)
<a href="/index.php?title=Special:Upload&amp;wpDestFile=Nonexistent" class="new" title="File:Nonexistent">abc</a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&lt;script>&lt;/script>"}]}' data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}],"caption":"&amp;lt;script>&amp;lt;/script>"}'><a href="./File:Nonexistent" data-parsoid='{"a":{"href":"./File:Nonexistent"},"sa":{}}'><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220" data-parsoid='{"a":{"resource":"./File:Nonexistent","height":"220","width":"220"},"sa":{"resource":"File:Nonexistent"}}'/></a></span>
-<span typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"width","ak":"100x100px"},{"ck":"caption","ak":"&lt;script>&lt;/script>"}]}' data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}],"caption":"&amp;lt;script>&amp;lt;/script>"}'><a href="./File:Nonexistent" data-parsoid='{"a":{"href":"./File:Nonexistent"},"sa":{}}'><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="100" width="100" data-parsoid='{"a":{"resource":"./File:Nonexistent","height":"100","width":"100"},"sa":{"resource":"File:Nonexistent"}}'/></a></span>
-<span class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&amp;lt;"}]}' data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}],"caption":"&lt;span typeof=\"mw:Entity\" data-parsoid=&#39;{\"src\":\"&amp;amp;lt;\",\"srcContent\":\"&amp;lt;\",\"dsr\":[107,111,null,null]}&#39;>&amp;lt;&lt;/span>"}'><a href="./File:Nonexistent" data-parsoid='{"a":{"href":"./File:Nonexistent"},"sa":{}}'><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220" data-parsoid='{"a":{"resource":"./File:Nonexistent","height":"220","width":"220"},"sa":{"resource":"File:Nonexistent"}}'/></a></span>
-<span class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"a&lt;i>b&lt;/i>c"}]}' data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}],"caption":"a&lt;i data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[134,142,3,4]}&#39;>b&lt;/i>c"}'><a href="./File:Nonexistent" data-parsoid='{"a":{"href":"./File:Nonexistent"},"sa":{}}'><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220" data-parsoid='{"a":{"resource":"./File:Nonexistent","height":"220","width":"220"},"sa":{"resource":"File:Nonexistent"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&lt;script>&lt;/script>"}]}' data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}],"caption":"&amp;lt;script>&amp;lt;/script>"}'><a href="./File:Nonexistent" data-parsoid='{"a":{"href":"./File:Nonexistent"},"sa":{}}'><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220" data-parsoid='{"a":{"resource":"./File:Nonexistent","height":"220","width":"220"},"sa":{"resource":"File:Nonexistent"}}'/></a></figure-inline>
+<figure-inline typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"width","ak":"100x100px"},{"ck":"caption","ak":"&lt;script>&lt;/script>"}]}' data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}],"caption":"&amp;lt;script>&amp;lt;/script>"}'><a href="./File:Nonexistent" data-parsoid='{"a":{"href":"./File:Nonexistent"},"sa":{}}'><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="100" width="100" data-parsoid='{"a":{"resource":"./File:Nonexistent","height":"100","width":"100"},"sa":{"resource":"File:Nonexistent"}}'/></a></figure-inline>
+<figure-inline class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&amp;lt;"}]}' data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}],"caption":"&lt;span typeof=\"mw:Entity\" data-parsoid=&#39;{\"src\":\"&amp;amp;lt;\",\"srcContent\":\"&amp;lt;\",\"dsr\":[107,111,null,null]}&#39;>&amp;lt;&lt;/span>"}'><a href="./File:Nonexistent" data-parsoid='{"a":{"href":"./File:Nonexistent"},"sa":{}}'><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220" data-parsoid='{"a":{"resource":"./File:Nonexistent","height":"220","width":"220"},"sa":{"resource":"File:Nonexistent"}}'/></a></figure-inline>
+<figure-inline class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"a&lt;i>b&lt;/i>c"}]}' data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}],"caption":"a&lt;i data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[134,142,3,4]}&#39;>b&lt;/i>c"}'><a href="./File:Nonexistent" data-parsoid='{"a":{"href":"./File:Nonexistent"},"sa":{}}'><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220" data-parsoid='{"a":{"resource":"./File:Nonexistent","height":"220","width":"220"},"sa":{"resource":"File:Nonexistent"}}'/></a></figure-inline></p>
!! end
!! test
@@ -8077,7 +8012,7 @@ Plain link to URL
<p>[<a rel="nofollow" class="external autonumber" href="http://www.example.com">[1]</a>]
</p>
!! html/parsoid
-<p>[<a rel="mw:ExtLink" href="http://www.example.com"></a>]</p>
+<p>[<a rel="mw:ExtLink" class="external autonumber" href="http://www.example.com"></a>]</p>
!! end
!! test
@@ -8097,7 +8032,7 @@ Plain link to protocol-relative URL
<p>[<a rel="nofollow" class="external autonumber" href="//www.example.com">[1]</a>]
</p>
!! html/parsoid
-<p>[<a rel="mw:ExtLink" href="//www.example.com"></a>]</p>
+<p>[<a rel="mw:ExtLink" class="external autonumber" href="//www.example.com"></a>]</p>
!! end
!! test
@@ -8140,7 +8075,7 @@ Piped link to URL: [[http://www.example.com|an example URL]]
<p>Piped link to URL: [<a rel="nofollow" class="external text" href="http://www.example.com%7Can">example URL</a>]
</p>
!! html/parsoid
-<p>Piped link to URL: [<a rel="mw:ExtLink" href="http://www.example.com%7Can" data-parsoid='{"a":{"href":"http://www.example.com%7Can"},"sa":{"href":"http://www.example.com|an"}}'>example URL</a>]</p>
+<p>Piped link to URL: [<a rel="mw:ExtLink" class="external text" href="http://www.example.com%7Can" data-parsoid='{"a":{"href":"http://www.example.com%7Can"},"sa":{"href":"http://www.example.com|an"}}'>example URL</a>]</p>
!! end
!! test
@@ -8162,13 +8097,13 @@ parsoid=wt2html
</p><p>[<a rel="nofollow" class="external free" href="http://www.example.com">http://www.example.com</a>
</p>
!! html/parsoid
-<p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[http://www.example.com "},"2":{"wt":"123]"}},"i":0}}]}'>[<a rel="mw:ExtLink" href="http://www.example.com">http://www.example.com</a> </p>
+<p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[http://www.example.com "},"2":{"wt":"123]"}},"i":0}}]}'>[<a rel="mw:ExtLink" class="external free" href="http://www.example.com">http://www.example.com</a> </p>
-<p about="#mwt2" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[[http://www.example.com |123]]"}},"i":0}}]}'>[<a rel="mw:ExtLink" href="http://www.example.com">|123</a>]</p>
+<p about="#mwt2" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[[http://www.example.com |123]]"}},"i":0}}]}'>[<a rel="mw:ExtLink" class="external text" href="http://www.example.com">|123</a>]</p>
-<p>{{echo|[<a rel="mw:ExtLink" href="http://www.example.com" data-parsoid='{"targetOff":114,"contentOffsets":[114,118],"dsr":[90,119,24,1]}'>|123</a>}}</p>
+<p>{{echo|[<a rel="mw:ExtLink" class="external text" href="http://www.example.com" data-parsoid='{"targetOff":114,"contentOffsets":[114,118],"dsr":[90,119,24,1]}'>|123</a>}}</p>
-<p about="#mwt3" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[http://www.example.com "},"2":{"wt":"123]]"}},"i":0}}]}'>[<a rel="mw:ExtLink" href="http://www.example.com">http://www.example.com</a> </p>
+<p about="#mwt3" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[http://www.example.com "},"2":{"wt":"123]]"}},"i":0}}]}'>[<a rel="mw:ExtLink" class="external free" href="http://www.example.com">http://www.example.com</a> </p>
!! end
!! test
@@ -8189,11 +8124,9 @@ T2337: Escaped self-links should be bold
title=[[Bug462]]
!! wikitext
[[Bu&#103;462]] [[Bug462]]
-!! html/php
+!! html/php+tidy
<p><a class="mw-selflink selflink">Bu&#103;462</a> <a class="mw-selflink selflink">Bug462</a>
</p>
-!! html/php+tidy
-<p><a class="mw-selflink selflink">Bug462</a> <a class="mw-selflink selflink">Bug462</a></p>
!! html/parsoid
<p><a rel="mw:WikiLink" href="./Bug462" title="Bug462">Bug462</a> <a rel="mw:WikiLink" href="./Bug462" title="Bug462">Bug462</a></p>
!! end
@@ -8518,6 +8451,31 @@ Aðrir mótmælenda<nowiki/>[[söfnuður]]
!! end
!! test
+Parsoid link bracket escaping
+!! options
+parsoid=html2wt,html2html
+!! html/parsoid
+<p><a rel="mw:WikiLink" href="./Test" title="Test">Test</a></p>
+<p>[<a rel="mw:WikiLink" href="./Test" title="Test">Test</a>]</p>
+<p>[[<a rel="mw:WikiLink" href="./Test" title="Test">Test</a>]]</p>
+<p>[[[<a rel="mw:WikiLink" href="./Test" title="Test">Test</a>]]]</p>
+<p>[[[[<a rel="mw:WikiLink" href="./Test" title="Test">Test</a>]]]]</p>
+<p>[[[[[<a rel="mw:WikiLink" href="./Test" title="Test">Test</a>]]]]]</p>
+!! wikitext
+[[Test]]
+
+[<nowiki/>[[Test]]]
+
+[[[[Test]]]]
+
+[[[<nowiki/>[[Test]]]]]
+
+[[[[[[Test]]]]]]
+
+[[[[[<nowiki/>[[Test]]]]]]]
+!! end
+
+!! test
Parsoid-centric test: Whitespace in ext- and wiki-links should be preserved
!! wikitext
[[Foo| bar]]
@@ -8545,13 +8503,26 @@ Parsoid: Scoped parsing should handle mixed transclusions and plain text
!! test
Link with angle bracket after anchor
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
!! wikitext
[[Foo#<bar>]]
!! html/php
-<p><a href="/wiki/Foo#.3Cbar.3E" title="Foo">Foo#&lt;bar&gt;</a>
+<p><a href="/wiki/Foo#&lt;bar&gt;" title="Foo">Foo#&lt;bar&gt;</a>
</p>
!! html/parsoid
-<p><a rel="mw:WikiLink" href="./Foo#.3Cbar.3E" title="Foo" data-parsoid='{"stx":"simple","a":{"href":"./Foo#.3Cbar.3E"},"sa":{"href":"Foo#&lt;bar>"}}'>Foo#&lt;bar></a></p>
+<p><a rel="mw:WikiLink" href="./Foo#&lt;bar>" title="Foo" data-parsoid='{"stx":"simple","a":{"href":"./Foo#&lt;bar>"},"sa":{"href":"Foo#&lt;bar>"}}'>Foo#&lt;bar></a></p>
+!! end
+
+!! test
+Link with angle bracket after anchor (legacy)
+!! config
+wgFragmentMode=[ 'legacy' ]
+!! wikitext
+[[Foo#<bar>]]
+!! html/php
+<p><a href="/wiki/Foo#.3Cbar.3E" title="Foo">Foo#&lt;bar&gt;</a>
+</p>
!! end
###
@@ -8568,7 +8539,7 @@ parsoid=wt2html,wt2wt,html2html
<p><a href="http://www.usemod.com/cgi-bin/mb.pl?SoftSecurity" class="extiw" title="meatball:SoftSecurity">MeatBall:SoftSecurity</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://www.usemod.com/cgi-bin/mb.pl?SoftSecurity" title="meatball:SoftSecurity">MeatBall:SoftSecurity</a></p>
+<p><a rel="mw:WikiLink/Interwiki" href="http://www.usemod.com/cgi-bin/mb.pl?SoftSecurity" title="meatball:SoftSecurity">MeatBall:SoftSecurity</a></p>
!! end
!! test
@@ -8581,11 +8552,14 @@ parsoid=wt2html,wt2wt,html2html
<p><a href="http://www.usemod.com/cgi-bin/mb.pl" class="extiw" title="meatball:">MeatBall:</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://www.usemod.com/cgi-bin/mb.pl?" title="meatball:">MeatBall:</a></p>
+<p><a rel="mw:WikiLink/Interwiki" href="http://www.usemod.com/cgi-bin/mb.pl?" title="meatball:">MeatBall:</a></p>
!! end
+## html2wt and html2html will fail because we will prefer the :en: interwiki prefix over wikipedia:
!! test
Interwiki link encoding conversion (T3636)
+!! options
+parsoid=wt2html,wt2wt
!! wikitext
*[[Wikipedia:ro:Olteni&#0355;a]]
*[[Wikipedia:ro:Olteni&#355;a]]
@@ -8593,11 +8567,16 @@ Interwiki link encoding conversion (T3636)
<ul><li><a href="http://en.wikipedia.org/wiki/ro:Olteni%C5%A3a" class="extiw" title="wikipedia:ro:Olteniţa">Wikipedia:ro:Olteni&#355;a</a></li>
<li><a href="http://en.wikipedia.org/wiki/ro:Olteni%C5%A3a" class="extiw" title="wikipedia:ro:Olteniţa">Wikipedia:ro:Olteni&#355;a</a></li></ul>
-!! html+tidy
+!! html/php+tidy
<ul>
<li><a href="http://en.wikipedia.org/wiki/ro:Olteni%C5%A3a" class="extiw" title="wikipedia:ro:Olteniţa">Wikipedia:ro:Olteniţa</a></li>
<li><a href="http://en.wikipedia.org/wiki/ro:Olteni%C5%A3a" class="extiw" title="wikipedia:ro:Olteniţa">Wikipedia:ro:Olteniţa</a></li>
</ul>
+!! html/parsoid
+<ul>
+<li><a rel="mw:WikiLink/Interwiki" href="http://en.wikipedia.org/wiki/ro:Olteniţa" title="wikipedia:ro:Olteniţa">Wikipedia:ro:Olteniţa</a></li>
+<li><a rel="mw:WikiLink/Interwiki" href="http://en.wikipedia.org/wiki/ro:Olteniţa" title="wikipedia:ro:Olteniţa">Wikipedia:ro:Olteniţa</a></li>
+</ul>
!! end
!! test
@@ -8611,6 +8590,27 @@ Interwiki link with fragment (T4130)
!! test
Link scenarios with escaped fragments
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
+!! wikitext
+[[#Is this great?]]
+[[Foo#Is this great?]]
+[[meatball:Foo#Is this great?]]
+!! html/php
+<p><a href="#Is_this_great?">#Is this great?</a>
+<a href="/wiki/Foo#Is_this_great?" title="Foo">Foo#Is this great?</a>
+<a href="http://www.usemod.com/cgi-bin/mb.pl?Foo#Is_this_great.3F" class="extiw" title="meatball:Foo">meatball:Foo#Is this great?</a>
+</p>
+!! html/parsoid
+<p><a rel="mw:WikiLink" href="./Main_Page#Is_this_great?" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#Is_this_great?"},"sa":{"href":"#Is this great?"}}'>#Is this great?</a>
+<a rel="mw:WikiLink" href="./Foo#Is_this_great?" title="Foo" data-parsoid='{"stx":"simple","a":{"href":"./Foo#Is_this_great?"},"sa":{"href":"Foo#Is this great?"}}'>Foo#Is this great?</a>
+<a rel="mw:WikiLink/Interwiki" href="http://www.usemod.com/cgi-bin/mb.pl?Foo#Is_this_great?" title="meatball:Foo" data-parsoid='{"stx":"simple","a":{"href":"http://www.usemod.com/cgi-bin/mb.pl?Foo#Is_this_great?"},"sa":{"href":"meatball:Foo#Is this great?"},"isIW":true}'>meatball:Foo#Is this great?</a></p>
+!! end
+
+!! test
+Link scenarios with escaped fragments (legacy)
+!! config
+wgFragmentMode=[ 'legacy' ]
!! wikitext
[[#Is this great?]]
[[Foo#Is this great?]]
@@ -8620,10 +8620,6 @@ Link scenarios with escaped fragments
<a href="/wiki/Foo#Is_this_great.3F" title="Foo">Foo#Is this great?</a>
<a href="http://www.usemod.com/cgi-bin/mb.pl?Foo#Is_this_great.3F" class="extiw" title="meatball:Foo">meatball:Foo#Is this great?</a>
</p>
-!! html/parsoid
-<p><a rel="mw:WikiLink" href="./Main_Page#Is_this_great.3F" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#Is_this_great.3F"},"sa":{"href":"#Is this great?"}}'>#Is this great?</a>
-<a rel="mw:WikiLink" href="./Foo#Is_this_great.3F" title="Foo" data-parsoid='{"stx":"simple","a":{"href":"./Foo#Is_this_great.3F"},"sa":{"href":"Foo#Is this great?"}}'>Foo#Is this great?</a>
-<a rel="mw:ExtLink" href="http://www.usemod.com/cgi-bin/mb.pl?Foo#Is_this_great.3F" title="meatball:Foo" data-parsoid='{"stx":"simple","a":{"href":"http://www.usemod.com/cgi-bin/mb.pl?Foo#Is_this_great.3F"},"sa":{"href":"meatball:Foo#Is this great?"},"isIW":true}'>meatball:Foo#Is this great?</a></p>
!! end
# Ideally the wikipedia: prefix here should be proto-relative too
@@ -8648,19 +8644,19 @@ Different interwiki prefixes mapping to the same URL
[[ wikiPEdia :Foo]]
!! html/parsoid
-<p><a rel="mw:ExtLink" href="//en.wikipedia.org/wiki/Foo" data-parsoid='{"stx":"simple","a":{"href":"//en.wikipedia.org/wiki/Foo"},"sa":{"href":":en:Foo"},"isIW":true}' title="en:Foo">en:Foo</a></p>
+<p><a rel="mw:WikiLink/Interwiki" href="//en.wikipedia.org/wiki/Foo" data-parsoid='{"stx":"simple","a":{"href":"//en.wikipedia.org/wiki/Foo"},"sa":{"href":":en:Foo"},"isIW":true}' title="en:Foo">en:Foo</a></p>
-<p><a rel="mw:ExtLink" href="//en.wikipedia.org/wiki/Foo" data-parsoid='{"stx":"piped","a":{"href":"//en.wikipedia.org/wiki/Foo"},"sa":{"href":":en:Foo"},"isIW":true}' title="en:Foo">Foo</a></p>
+<p><a rel="mw:WikiLink/Interwiki" href="//en.wikipedia.org/wiki/Foo" data-parsoid='{"stx":"piped","a":{"href":"//en.wikipedia.org/wiki/Foo"},"sa":{"href":":en:Foo"},"isIW":true}' title="en:Foo">Foo</a></p>
-<p><a rel="mw:ExtLink" href="http://en.wikipedia.org/wiki/Foo" data-parsoid='{"stx":"simple","a":{"href":"http://en.wikipedia.org/wiki/Foo"},"sa":{"href":"wikipedia:Foo"},"isIW":true}' title="wikipedia:Foo">wikipedia:Foo</a></p>
+<p><a rel="mw:WikiLink/Interwiki" href="http://en.wikipedia.org/wiki/Foo" data-parsoid='{"stx":"simple","a":{"href":"http://en.wikipedia.org/wiki/Foo"},"sa":{"href":"wikipedia:Foo"},"isIW":true}' title="wikipedia:Foo">wikipedia:Foo</a></p>
-<p><a rel="mw:ExtLink" href="http://en.wikipedia.org/wiki/Foo" data-parsoid='{"stx":"piped","a":{"href":"http://en.wikipedia.org/wiki/Foo"},"sa":{"href":":wikipedia:Foo"},"isIW":true}' title="wikipedia:Foo">Foo</a></p>
+<p><a rel="mw:WikiLink/Interwiki" href="http://en.wikipedia.org/wiki/Foo" data-parsoid='{"stx":"piped","a":{"href":"http://en.wikipedia.org/wiki/Foo"},"sa":{"href":":wikipedia:Foo"},"isIW":true}' title="wikipedia:Foo">Foo</a></p>
-<p><a rel="mw:ExtLink" href="http://en.wikipedia.org/wiki/en:Foo" data-parsoid='{"stx":"simple","a":{"href":"http://en.wikipedia.org/wiki/en:Foo"},"sa":{"href":"wikipedia:en:Foo"},"isIW":true}' title="wikipedia:en:Foo">wikipedia:en:Foo</a></p>
+<p><a rel="mw:WikiLink/Interwiki" href="http://en.wikipedia.org/wiki/en:Foo" data-parsoid='{"stx":"simple","a":{"href":"http://en.wikipedia.org/wiki/en:Foo"},"sa":{"href":"wikipedia:en:Foo"},"isIW":true}' title="wikipedia:en:Foo">wikipedia:en:Foo</a></p>
-<p><a rel="mw:ExtLink" href="http://en.wikipedia.org/wiki/en:Foo" data-parsoid='{"stx":"simple","a":{"href":"http://en.wikipedia.org/wiki/en:Foo"},"sa":{"href":":wikipedia:en:Foo"},"isIW":true}' title="wikipedia:en:Foo">wikipedia:en:Foo</a></p>
+<p><a rel="mw:WikiLink/Interwiki" href="http://en.wikipedia.org/wiki/en:Foo" data-parsoid='{"stx":"simple","a":{"href":"http://en.wikipedia.org/wiki/en:Foo"},"sa":{"href":":wikipedia:en:Foo"},"isIW":true}' title="wikipedia:en:Foo">wikipedia:en:Foo</a></p>
-<p><a rel="mw:ExtLink" href="http://en.wikipedia.org/wiki/Foo" data-parsoid='{"stx":"simple","a":{"href":"http://en.wikipedia.org/wiki/Foo"},"sa":{"href":" wikiPEdia :Foo"},"isIW":true}' title="wikipedia:Foo"> wikiPEdia :Foo</a></p>
+<p><a rel="mw:WikiLink/Interwiki" href="http://en.wikipedia.org/wiki/Foo" data-parsoid='{"stx":"simple","a":{"href":"http://en.wikipedia.org/wiki/Foo"},"sa":{"href":" wikiPEdia :Foo"},"isIW":true}' title="wikipedia:Foo"> wikiPEdia :Foo</a></p>
!! end
!! test
@@ -8680,11 +8676,11 @@ Interwiki links that cannot be represented in wiki syntax
<a rel="nofollow" class="external text" href="http://de.wikipedia.org/wiki/#foo">is just fragment</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://www.usemod.com/cgi-bin/mb.pl?ok" title="meatball:ok">meatball:ok</a>
-<a rel="mw:ExtLink" href="http://www.usemod.com/cgi-bin/mb.pl?ok#foo" title="meatball:ok">ok with fragment</a>
-<a rel="mw:ExtLink" href="http://www.usemod.com/cgi-bin/mb.pl?ok_as_well?" title="meatball:ok as well?">ok ending with ? mark</a>
-<a rel="mw:ExtLink" href="http://de.wikipedia.org/wiki/Foo?action=history">has query</a>
-<a rel="mw:ExtLink" href="http://de.wikipedia.org/wiki/#foo">is just fragment</a></p>
+<p><a rel="mw:WikiLink/Interwiki" href="http://www.usemod.com/cgi-bin/mb.pl?ok" title="meatball:ok">meatball:ok</a>
+<a rel="mw:WikiLink/Interwiki" href="http://www.usemod.com/cgi-bin/mb.pl?ok#foo" title="meatball:ok">ok with fragment</a>
+<a rel="mw:WikiLink/Interwiki" href="http://www.usemod.com/cgi-bin/mb.pl?ok_as_well?" title="meatball:ok as well?">ok ending with ? mark</a>
+<a rel="mw:ExtLink" class="external text" href="http://de.wikipedia.org/wiki/Foo?action=history">has query</a>
+<a rel="mw:ExtLink" class="external text" href="http://de.wikipedia.org/wiki/#foo">is just fragment</a></p>
!! end
!! test
@@ -8695,7 +8691,7 @@ Interwiki links: trail
<p><a href="http://en.wikipedia.org/wiki/Foo" class="extiw" title="wikipedia:Foo">Bar</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://en.wikipedia.org/wiki/Foo" data-parsoid='{"stx":"piped","a":{"href":"http://en.wikipedia.org/wiki/Foo"},"sa":{"href":"wikipedia:Foo"},"isIW":true,"tail":"r"}' title="wikipedia:Foo">Bar</a></p>
+<p><a rel="mw:WikiLink/Interwiki" href="http://en.wikipedia.org/wiki/Foo" data-parsoid='{"stx":"piped","a":{"href":"http://en.wikipedia.org/wiki/Foo"},"sa":{"href":"wikipedia:Foo"},"isIW":true,"tail":"r"}' title="wikipedia:Foo">Bar</a></p>
!! end
!! test
@@ -8749,7 +8745,7 @@ parsoid=wt2html,wt2wt,html2html
<p><a href="http://www.usemod.com/cgi-bin/mb.pl?Hello" class="extiw" title="meatball:Hello">local:meatball:Hello</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://www.usemod.com/cgi-bin/mb.pl?Hello" title="meatball:Hello">local:meatball:Hello</a></p>
+<p><a rel="mw:WikiLink/Interwiki" href="http://www.usemod.com/cgi-bin/mb.pl?Hello" title="meatball:Hello">local:meatball:Hello</a></p>
!! end
!! test
@@ -8847,8 +8843,8 @@ Blah blah blah
</p>
!! html/parsoid
<p>Blah blah blah
-<a rel="mw:ExtLink" href="http://es.wikipedia.org/wiki/Spanish" title="es:Spanish">es:Spanish</a>
-<a rel="mw:ExtLink" href="http://zh.wikipedia.org/wiki/Chinese" title="zh:Chinese"> zh : Chinese </a></p>
+<a rel="mw:WikiLink/Interwiki" href="http://es.wikipedia.org/wiki/Spanish" title="es:Spanish">es:Spanish</a>
+<a rel="mw:WikiLink/Interwiki" href="http://zh.wikipedia.org/wiki/Chinese" title="zh:Chinese"> zh : Chinese </a></p>
!! end
!! test
@@ -8865,7 +8861,7 @@ parsoid=wt2html
[[:::es:Spanish]]
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://es.wikipedia.org/wiki/Spanish" title="es:Spanish">es:Spanish</a>
+<p><a rel="mw:WikiLink/Interwiki" href="http://es.wikipedia.org/wiki/Spanish" title="es:Spanish">es:Spanish</a>
[[::es:Spanish]]
[[:::es:Spanish]]</p>
!! end
@@ -8942,7 +8938,7 @@ parsoid=wt2html,wt2wt,html2html
Blah blah blah
[[zh:Chinese]]
!! html/parsoid
-<p>Blah blah blah <a rel="mw:ExtLink" href="http://zh.wikipedia.org/wiki/Chinese" title="zh:Chinese">zh:Chinese</a></p>
+<p>Blah blah blah <a rel="mw:WikiLink/Interwiki" href="http://zh.wikipedia.org/wiki/Chinese" title="zh:Chinese">zh:Chinese</a></p>
!! end
## PHP parser tests script needs an update
@@ -8956,7 +8952,7 @@ parsoid=wt2html,wt2wt,html2html
Blah blah blah
[[zh:Chinese]]
!! html/parsoid
-<p>Blah blah blah <a rel="mw:ExtLink" href="http://zh.wikipedia.org/wiki/Chinese" title="zh:Chinese">zh:Chinese</a></p>
+<p>Blah blah blah <a rel="mw:WikiLink/Interwiki" href="http://zh.wikipedia.org/wiki/Chinese" title="zh:Chinese">zh:Chinese</a></p>
!! end
!! test
@@ -9043,7 +9039,7 @@ parsoid=wt2html,wt2wt,html2html
</p><p><a href="/wiki/Ko:" title="Ko:">ko:</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://es.wikipedia.org/wiki/" title="es:">es:</a></p>
+<p><a rel="mw:WikiLink/Interwiki" href="http://es.wikipedia.org/wiki/" title="es:">es:</a></p>
<p><a rel="mw:WikiLink" href="./Ko:" title="Ko:">ko:</a></p>
!! end
@@ -9071,7 +9067,7 @@ Blah blah blah
</p>
!! html/parsoid
<p>Blah blah blah
-<a rel="mw:ExtLink" href="http://es.wikipedia.org/wiki/Spanish" title="es:Spanish">local:es:Spanish</a></p>
+<a rel="mw:WikiLink/Interwiki" href="http://es.wikipedia.org/wiki/Spanish" title="es:Spanish">local:es:Spanish</a></p>
!! end
!! test
@@ -9114,10 +9110,12 @@ Blah blah blah
# This tests the Parsoid bail-out code.
!! test
3. Other redirect variants
+!! options
+parsoid=wt2html
!! wikitext
#REDIRECT [[<nowiki>[[Bar]]</nowiki>]]
!! html/parsoid
-<ol><li data-parsoid>REDIRECT [[[[Bar]]]]</li></ol>
+<ol><li>REDIRECT [[<span typeof="mw:Nowiki">[[Bar]]</span>]]</li></ol>
!! end
!! test
@@ -9265,6 +9263,7 @@ language=is
Redirect syntax under text isn't considered a redirect
!! wikitext
some text
+
#redirect [[Main Page]]
!! html/parsoid
<p>some text</p>
@@ -9287,9 +9286,9 @@ Redirect followed by block on the same line
!! options
parsoid=wt2html
!! wikitext
-#REDIRECT [[Main Page]]<!-- haha -->== hi ==
+#REDIRECT [[Main Page]]<!-- haha -->==hi==
!! html/parsoid
-<link rel="mw:PageProp/redirect" href="./Main_Page"/><!-- haha --><h2 id="hi"> hi </h2>
+<link rel="mw:PageProp/redirect" href="./Main_Page"/><!-- haha --><h2 id="hi">hi</h2>
!! end
!! test
@@ -9360,8 +9359,9 @@ parsoid=wt2html
<br/ >
!! html+tidy
-<p><br /></p>
-<p><br /></p>
+<p><br />
+</p><p><br />
+</p>
!! end
!! test
@@ -9411,7 +9411,7 @@ Handling html with a div self-closing tag
!! html/parsoid
<div title="" data-parsoid='{"stx":"html","selfClose":true}'></div>
<div title="" data-parsoid='{"stx":"html","selfClose":true}'></div>
-<div title="" data-parsoid='{"stx":"html","selfClose":true,"brokenHTMLTag":true}'></div>
+<div title="" data-parsoid='{"stx":"html","selfClose":true}'></div>
<div title="bar" data-parsoid='{"stx":"html","selfClose":true}'></div>
<div title="bar" data-parsoid='{"stx":"html","selfClose":true}'></div>
<div title="bar/" data-parsoid='{"stx":"html","autoInsertedEnd":true}'></div>
@@ -9453,10 +9453,9 @@ foo <hr
> bar
!! html+tidy
<hr />
-<hr />
-<p>foo</p>
-<hr />
-<p>bar</p>
+<hr /><p>
+foo </p><hr /><p> bar
+</p>
!! end
!! test
@@ -9505,8 +9504,8 @@ Horizontal ruler -- Supports content following dashes on same line
<hr /> Foo
!! html+tidy
-<hr />
-<p>Foo</p>
+<hr /><p> Foo
+</p>
!! end
###
@@ -9516,11 +9515,11 @@ Horizontal ruler -- Supports content following dashes on same line
Common list
!! wikitext
*Common list
-* item 2
+*item 2
*item 3
!! html
<ul><li>Common list</li>
-<li> item 2</li>
+<li>item 2</li>
<li>item 3</li></ul>
!! end
@@ -9530,21 +9529,22 @@ Numbered list
!! wikitext
#Numbered list
#item 2
-# item 3
+#item 3
!! html
<ol><li>Numbered list</li>
<li>item 2</li>
-<li> item 3</li></ol>
+<li>item 3</li></ol>
!! end
+# the switch from level 3 to ordered should not introduce a newline between
!! test
Mixed list
!! wikitext
*Mixed list
-*# with numbers
-** and bullets
-*# and numbers
+*#with numbers
+**and bullets
+*#and numbers
*bullets again
**bullet level 2
***bullet level 3
@@ -9554,13 +9554,13 @@ Mixed list
**#Number on level 3
*#number level 2
*Level 1
-*** Level 3
-#** Level 3, but ordered
+***Level 3
+#**Level 3, but ordered
!! html
<ul><li>Mixed list
-<ol><li> with numbers</li></ol>
-<ul><li> and bullets</li></ul>
-<ol><li> and numbers</li></ol></li>
+<ol><li>with numbers</li></ol>
+<ul><li>and bullets</li></ul>
+<ol><li>and numbers</li></ol></li>
<li>bullets again
<ul><li>bullet level 2
<ul><li>bullet level 3
@@ -9570,43 +9570,43 @@ Mixed list
<li>Number on level 3</li></ol></li></ul>
<ol><li>number level 2</li></ol></li>
<li>Level 1
-<ul><li><ul><li> Level 3</li></ul></li></ul></li></ul>
-<ol><li><ul><li><ul><li> Level 3, but ordered</li></ul></li></ul></li></ol>
+<ul><li><ul><li>Level 3</li></ul></li></ul></li></ul>
+<ol><li><ul><li><ul><li>Level 3, but ordered</li></ul></li></ul></li></ol>
!! end
!! test
1. Nested mixed wikitext and html list
!! wikitext
-* hi
-* <ul><li>ho</li></ul>
-* hi
-** ho
+*hi
+*<ul><li>ho</li></ul>
+*hi
+**ho
!! html/php
-<ul><li> hi</li>
-<li> <ul><li>ho</li></ul></li>
-<li> hi
-<ul><li> ho</li></ul></li></ul>
+<ul><li>hi</li>
+<li><ul><li>ho</li></ul></li>
+<li>hi
+<ul><li>ho</li></ul></li></ul>
!! html/parsoid
-<ul><li> hi</li>
-<li> <ul data-parsoid='{"stx":"html"}'><li data-parsoid='{"stx":"html"}'>ho</li></ul></li>
-<li> hi
-<ul><li> ho</li></ul></li></ul>
+<ul><li>hi</li>
+<li><ul data-parsoid='{"stx":"html"}'><li data-parsoid='{"stx":"html"}'>ho</li></ul></li>
+<li>hi
+<ul><li>ho</li></ul></li></ul>
!! end
!! test
2. Nested mixed wikitext and html list (incompatible)
!! wikitext
-; hi
-: {{echo|<li>ho</li>}}
+;hi
+:{{echo|<li>ho</li>}}
!! html/php
-<dl><dt> hi</dt>
-<dd> <li>ho</li></dd></dl>
+<dl><dt>hi</dt>
+<dd><li>ho</li></dd></dl>
!! html/parsoid
-<dl><dt> hi</dt>
-<dd> <li about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"stx":"html","pi":[[{"k":"1"}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;li>ho&lt;/li>"}},"i":0}}]}'>ho</li></dd></dl>
+<dl><dt>hi</dt>
+<dd><li about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"stx":"html","pi":[[{"k":"1"}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;li>ho&lt;/li>"}},"i":0}}]}'>ho</li></dd></dl>
!! end
!! test
@@ -9678,24 +9678,24 @@ Nested lists 6 (both elements empty)
!! test
Nested lists 7 (skip initial nesting levels)
!! wikitext
-*** foo
+***foo
!! html
-<ul><li><ul><li><ul><li> foo</li></ul></li></ul></li></ul>
+<ul><li><ul><li><ul><li>foo</li></ul></li></ul></li></ul>
!! end
!! test
Nested lists 8 (multiple nesting transitions)
!! wikitext
-* foo
-*** bar
-** baz
-* boo
+*foo
+***bar
+**baz
+*boo
!! html
-<ul><li> foo
-<ul><li><ul><li> bar</li></ul></li>
-<li> baz</li></ul></li>
-<li> boo</li></ul>
+<ul><li>foo
+<ul><li><ul><li>bar</li></ul></li>
+<li>baz</li></ul></li>
+<li>boo</li></ul>
!! end
@@ -9736,60 +9736,61 @@ parsoid
!! test
List items are not parsed correctly following a <pre> block (T2785)
!! wikitext
-* <pre>foo</pre>
-* <pre>bar</pre>
-* zar
+*<pre>foo</pre>
+*<pre>bar</pre>
+*zar
!! html/php
-<ul><li> <pre>foo</pre></li>
-<li> <pre>bar</pre></li>
-<li> zar</li></ul>
+<ul><li><pre>foo</pre></li>
+<li><pre>bar</pre></li>
+<li>zar</li></ul>
!! html/parsoid
-<ul><li> <pre typeof="mw:Extension/pre" about="#mwt2" data-mw='{"name":"pre","attrs":{},"body":{"extsrc":"foo"}}'>foo</pre></li>
-<li> <pre typeof="mw:Extension/pre" about="#mwt4" data-mw='{"name":"pre","attrs":{},"body":{"extsrc":"bar"}}'>bar</pre></li>
-<li> zar</li></ul>
+<ul><li><pre typeof="mw:Extension/pre" about="#mwt2" data-mw='{"name":"pre","attrs":{},"body":{"extsrc":"foo"}}'>foo</pre></li>
+<li><pre typeof="mw:Extension/pre" about="#mwt4" data-mw='{"name":"pre","attrs":{},"body":{"extsrc":"bar"}}'>bar</pre></li>
+<li>zar</li></ul>
!! end
+# FIXME: Might benefit from a html/parsoid since this has a template
!! test
List items from template
!! wikitext
{{inner list}}
-* item 2
+*item 2
-* item 0
+*item 0
{{inner list}}
-* item 2
+*item 2
-* item 0
-* notSOL{{inner list}}
-* item 2
+*item 0
+*notSOL{{inner list}}
+*item 2
!! html
-<ul><li> item 1</li>
-<li> item 2</li></ul>
-<ul><li> item 0</li>
-<li> item 1</li>
-<li> item 2</li></ul>
-<ul><li> item 0</li>
-<li> notSOL</li>
-<li> item 1</li>
-<li> item 2</li></ul>
+<ul><li>item 1</li>
+<li>item 2</li></ul>
+<ul><li>item 0</li>
+<li>item 1</li>
+<li>item 2</li></ul>
+<ul><li>item 0</li>
+<li>notSOL</li>
+<li>item 1</li>
+<li>item 2</li></ul>
!! end
!! test
List interrupted by empty line or heading
!! wikitext
-* foo
+*foo
-** bar
-== A heading ==
-* Another list item
+**bar
+==A heading==
+*Another list item
!! html
-<ul><li> foo</li></ul>
-<ul><li><ul><li> bar</li></ul></li></ul>
+<ul><li>foo</li></ul>
+<ul><li><ul><li>bar</li></ul></li></ul>
<h2><span class="mw-headline" id="A_heading">A heading</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: A heading">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<ul><li> Another list item</li></ul>
+<ul><li>Another list item</li></ul>
!!end
@@ -9807,11 +9808,14 @@ Multiple list tags generated by templates
</li>
!! html+tidy
-<ul>
-<li>a</li>
-<li>b</li>
-<li>c</li>
-</ul>
+<li>a
+</li><li>b
+</li><li>c
+</li>
+!! html/parsoid
+<li about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"stx":"html","autoInsertedEnd":true,"dsr":[0,44,null,null],"pi":[[{"k":"1"}],[{"k":"1"}],[{"k":"1"}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;li>"}},"i":0}},"a\n",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;li>"}},"i":1}},"b\n",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;li>"}},"i":2}},"c"]}'>a
+</li><li about="#mwt1">b
+</li><li about="#mwt1" data-parsoid='{"stx":"html","autoInsertedEnd":true,"dsr":[null,44,null,0]}'>c</li>
!!end
!!test
@@ -9851,36 +9855,36 @@ Replacing whitespace with tabs still doesn't break the list (gerrit 78327)
!!end
+# FIXME: Parsoid has a dedicated DOM pass to mimic this Tidy-specific li-hack
+# That pass could possibly be removed.
!!test
-Test the li-hack
-(The PHP parser relies on Tidy for the hack)
+Test the li-hack (a hack from Tidy days, but doesn't work as advertised with Remex)
!!options
parsoid=wt2html,wt2wt
!! wikitext
-* foo
-* <li>li-hack
-* {{echo|<li>templated li-hack}}
-* <!--foo--> <li> unsupported li-hack with preceding comments
+*foo
+*<li>li-hack
+*{{echo|<li>templated li-hack}}
+*<!--foo--><li> unsupported li-hack with preceding comments
<ul>
<li><li>not a li-hack
</li>
</ul>
!! html+tidy
+<ul><li>foo</li>
+<li class="mw-empty-elt"></li><li>li-hack</li>
+<li class="mw-empty-elt"></li><li>templated li-hack</li>
+<li class="mw-empty-elt"></li><li> unsupported li-hack with preceding comments</li></ul>
<ul>
-<li>foo</li>
-<li>li-hack</li>
-<li>templated li-hack</li>
-<li>unsupported li-hack with preceding comments</li>
-</ul>
-<ul>
-<li>not a li-hack</li>
+<li class="mw-empty-elt"></li><li>not a li-hack
+</li>
</ul>
!! html/parsoid
<ul><li> foo</li>
-<li data-parsoid='{"stx":"html","autoInsertedEnd":true,"liHackSrc":"* "}'>li-hack</li>
-<li about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"stx":"html","autoInsertedEnd":true,,"pi":[[{"k":"1"}]]}' data-mw='{"parts":["* ",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;li>templated li-hack"}},"i":0}}]}'>templated li-hack</li>
-<li data-parsoid='{"autoInsertedEnd":true}'> <!--foo--> </li><li data-parsoid='{"stx":"html","autoInsertedEnd":true}'> unsupported li-hack with preceding comments</li></ul>
+<li data-parsoid='{"stx":"html","autoInsertedEnd":true,"liHackSrc":"*"}'>li-hack</li>
+<li about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"stx":"html","autoInsertedEnd":true,,"pi":[[{"k":"1"}]]}' data-mw='{"parts":["*",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;li>templated li-hack"}},"i":0}}]}'>templated li-hack</li>
+<li data-parsoid='{"autoInsertedEnd":true}'><!--foo--></li><li data-parsoid='{"stx":"html","autoInsertedEnd":true}'>unsupported li-hack with preceding comments</li></ul>
<ul data-parsoid='{"stx":"html"}'>
<li class="mw-empty-elt" data-parsoid='{"stx":"html","autoInsertedEnd":true}'></li><li data-parsoid='{"stx":"html"}'>not a li-hack
@@ -9894,24 +9898,26 @@ Parsoid: Make sure nested lists are serialized on their own line even if HTML co
!! options
parsoid
!! wikitext
-# foo
-## bar
-* foo
-** bar
-: foo
-:: bar
+#foo
+##bar
+
+*foo
+**bar
+
+:foo
+::bar
!! html
<ol>
-<li> foo<ol>
-<li> bar</li>
+<li>foo<ol>
+<li>bar</li>
</ol></li>
</ol><ul>
-<li> foo<ul>
-<li> bar</li>
+<li>foo<ul>
+<li>bar</li>
</ul></li>
</ul><dl>
-<dd> foo<dl>
-<dd> bar</dd>
+<dd>foo<dl>
+<dd>bar</dd>
</dl></dd>
</dl>
!! end
@@ -9933,101 +9939,103 @@ parsoid
# tags (parse, minimize scope of fixup, and roundtrip back)
# ------------------------------------------------------------------------
+# Remex and Parsoid output stems from list handling diffs because Parsoid & PHP parser.
+# Parsoid's list handling is more aware of block structure.
!! test
Unbalanced closing block tags break a list
-(php parser relies on Tidy to fix up)
!! wikitext
<div>
*a</div><div>
*b</div>
!! html+tidy
<div>
-<ul>
+<ul><li>a</li></ul></div><div>
+<li>b</li></div>
+!! html/parsoid
+<div><ul>
<li>a</li>
-</ul>
-</div>
-<div>
-<ul>
+</ul></div>
+<div><ul>
<li>b</li>
-</ul>
-</div>
+</ul></div>
!! end
-# Parsoid fails this test, but it might be tricky to support properly.
-# See T70395.
!! test
Unbalanced closing non-block tags don't break a list
-(php parser relies on Tidy to fix up)
!! wikitext
<span>
*a</span><span>
*b</span>
!! html/php+tidy
-<ul>
-<li><span>a</span></li>
-<li><span>b</span></li>
-</ul>
+<p><span>
+</span></p>
+<ul><li>a<span></span></li>
+<li>b</li></ul>
!! html/parsoid
<span>
<ul>
-<li>a<span></span>
-</li>
-<li>b
-</li>
+<li>a<span></span></li>
+<li>b</li>
</ul>
</span>
!! end
+# Parsoid does some post-dom-building cleanup
+# which is why its output differs from Remex.
!! test
Unclosed formatting tags that straddle lists are closed and reopened
-(php parser relies on Tidy to fix up)
!! options
parsoid=wt2html,wt2wt,html2html
!! wikitext
-# <s> a
-# b </s>
+#<s> a
+#b </s>
!! html/php+tidy
-<ol>
-<li><s>a</s></li>
-<li><s>b</s></li>
-</ol>
+<ol><li><s> a</s></li><s>
+</s><li><s>b </s></li></ol>
!! html/parsoid
-<ol><li> <s> a</s></li>
-<li><s> b </s></li></ol>
+<ol><li><s> a</s></li>
+<li><s>b </s></li></ol>
!! end
+# Output is ugly because of all the misnested tag fixups.
+# Remex is wrapping p-tags around empty elements.
+# Parsoid has special-case handling of this pattern of
+# wrapping lists in formatting tags.
+# FIXME: Should we remove this code from Parsoid? Or add
+# special support in Remex? If the latter, maybe just wait
+# for Parsoid to become the default parser.
# See T70395.
!!test
1. List embedded in a formatting tag
!! wikitext
<small>
-* foo
+*foo
</small>
!! html/php+tidy
-<ul>
-<li><small>foo</small></li>
-</ul>
+<p><small>
+</small></p><small><ul><li>foo</li></ul></small><small></small><p><small></small>
+</p>
!! html/parsoid
<small>
<ul>
-<li> foo</li>
+<li>foo</li>
</ul>
</small>
!!end
-## Ugly Parsoid output here
-## Not sure what the right output is.
+# Output is ugly because of all the misnested tag fixups
+# Remex is wrapping p-tags around empty elements.
+# Parsoid has code that strips useless p-tags.
!!test
-2. List embedded in a formatting tag
+2. List embedded in a formatting tag in a misnested way
!! wikitext
<small>
*a
*b</small>
!! html/php+tidy
-<ul>
-<li><small>a</small></li>
-<li><small>b</small></li>
-</ul>
+<p><small>
+</small></p><small></small><ul><small><li>a</li>
+</small><li><small>b</small></li></ul>
!! html/parsoid
<small></small>
<ul><small>
@@ -10037,31 +10045,6 @@ parsoid=wt2html,wt2wt,html2html
</ul>
!!end
-# Ugly Parsoid and PHP parser output here
-# Not sure if we want to make this a test!
-#
-## !!test
-## 3. Unclosed formatting tags in list elements
-## !! wikitext
-## *<small>a
-## *<small>b
-## !! html/php+tidy
-## <ul>
-## <li><small>a</small></li>
-## <li><small><small>b</small></small></li>
-## </ul>
-## !! html/parsoid
-## <ul>
-## <li><small>a</small></li>
-## <small>
-## <li><small>b</small></li>
-## </small></ul>
-## !!end
-
-# This is a bug in the PHP parser + tidy combination.
-# (The </tr> tag gets parsed as text and html-escaped by PHP,
-# and then fostered out of the table by tidy.)
-# We believe the Parsoid output to be correct.
!! test
Table with missing opening <tr> tag
!! options
@@ -10073,10 +10056,9 @@ parsoid=wt2html,wt2wt
</table>
!! html+tidy
<table>
-<tr>
-<td>foo</td>
+<tbody><tr><td>foo</td>
</tr>
-</table>
+</tbody></table>
!! end
###
@@ -10217,35 +10199,35 @@ Magic Word: {{CURRENTTIMESTAMP}}
!! test
Magic Words LOCAL (UTC)
!! wikitext
-* {{LOCALMONTH}}
-* {{LOCALMONTH1}}
-* {{LOCALMONTHNAME}}
-* {{LOCALMONTHNAMEGEN}}
-* {{LOCALMONTHABBREV}}
-* {{LOCALDAY}}
-* {{LOCALDAY2}}
-* {{LOCALDAYNAME}}
-* {{LOCALYEAR}}
-* {{LOCALTIME}}
-* {{LOCALHOUR}}
-* {{LOCALWEEK}}
-* {{LOCALDOW}}
-* {{LOCALTIMESTAMP}}
-!! html
-<ul><li> 01</li>
-<li> 1</li>
-<li> January</li>
-<li> January</li>
-<li> Jan</li>
-<li> 1</li>
-<li> 01</li>
-<li> Thursday</li>
-<li> 1970</li>
-<li> 00:02</li>
-<li> 00</li>
-<li> 1</li>
-<li> 4</li>
-<li> 19700101000203</li></ul>
+*{{LOCALMONTH}}
+*{{LOCALMONTH1}}
+*{{LOCALMONTHNAME}}
+*{{LOCALMONTHNAMEGEN}}
+*{{LOCALMONTHABBREV}}
+*{{LOCALDAY}}
+*{{LOCALDAY2}}
+*{{LOCALDAYNAME}}
+*{{LOCALYEAR}}
+*{{LOCALTIME}}
+*{{LOCALHOUR}}
+*{{LOCALWEEK}}
+*{{LOCALDOW}}
+*{{LOCALTIMESTAMP}}
+!! html
+<ul><li>01</li>
+<li>1</li>
+<li>January</li>
+<li>January</li>
+<li>Jan</li>
+<li>1</li>
+<li>01</li>
+<li>Thursday</li>
+<li>1970</li>
+<li>00:02</li>
+<li>00</li>
+<li>1</li>
+<li>4</li>
+<li>19700101000203</li></ul>
!! end
@@ -10554,11 +10536,9 @@ title=[['foo & bar = baz']]
parsoid={ "modes": ["wt2html","wt2wt"], "normalizePhp": true }
!! wikitext
''{{PAGENAME}}''
-!! html/php
+!! html+tidy
<p><i>&#39;foo &#38; bar &#61; baz&#39;</i>
</p>
-!! html+tidy
-<p><i>'foo &amp; bar = baz'</i></p>
!! end
!! test
@@ -10568,11 +10548,9 @@ title=[[*RFC 1234 http://example.com/]]
parsoid={ "modes": ["wt2html","wt2wt"], "normalizePhp": true }
!! wikitext
{{PAGENAME}}
-!! html/php
+!! html+tidy
<p>&#42;RFC&#32;1234 http&#58;//example.com/
</p>
-!! html+tidy
-<p>*RFC 1234 http://example.com/</p>
!! end
!! test
@@ -10594,11 +10572,9 @@ title=[[*RFC 1234 http://example.com/]]
parsoid={ "modes": ["wt2html","wt2wt"], "normalizePhp": true }
!! wikitext
{{PAGENAMEE}}
-!! html/php
+!! html+tidy
<p>&#42;RFC_1234_http&#58;//example.com/
</p>
-!! html+tidy
-<p>*RFC_1234_http://example.com/</p>
!! end
!! test
@@ -10935,10 +10911,10 @@ Magic links: RFC (T2479)
!! wikitext
RFC 822
!! html/php
-<p><a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc822">RFC 822</a>
+<p><a class="external mw-magiclink-rfc" rel="nofollow" href="https://tools.ietf.org/html/rfc822">RFC 822</a>
</p>
!! html/parsoid
-<p><a href="//tools.ietf.org/html/rfc822" rel="mw:ExtLink">RFC 822</a></p>
+<p><a href="https://tools.ietf.org/html/rfc822" rel="mw:ExtLink" class="external text">RFC 822</a></p>
!! end
!! test
@@ -10946,10 +10922,10 @@ Magic links: RFC (T67278)
!! wikitext
This is RFC 822 but thisRFC 822 is not RFC 822linked.
!! html/php
-<p>This is <a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc822">RFC 822</a> but thisRFC 822 is not RFC 822linked.
+<p>This is <a class="external mw-magiclink-rfc" rel="nofollow" href="https://tools.ietf.org/html/rfc822">RFC 822</a> but thisRFC 822 is not RFC 822linked.
</p>
!! html/parsoid
-<p>This is <a href="//tools.ietf.org/html/rfc822" rel="mw:ExtLink">RFC 822</a> but thisRFC 822 is not RFC 822linked.</p>
+<p>This is <a href="https://tools.ietf.org/html/rfc822" rel="mw:ExtLink" class="external text">RFC 822</a> but thisRFC 822 is not RFC 822linked.</p>
!! end
!! test
@@ -10959,12 +10935,12 @@ RFC &nbsp;&#160;&#0160;&#xA0;&#Xa0; 822
RFC
822
!! html/php
-<p><a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc822">RFC 822</a>
+<p><a class="external mw-magiclink-rfc" rel="nofollow" href="https://tools.ietf.org/html/rfc822">RFC 822</a>
RFC
822
</p>
!! html/parsoid
-<p><a href="//tools.ietf.org/html/rfc822" rel="mw:ExtLink">RFC <span typeof="mw:Entity" data-parsoid='{"src":"&amp;nbsp;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#160;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#0160;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#xA0;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#Xa0;","srcContent":" "}'> </span> 822</a>
+<p><a href="https://tools.ietf.org/html/rfc822" rel="mw:ExtLink" class="external text">RFC <span typeof="mw:Entity" data-parsoid='{"src":"&amp;nbsp;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#160;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#0160;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#xA0;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#Xa0;","srcContent":" "}'> </span> 822</a>
RFC
822</p>
!! end
@@ -11022,7 +10998,7 @@ PMID 1234
<p><a class="external mw-magiclink-pmid" rel="nofollow" href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract">PMID 1234</a>
</p>
!! html/parsoid
-<p><a href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract" rel="mw:ExtLink">PMID 1234</a></p>
+<p><a href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract" rel="mw:ExtLink" class="external text">PMID 1234</a></p>
!! end
!! test
@@ -11033,7 +11009,7 @@ This is PMID 1234 but thisPMID 1234 is not PMID 1234linked.
<p>This is <a class="external mw-magiclink-pmid" rel="nofollow" href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract">PMID 1234</a> but thisPMID 1234 is not PMID 1234linked.
</p>
!! html/parsoid
-<p>This is <a href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract" rel="mw:ExtLink">PMID 1234</a> but thisPMID 1234 is not PMID 1234linked.</p>
+<p>This is <a href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract" rel="mw:ExtLink" class="external text">PMID 1234</a> but thisPMID 1234 is not PMID 1234linked.</p>
!! end
!! test
@@ -11048,7 +11024,7 @@ PMID
1234
</p>
!! html/parsoid
-<p><a href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract" rel="mw:ExtLink">PMID <span typeof="mw:Entity" data-parsoid='{"src":"&amp;nbsp;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#160;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#0160;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#xA0;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#Xa0;","srcContent":" "}'> </span> 1234</a>
+<p><a href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract" rel="mw:ExtLink" class="external text">PMID <span typeof="mw:Entity" data-parsoid='{"src":"&amp;nbsp;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#160;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#0160;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#xA0;","srcContent":" "}'> </span><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#Xa0;","srcContent":" "}'> </span> 1234</a>
PMID
1234</p>
!! end
@@ -11060,14 +11036,14 @@ Magic links: use appropriate serialization for "almost" magic links.
!! wikitext
X[[Special:BookSources/0978739256|foo]]
-X[//tools.ietf.org/html/rfc1234 foo]
+X[https://tools.ietf.org/html/rfc1234 foo]
!! html/php
<p>X<a href="/wiki/Special:BookSources/0978739256" title="Special:BookSources/0978739256">foo</a>
-</p><p>X<a rel="nofollow" class="external text" href="//tools.ietf.org/html/rfc1234">foo</a>
+</p><p>X<a rel="nofollow" class="external text" href="https://tools.ietf.org/html/rfc1234">foo</a>
</p>
!! html/parsoid
<p>X<a rel="mw:WikiLink" href="./Special:BookSources/0978739256" title="Special:BookSources/0978739256">foo</a></p>
-<p>X<a rel="mw:ExtLink" href="//tools.ietf.org/html/rfc1234">foo</a></p>
+<p>X<a rel="mw:ExtLink" class="external text" href="https://tools.ietf.org/html/rfc1234">foo</a></p>
!! end
!! test
@@ -11317,11 +11293,20 @@ Templates with templated name
!! html
<p>foo
</p>
-<ul><li> item 1</li></ul>
+<ul><li>item 1</li></ul>
!! html/parsoid
<p about="#mwt2" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"{{echo|echo}}","href":"./Template:Echo"},"params":{"1":{"wt":"foo"}},"i":0}}]}'>foo</p>
-<ul about="#mwt4" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"{{echo|inner list}} ","href":"./Template:Inner_list"},"params":{},"i":0}}]}'><li> item 1</li></ul>
+<ul about="#mwt4" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"{{echo|inner list}} ","href":"./Template:Inner_list"},"params":{},"i":0}}]}'><li>item 1</li></ul>
+!! end
+
+## Regression test; the output here isn't really that interesting.
+!! test
+Templates with templated name and top level template args
+!! wikitext
+{{1{{2{{{3}}}|4=5}}}}
+!! html/parsoid
+<p about="#mwt2" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"1{{2{{{3}}}|4=5}}"},"params":{},"i":0}}]}'>{{1{{2{{{3}}}|4=5}}}}</p>
!! end
# Parsoid markup is deliberate "broken". This is an edge case.
@@ -11350,12 +11335,7 @@ Template with thumb image (with link in description)
This is a test template with parameter <div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/index.php?title=Special:Upload&amp;wpDestFile=Noimage.png" class="new" title="File:Noimage.png">File:Noimage.png</a> <div class="thumbcaption"><a href="/index.php?title=No_link&amp;action=edit&amp;redlink=1" class="new" title="No link (page does not exist)">link</a> <a href="/index.php?title=No_link&amp;action=edit&amp;redlink=1" class="new" title="No link (page does not exist)">caption</a></div></div></div>
!! html+tidy
-<p>This is a test template with parameter</p>
-<div class="thumb tright">
-<div class="thumbinner" style="width:182px;"><a href="/index.php?title=Special:Upload&amp;wpDestFile=Noimage.png" class="new" title="File:Noimage.png">File:Noimage.png</a>
-<div class="thumbcaption"><a href="/index.php?title=No_link&amp;action=edit&amp;redlink=1" class="new" title="No link (page does not exist)">link</a> <a href="/index.php?title=No_link&amp;action=edit&amp;redlink=1" class="new" title="No link (page does not exist)">caption</a></div>
-</div>
-</div>
+<p>This is a test template with parameter </p><div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/index.php?title=Special:Upload&amp;wpDestFile=Noimage.png" class="new" title="File:Noimage.png">File:Noimage.png</a> <div class="thumbcaption"><a href="/index.php?title=No_link&amp;action=edit&amp;redlink=1" class="new" title="No link (page does not exist)">link</a> <a href="/index.php?title=No_link&amp;action=edit&amp;redlink=1" class="new" title="No link (page does not exist)">caption</a></div></div></div>
!! html/parsoid
<p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"paramtest","href":"./Template:Paramtest"},"params":{"param":{"wt":"[[Image:noimage.png|thumb|[[no link|link]] [[no link|caption]]]]"}},"i":0}}]}'>This is a test template with parameter </p><figure class="mw-default-size" typeof="mw:Error mw:Image/Thumb" about="#mwt1" data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}]}'><a href="./File:Noimage.png" ><img resource="./File:Noimage.png" src="./Special:FilePath/Noimage.png" height="220" width="220"/></a><figcaption><a rel="mw:WikiLink" href="./No_link" title="No link">link</a> <a rel="mw:WikiLink" href="./No_link" title="No link">caption</a></figcaption></figure>
!! end
@@ -11401,28 +11381,28 @@ T2553: link with two variables in a piped link
Abort table cell attribute parsing on wikilink
!! wikitext
{|
-| testing [[one|two]] | three || four
-| testing one two | three || four
-| testing="[[one|two]]" | three || four
+|testing [[one|two]] |three||four
+|testing one two |three||four
+|testing="[[one|two]]" |three||four
|}
!! html/php
<table>
<tr>
-<td> testing <a href="/index.php?title=One&amp;action=edit&amp;redlink=1" class="new" title="One (page does not exist)">two</a> | three </td>
-<td> four
+<td>testing <a href="/index.php?title=One&amp;action=edit&amp;redlink=1" class="new" title="One (page does not exist)">two</a> |three</td>
+<td>four
</td>
-<td> three </td>
-<td> four
+<td>three</td>
+<td>four
</td>
-<td> testing="<a href="/index.php?title=One&amp;action=edit&amp;redlink=1" class="new" title="One (page does not exist)">two</a>" | three </td>
-<td> four
+<td>testing="<a href="/index.php?title=One&amp;action=edit&amp;redlink=1" class="new" title="One (page does not exist)">two</a>" |three</td>
+<td>four
</td></tr></table>
!! html/parsoid
<table>
-<tbody><tr data-parsoid='{"autoInsertedEnd":true,"autoInsertedStart":true}'><td data-parsoid='{"autoInsertedEnd":true}'> testing <a rel="mw:WikiLink" href="./One" title="One" data-parsoid='{"stx":"piped","a":{"href":"./One"},"sa":{"href":"one"}}'>two</a> | three </td><td data-parsoid='{"stx_v":"row","autoInsertedEnd":true}'> four</td>
-<td data-parsoid='{"a":{"testing":null,"one":null,"two":null},"sa":{"testing":"","one":"","two":""},"autoInsertedEnd":true}'> three </td><td data-parsoid='{"stx_v":"row","autoInsertedEnd":true}'> four</td>
-<td> testing="<a rel="mw:WikiLink" href="./One" title="One" data-parsoid='{"stx":"piped","a":{"href":"./One"},"sa":{"href":"one"}}'>two</a>" | three </td><td data-parsoid='{"stx_v":"row","autoInsertedEnd":true}'> four</td></tr>
+<tbody><tr data-parsoid='{"autoInsertedEnd":true,"autoInsertedStart":true}'><td data-parsoid='{"autoInsertedEnd":true}'>testing <a rel="mw:WikiLink" href="./One" title="One" data-parsoid='{"stx":"piped","a":{"href":"./One"},"sa":{"href":"one"}}'>two</a> |three</td><td data-parsoid='{"stx":"row","autoInsertedEnd":true}'>four</td>
+<td data-parsoid='{"a":{"testing":null,"one":null,"two":null},"sa":{"testing":"","one":"","two":""},"autoInsertedEnd":true}'>three</td><td data-parsoid='{"stx":"row","autoInsertedEnd":true}'>four</td>
+<td>testing="<a rel="mw:WikiLink" href="./One" title="One" data-parsoid='{"stx":"piped","a":{"href":"./One"},"sa":{"href":"one"}}'>two</a>" |three</td><td data-parsoid='{"stx":"row","autoInsertedEnd":true}'>four</td></tr>
</tbody></table>
!! end
@@ -11430,11 +11410,11 @@ Abort table cell attribute parsing on wikilink
Don't abort table cell attribute parsing if wikilink is found in template arg
!! wikitext
{|
-| Test {{#tag:ref|One two "[[three]]" four}}
+|Test {{#tag:ref|One two "[[three]]" four}}
|}
!! html/parsoid
<table>
-<tbody><tr><td> Test <ref about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[{"k":"1"}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"#tag:ref","function":"tag"},"params":{"1":{"wt":"One two \"[[three]]\" four"}},"i":0}}]}'>One two "<a rel="mw:WikiLink" href="./Three" title="Three">three</a>" four</ref></td></tr>
+<tbody><tr><td>Test <ref about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[{"k":"1"}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"#tag:ref","function":"tag"},"params":{"1":{"wt":"One two \"[[three]]\" four"}},"i":0}}]}'>One two "<a rel="mw:WikiLink" href="./Three" title="Three">three</a>" four</ref></td></tr>
</tbody></table>
!! end
@@ -11548,12 +11528,12 @@ foo {{table}}
</p>
<table>
<tr>
-<td> 1 </td>
-<td> 2
+<td>1</td>
+<td>2
</td></tr>
<tr>
-<td> 3 </td>
-<td> 4
+<td>3</td>
+<td>4
</td></tr></table>
!! end
@@ -11568,12 +11548,12 @@ foo
</p>
<table>
<tr>
-<td> 1 </td>
-<td> 2
+<td>1</td>
+<td>2
</td></tr>
<tr>
-<td> 3 </td>
-<td> 4
+<td>3</td>
+<td>4
</td></tr></table>
!! end
@@ -11639,21 +11619,13 @@ Templates with intersecting and overlapping ranges
{{echo|{{!}}hi}}
|}
!! html/php+tidy
-<p>ha</p>
-<p>ho</p>
-<table>
-<tr>
-<td></td>
-</tr>
-<tr>
-<td>hi</td>
-</tr>
-</table>
-<table>
-<tr>
-<td></td>
-</tr>
-</table>
+<p>ha</p><table>
+
+</table><p>ho</p><table>
+
+<tbody><tr>
+<td>hi
+</td></tr></tbody></table>
!! html/parsoid
<p about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"stx":"html","autoInsertedEnd":true,"pi":[[{"k":"1"}],[{"k":"1"}],[{"k":"1"}]],"firstWikitextNode":"table"}' data-mw='{"parts":["{|",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"\n&lt;p>ha&lt;/p>"}},"i":0}},"\n","{|",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"\n&lt;p>ho&lt;/p>"}},"i":1}},"\n",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"{{!}}hi"}},"i":2}},"\n|}"]}'>ha</p><table about="#mwt1" typeof="mw:ExpandedAttrs" data-mw='{"attribs":[[{"txt":"","html":""},{"html":""}]]}'>
@@ -11899,32 +11871,32 @@ Includes and comments at SOL
!! options
parsoid=wt2html,html2html
!! wikitext
-<!-- comment --><noinclude><!-- comment --></noinclude><!-- comment -->== hu ==
+<!-- comment --><noinclude><!-- comment --></noinclude><!-- comment -->==hu==
<noinclude>
some
-</noinclude>* stuff
-* here
+</noinclude>*stuff
+*here
-<includeonly>can have stuff</includeonly>=== here ===
+<includeonly>can have stuff</includeonly>===here===
!! html/php
<h2><span class="mw-headline" id="hu">hu</span></h2>
<p>some
</p>
-<ul><li> stuff</li>
-<li> here</li></ul>
+<ul><li>stuff</li>
+<li>here</li></ul>
<h3><span class="mw-headline" id="here">here</span></h3>
!! html/parsoid
-<!-- comment --><meta typeof="mw:Includes/NoInclude" data-parsoid='{"src":"&lt;noinclude>"}'/><!-- comment --><meta typeof="mw:Includes/NoInclude/End" data-parsoid='{"src":"&lt;/noinclude>"}'/><!-- comment --><h2> hu </h2>
+<!-- comment --><meta typeof="mw:Includes/NoInclude" data-parsoid='{"src":"&lt;noinclude>"}'/><!-- comment --><meta typeof="mw:Includes/NoInclude/End" data-parsoid='{"src":"&lt;/noinclude>"}'/><!-- comment --><h2 id="hu">hu</h2>
<meta typeof="mw:Includes/NoInclude" data-parsoid='{"src":"&lt;noinclude>"}'/>
<p>some</p>
-<meta typeof="mw:Includes/NoInclude/End" data-parsoid='{"src":"&lt;/noinclude>"}'/><ul><li> stuff</li>
-<li> here</li></ul>
+<meta typeof="mw:Includes/NoInclude/End" data-parsoid='{"src":"&lt;/noinclude>"}'/><ul><li>stuff</li>
+<li>here</li></ul>
-<meta typeof="mw:Includes/IncludeOnly" data-parsoid='{"src":"&lt;includeonly>can have stuff&lt;/includeonly>"}'/><meta typeof="mw:Includes/IncludeOnly/End" data-parsoid='{"src":""}'/><h3> here </h3>
+<meta typeof="mw:Includes/IncludeOnly" data-parsoid='{"src":"&lt;includeonly>can have stuff&lt;/includeonly>"}'/><meta typeof="mw:Includes/IncludeOnly/End" data-parsoid='{"src":""}'/><h3 id="here">here</h3>
!! end
@@ -12143,10 +12115,10 @@ Preprocessor precedence 5: tplarg takes precedence over template
!! wikitext
{{Precedence5|Bullet}}
!! html/php
-<ul><li> Bar</li></ul>
+<ul><li>Bar</li></ul>
!! html/parsoid
-<ul typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"Precedence5","href":"./Template:Precedence5"},"params":{"1":{"wt":"Bullet"}},"i":0}}]}'><li> Bar</li></ul>
+<ul typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"Precedence5","href":"./Template:Precedence5"},"params":{"1":{"wt":"Bullet"}},"i":0}}]}'><li>Bar</li></ul>
!! end
!! test
@@ -12236,14 +12208,14 @@ Preprocessor precedence 9: groups of braces
{{Preprocessor precedence 9|Four|Bullet|1|2}}
!! html/php
<dl><dt>4</dt>
-<dd> {Four}</dd>
+<dd>{Four}</dd>
<dt>5</dt>
-<dd> </dd></dl>
-<ul><li> Bar</li></ul>
+<dd></dd></dl>
+<ul><li>Bar</li></ul>
<dl><dt>6</dt>
-<dd> Four</dd>
+<dd>Four</dd>
<dt>7</dt>
-<dd> {Bullet}</dd></dl>
+<dd>{Bullet}</dd></dl>
!! html/parsoid
<dl about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"Preprocessor precedence 9","href":"./Template:Preprocessor_precedence_9"},"params":{"1":{"wt":"Four"},"2":{"wt":"Bullet"},"3":{"wt":"1"},"4":{"wt":"2"}},"i":0}}]}'>
@@ -12281,21 +12253,21 @@ language=zh
{{Preprocessor precedence 10|Three|raw2|Bullet|1|2}}
!! html/php
<dl><dt>1</dt>
-<dd> raw</dd>
+<dd>raw</dd>
<dt>2</dt>
-<dd> -</dd></dl>
-<ul><li> Bar-</li></ul>
+<dd>-</dd></dl>
+<ul><li>Bar-</li></ul>
<dl><dt>3</dt>
-<dd> -Three-</dd>
+<dd>-Three-</dd>
<dt>4</dt>
-<dd> raw2</dd>
+<dd>raw2</dd>
<dt>5</dt>
-<dd> -</dd></dl>
-<ul><li> Bar-</li></ul>
+<dd>-</dd></dl>
+<ul><li>Bar-</li></ul>
<dl><dt>6</dt>
-<dd> -Three-</dd>
+<dd>-Three-</dd>
<dt>7</dt>
-<dd> raw2</dd></dl>
+<dd>raw2</dd></dl>
!! html/parsoid
<dl about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"Preprocessor precedence 10","href":"./Template:Preprocessor_precedence_10"},"params":{"1":{"wt":"Three"},"2":{"wt":"raw2"},"3":{"wt":"Bullet"},"4":{"wt":"1"},"5":{"wt":"2"}},"i":0}}]}'>
@@ -12349,43 +12321,34 @@ Preprocessor precedence 12: broken language converter closed by brace.
parsoid=wt2html
!! wikitext
This form breaks the template, which is unfortunate:
-* {{echo|foo-{bar}bat}}
+*{{echo|foo-{bar}bat}}
But if the broken language converter markup is inside an extension
tag, nothing bad happens:
-* <nowiki>foo-{bar}bat</nowiki>
-* {{echo|<nowiki>foo-{bar}bat</nowiki>}}
-* <pre>foo-{bar}bat</pre>
-* {{echo|<pre>foo-{bar}bat</pre>}}
+*<nowiki>foo-{bar}bat</nowiki>
+*{{echo|<nowiki>foo-{bar}bat</nowiki>}}
+*<pre>foo-{bar}bat</pre>
+*{{echo|<pre>foo-{bar}bat</pre>}}
<tag>foo-{bar}bat</tag>
{{echo|<tag>foo-{bar}bat</tag>}}
!! html/php+tidy
-<p>This form breaks the template, which is unfortunate:</p>
-<ul>
-<li>{{echo|foo-{bar}bat}}</li>
-</ul>
-<p>But if the broken language converter markup is inside an extension tag, nothing bad happens:</p>
-<ul>
-<li>foo-{bar}bat</li>
-<li>foo-{bar}bat</li>
-<li>
-<pre>
-foo-{bar}bat
-</pre></li>
-<li>
-<pre>
-foo-{bar}bat
-</pre></li>
-</ul>
-<pre>
-'foo-{bar}bat'
+<p>This form breaks the template, which is unfortunate:
+</p>
+<ul><li>{{echo|foo-{bar}bat}}</li></ul>
+<p>But if the broken language converter markup is inside an extension
+tag, nothing bad happens:
+</p>
+<ul><li>foo-&#123;bar}bat</li>
+<li>foo-&#123;bar}bat</li>
+<li><pre>foo-{bar}bat</pre></li>
+<li><pre>foo-{bar}bat</pre></li></ul>
+<pre>'foo-{bar}bat'
array (
)
</pre>
-<pre>
-'foo-{bar}bat'
+<pre>'foo-{bar}bat'
array (
)
</pre>
@@ -12409,45 +12372,43 @@ Preprocessor precedence 13: broken language converter in external link
!! options
parsoid=wt2html
!! wikitext
-* [http://example.com/-{foo Example in URL]
-* [http://example.com Example in -{link} description]
-* {{echo|[http://example.com/-{foo Breaks template, however]}}
+*[http://example.com/-{foo Example in URL]
+*[http://example.com Example in -{link} description]
+*{{echo|[http://example.com/-{foo Breaks template, however]}}
!! html/php+tidy
-<ul>
-<li><a rel="nofollow" class="external text" href="http://example.com/-{foo">Example in URL</a></li>
+<ul><li><a rel="nofollow" class="external text" href="http://example.com/-{foo">Example in URL</a></li>
<li><a rel="nofollow" class="external text" href="http://example.com">Example in -{link} description</a></li>
-<li>{{echo|<a rel="nofollow" class="external text" href="http://example.com/-{foo">Breaks template, however</a>}}</li>
-</ul>
+<li>{{echo|<a rel="nofollow" class="external text" href="http://example.com/-{foo">Breaks template, however</a>}}</li></ul>
!! html/parsoid
<ul>
-<li><a rel="mw:ExtLink" href="http://example.com/-{foo">Example in URL</a></li>
-<li><a rel="mw:ExtLink" href="http://example.com">Example in -{link} description</a></li>
-<li>{{echo|<a rel="mw:ExtLink" href="http://example.com/-{foo">Breaks template, however</a>}}</li>
+<li><a rel="mw:ExtLink" class="external text" href="http://example.com/-{foo">Example in URL</a></li>
+<li><a rel="mw:ExtLink" class="external text" href="http://example.com">Example in -{link} description</a></li>
+<li>{{echo|<a rel="mw:ExtLink" class="external text" href="http://example.com/-{foo">Breaks template, however</a>}}</li>
</ul>
!! end
!! test
Preprocessor precedence 14: broken language converter in comment
!! wikitext
-* <!--{{foo}}--> ...should be ok
-* <!---{{foo}}--> ...extra dashes
-* {{echo|foo<!-- -{bar} -->bat}} ...should be ok
+*<!--{{foo}}-->...should be ok
+*<!---{{foo}}-->...extra dashes
+*{{echo|foo<!-- -{bar} -->bat}}...should be ok
!! html/php+tidy
-<ul>
-<li>...should be ok</li>
+<ul><li>...should be ok</li>
<li>...extra dashes</li>
-<li>foobat ...should be ok</li>
-</ul>
+<li>foobat...should be ok</li></ul>
!! html/parsoid
<ul>
-<li><!--{{foo}}--> ...should be ok</li>
-<li><!--&#x2D;{{foo}}--> ...extra dashes</li>
-<li><span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo&lt;!-- -{bar} -->bat"}},"i":0}}]}'>foo</span><span about="#mwt1"><!-- &#x2D;{bar} --></span><span about="#mwt1">bat</span> ...should be ok</li>
+<li><!--{{foo}}-->...should be ok</li>
+<li><!--&#x2D;{{foo}}-->...extra dashes</li>
+<li><span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo&lt;!-- -{bar} -->bat"}},"i":0}}]}'>foo</span><span about="#mwt1"><!-- &#x2D;{bar} --></span><span about="#mwt1">bat</span>...should be ok</li>
</ul>
!! end
!! test
Preprocessor precedence 15: broken brace markup in headings
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
!! options
parsoid=wt2html
!! wikitext
@@ -12465,32 +12426,37 @@ __NOTOC__ __NOEDITSECTION__
===6 foo-{bar 6===
6
!! html/php+tidy
-<h3><span class="mw-headline" id="1_foo.5Bbar_1">1 foo[bar 1</span></h3>
-<p>1</p>
-<h3><span class="mw-headline" id="2_foo.5B.5Bbar_2">2 foo[[bar 2</span></h3>
-<p>2</p>
-<h3><span class="mw-headline" id="3_foo.7Bbar_3">3 foo{bar 3</span></h3>
-<p>3</p>
-<h3><span class="mw-headline" id="4_foo.7B.7Bbar_4">4 foo{{bar 4</span></h3>
-<p>4</p>
-<h3><span class="mw-headline" id="5_foo.7B.7B.7Bbar_5">5 foo{{{bar 5</span></h3>
-<p>5</p>
-<h3><span class="mw-headline" id="6_foo-.7Bbar_6">6 foo-{bar 6</span></h3>
-<p>6</p>
+<h3><span id="1_foo.5Bbar_1"></span><span class="mw-headline" id="1_foo[bar_1">1 foo[bar 1</span></h3>
+<p>1
+</p>
+<h3><span id="2_foo.5B.5Bbar_2"></span><span class="mw-headline" id="2_foo[[bar_2">2 foo[[bar 2</span></h3>
+<p>2
+</p>
+<h3><span id="3_foo.7Bbar_3"></span><span class="mw-headline" id="3_foo{bar_3">3 foo{bar 3</span></h3>
+<p>3
+</p>
+<h3><span id="4_foo.7B.7Bbar_4"></span><span class="mw-headline" id="4_foo{{bar_4">4 foo{{bar 4</span></h3>
+<p>4
+</p>
+<h3><span id="5_foo.7B.7B.7Bbar_5"></span><span class="mw-headline" id="5_foo{{{bar_5">5 foo{{{bar 5</span></h3>
+<p>5
+</p>
+<h3><span id="6_foo-.7Bbar_6"></span><span class="mw-headline" id="6_foo-{bar_6">6 foo-{bar 6</span></h3>
+<p>6
+</p>
!! html/parsoid
-<meta property="mw:PageProp/notoc"/> <meta property="mw:PageProp/noeditsection"/
->
-<h3>1 foo[bar 1</h3>
+<meta property="mw:PageProp/notoc"/> <meta property="mw:PageProp/noeditsection"/>
+<h3 id="1_foo[bar_1"><span id="1_foo.5Bbar_1" typeof="mw:FallbackId"></span>1 foo[bar 1</h3>
<p>1</p>
-<h3>2 foo[[bar 2</h3>
+<h3 id="2_foo[[bar_2"><span id="2_foo.5B.5Bbar_2" typeof="mw:FallbackId"></span>2 foo[[bar 2</h3>
<p>2</p>
-<h3>3 foo{bar 3</h3>
+<h3 id="3_foo{bar_3"><span id="3_foo.7Bbar_3" typeof="mw:FallbackId"></span>3 foo{bar 3</h3>
<p>3</p>
-<h3>4 foo{{bar 4</h3>
+<h3 id="4_foo{{bar_4"><span id="4_foo.7B.7Bbar_4" typeof="mw:FallbackId"></span>4 foo{{bar 4</h3>
<p>4</p>
-<h3>5 foo{{{bar 5</h3>
+<h3 id="5_foo{{{bar_5"><span id="5_foo.7B.7B.7Bbar_5" typeof="mw:FallbackId"></span>5 foo{{{bar 5</h3>
<p>5</p>
-<h3>6 foo-{bar 6</h3>
+<h3 id="6_foo-{bar_6"><span id="6_foo-.7Bbar_6" typeof="mw:FallbackId"></span>6 foo-{bar 6</h3>
<p>6</p>
!! end
@@ -12508,6 +12474,45 @@ parsoid=wt2html
<p><span typeof="mw:LanguageVariant" data-mw-variant='{"disabled":{"t":"&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=&#39;{\"pi\":[[{\"k\":\"1\"}]],\"dsr\":[2,14,null,null]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"foo\"}},\"i\":0}}]}&#39;>foo&lt;/span>bar"}}'></span></p>
!! end
+!! test
+Preprocessor precedence 17: template w/o target shouldn't prevent closing
+!! options
+parsoid=wt2html
+!! wikitext
+{{echo|hi {{}}}}
+!! html/php
+<p>hi {{}}
+</p>
+!! html/parsoid
+<p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"hi {{}}"}},"i":0}}]}'>hi {{}}</p>
+!! end
+
+!! test
+Preprocessor precedence 18: another rightmost wins scenario
+!! options
+parsoid=wt2html
+!! wikitext
+{{ -{{{{1|tplarg}}} }} }-
+!! html/php
+<p>{{ -{tplarg }} }-
+</p>
+!! html/parsoid
+<p>{{ -{<span about="#mwt1" typeof="mw:Param" data-mw='{"parts":[{"templatearg":{"target":{"wt":"1"},"params":{"1":{"wt":"tplarg"}},"i":0}}]}'>tplarg</span> }} }-</p>
+!! end
+
+!! test
+Preprocessor precedence 19: break syntax
+!! options
+parsoid=wt2html
+!! wikitext
+-{{
+!! html/php
+<p>-{{
+</p>
+!! html/parsoid
+<p>-{{</p>
+!! end
+
###
### Token Stream Patcher tests
###
@@ -12637,9 +12642,7 @@ Templates: 2. Inside a block tag
!! html+tidy
<div>Foo</div>
-<blockquote>
-<p>Foo</p>
-</blockquote>
+<blockquote><p>Foo</p></blockquote>
!!end
!!test
@@ -12678,9 +12681,9 @@ Templates: P-wrapping: 1c. Templates on consecutive lines
bar <div>baz</div>
!! html+tidy
-<p>Foo</p>
-<p>bar</p>
-<div>baz</div>
+<p>Foo
+</p><p>
+bar </p><div>baz</div>
!! end
!!test
@@ -12974,17 +12977,17 @@ Templates: Support for templates generating attributes and content
4. Entities and nowikis inside templated attributes should be handled correctly inside templated tables
!! wikitext
{|
-| {{table_attribs_6}} hi
+|{{table_attribs_6}} hi
|}
!! html/php
<table>
<tr>
-<td style="background: red;"> hi
+<td style="background: red;">hi
</td></tr></table>
!! html/parsoid
<table>
-<tbody><tr><td style="background: red;" typeof="mw:Transclusion" about="#mwt1" data-parsoid='{"autoInsertedEnd":true,"pi":[[]]}' data-mw='{"parts":["| ",{"template":{"target":{"wt":"table_attribs_6","href":"./Template:Table_attribs_6"},"params":{},"i":0}}," hi"]}'> hi</td></tr>
+<tbody><tr><td style="background: red;" typeof="mw:Transclusion" about="#mwt1" data-parsoid='{"autoInsertedEnd":true,"pi":[[]]}' data-mw='{"parts":["|",{"template":{"target":{"wt":"table_attribs_6","href":"./Template:Table_attribs_6"},"params":{},"i":0}}," hi"]}'> hi</td></tr>
</tbody></table>
!! end
@@ -13103,12 +13106,13 @@ Templates: Wiki Tables: 1a. Fostering of entire template content
a
<tr><td></td></tr></table>
-!! html+tidy
-<p>a</p>
-<table>
-<tr>
-<td></td>
-</tr>
+!! html/php+tidy
+
+a
+<table><tbody><tr><td></td></tr></tbody></table>
+!! html/parsoid
+<p about="#mwt2" typeof="mw:Transclusion" data-parsoid='{"fostered":true,"autoInsertedEnd":true,"firstWikitextNode":"TABLE","pi":[[{"k":"1"}]]}' data-mw='{"parts":["{|\n",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"a"}},"i":0}},"\n|}"]}'>a</p><table about="#mwt2">
+
</table>
!! end
@@ -13128,14 +13132,18 @@ foo
</div>
<tr><td></td></tr></table>
-!! html+tidy
+!! html/php+tidy
<div>
+<p>foo
+</p>
+</div><table>
+
+<tbody><tr><td></td></tr></tbody></table>
+!! html/parsoid
+<div about="#mwt3" typeof="mw:Transclusion" data-parsoid='{"stx":"html","fostered":true,"autoInsertedEnd":true,"firstWikitextNode":"TABLE","pi":[[{"k":"1"}],[{"k":"1"}]]}' data-mw='{"parts":["{|\n",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;div>"}},"i":0}},"\nfoo\n",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;/div>"}},"i":1}},"\n|}"]}'>
<p>foo</p>
-</div>
-<table>
-<tr>
-<td></td>
-</tr>
+</div><table about="#mwt3">
+
</table>
!! end
@@ -13152,13 +13160,15 @@ a
<div>b</div>
<tr><td></td></tr></table>
-!! html+tidy
-<p>a</p>
-<div>b</div>
-<table>
-<tr>
-<td></td>
-</tr>
+!! html/php+tidy
+
+a
+<div>b</div><table>
+<tbody><tr><td></td></tr></tbody></table>
+!! html/parsoid
+<p about="#mwt2" typeof="mw:Transclusion" data-parsoid='{"fostered":true,"autoInsertedEnd":true,"firstWikitextNode":"TABLE","pi":[[{"k":"1"}]]}' data-mw='{"parts":["{|\n",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"a\n&lt;div>b&lt;/div>"}},"i":0}},"\n|}"]}'>a</p><div about="#mwt2">b</div><table about="#mwt2">
+
+
</table>
!! end
@@ -13228,7 +13238,7 @@ Templates: Wiki Tables: 7. Fosterable <ref>s should get fostered
<references />
!!html/parsoid
-<span about="#mwt2" class="mw-ref" id="cite_ref-1" rel="dc:references" typeof="mw:Transclusion mw:Extension/ref" data-mw='{"parts":[{"template":{"target":{"wt":"PartialTable","href":"./Template:PartialTable"},"params":{},"i":0}},"&lt;ref>foo&lt;/ref>\n|}"]}'><a href="./Main_Page#cite_note-1"><span class="mw-reflink-text">[1]</span></a></span><table about="#mwt2">
+<sup about="#mwt2" class="mw-ref" id="cite_ref-1" rel="dc:references" typeof="mw:Transclusion mw:Extension/ref" data-mw='{"parts":[{"template":{"target":{"wt":"PartialTable","href":"./Template:PartialTable"},"params":{},"i":0}},"&lt;ref>foo&lt;/ref>\n|}"]}'><a href="./Main_Page#cite_note-1"><span class="mw-reflink-text">[1]</span></a></sup><table about="#mwt2">
<tbody>
</tbody></table>
@@ -13307,78 +13317,10 @@ a<div>b{{echo|c</div>d}}e
a<div>bc</div>de
!! html+tidy
-<p>a</p>
-<div>bc</div>
-<p>de</p>
+<p>a</p><div>bc</div><p>de
+</p>
!! end
-!!test
-Templates: Ugly templates: 1. Navbox template parses badly leading to table misnesting
-(Parsoid-centric)
-!! options
-parsoid
-!! wikitext
-{|
-|{{echo|foo</table>}}
-|bar
-|}
-!! html
-<table about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":["{|\n|",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo</table>"}},"i":0}},"\n|bar\n|}"]}'>
-
-<tbody>
-<tr>
-<td>foo</td></tr></tbody></table><span about="#mwt1">
-</span><span about="#mwt1">|bar</span><span about="#mwt1">
-|}</span>
-!!end
-
-!!test
-Templates: Ugly templates: 2. Navbox template parses badly leading to table misnesting
-(Parsoid-centric)
-!! options
-parsoid
-!! wikitext
-<table>
- <tr>
- <td>
- <table>
- <tr>
- <td>1. {{echo|foo </table>}}</td>
- <td> bar </td>
- <td>2. {{echo|baz </table>}}</td>
- </tr>
- <tr>
- <td>abc</td>
- </tr>
- </table>
- </td>
- </tr>
- <tr>
- <td>xyz</td>
- </tr>
-</table>
-!! html
-<table about="#mwt2" typeof="mw:Transclusion" data-mw='{"parts":["<table>\n <tr>\n <td>\n <table>\n <tr>\n <td>1. ",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo </table>"}},"i":0}},"</td>\n <td> bar </td>\n <td>2. ",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"baz </table>"}},"i":1}},"</td>\n </tr>\n <tr>\n <td>abc</td>\n </tr>\n </table>\n </td>\n </tr>\n <tr>\n <td>xyz</td>\n </tr>\n</table>"]}'>
- <tbody><tr>
- <td>
- <table>
- <tbody><tr>
- <td>1. foo </td></tr></tbody></table></td>
- <td> bar </td>
- <td>2. baz </td></tr></tbody></table><span about="#mwt2">
- </span><span about="#mwt2">
- </span><span about="#mwt2">
- </span><span about="#mwt2">abc</span><span about="#mwt2">
- </span><span about="#mwt2">
- </span><span about="#mwt2">
- </span><span about="#mwt2">
- </span><span about="#mwt2">
- </span><span about="#mwt2">
- </span><span about="#mwt2">xyz</span><span about="#mwt2">
- </span><span about="#mwt2">
-</span>
-!!end
-
!! test
Templates: Ugly templates: 3. newline-only template parameter
!! wikitext
@@ -14192,15 +14134,15 @@ parsoid=wt2html,wt2wt,html2html
<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure-inline></p>
!! end
!! test
-Serialize simple image with figure-inline wrapper
+Serialize simple image with span wrapper
!! options
parsoid=html2wt
!! html/parsoid
-<p><figure-inline class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure-inline></p>
+<p><span class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
!! wikitext
[[File:Foobar.jpg]]
!! end
@@ -14213,7 +14155,7 @@ Simple image (using File: namespace, now canonical)
<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure-inline></p>
!! end
!! test
@@ -14311,6 +14253,8 @@ Titles in unlinked images (T23454)
!! html/php
<p><img alt="stuff" src="http://example.com/images/3/3a/Foobar.jpg" title="stuff" width="1941" height="220" />
</p>
+!! html/parsoid
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"stuff"}'><span><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></span></figure-inline></p>
!! end
!! test
@@ -14330,7 +14274,7 @@ Linktrails should not work for images: [[File:Foobar.jpg]]s
<p>Linktrails should not work for images: <a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>s
</p>
!! html/parsoid
-<p>Linktrails should not work for images: <span class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span>s</p>
+<p>Linktrails should not work for images: <figure-inline class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure-inline>s</p>
!! end
!! test
@@ -14376,7 +14320,7 @@ parsoid=wt2html,wt2wt,html2html
<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" width="50" height="6" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/75px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/100px-Foobar.jpg 2x" /></a>
</p>
!! html/parsoid
-<p><span typeof="mw:Image mw:ExpandedAttrs" about="#mwt2" data-parsoid='{"optList":[{"ck":"width","ak":"{{echo|50px}}"}]}' data-mw='{"attribs":[["width",{"html":"&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=&#39;{\"pi\":[[{\"k\":\"1\"}]],\"dsr\":[18,31,null,null]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"50px\"}},\"i\":0}}]}&#39;>50px&lt;/span>"}]]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"6","width":"50"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline typeof="mw:Image mw:ExpandedAttrs" about="#mwt2" data-parsoid='{"optList":[{"ck":"width","ak":"{{echo|50px}}"}]}' data-mw='{"attribs":[["width",{"html":"&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=&#39;{\"pi\":[[{\"k\":\"1\"}]],\"dsr\":[18,31,null,null]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"50px\"}},\"i\":0}}]}&#39;>50px&lt;/span>"}]]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"6","width":"50"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
## Parsoid does not provide editing support for images where templates produce multiple image attributes.
@@ -14407,20 +14351,13 @@ thumbsize=220
123<div class="thumb tright"><div class="thumbinner" style="width:222px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" width="220" height="25" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/330px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/440px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div></div></div></div>456
!! html/php+tidy
-<p>123<a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>456</p>
-<p>123</p>
-<div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div>
-<p>456 123</p>
-<div class="thumb tright">
-<div class="thumbinner" style="width:222px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" width="220" height="25" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/330px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/440px-Foobar.jpg 2x" /></a>
-<div class="thumbcaption">
-<div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>
-</div>
-</div>
-</div>
-<p>456</p>
+<p>123<a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>456
+</p><p>
+123</p><div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div><p>456
+123</p><div class="thumb tright"><div class="thumbinner" style="width:222px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" width="220" height="25" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/330px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/440px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div></div></div></div><p>456
+</p>
!! html/parsoid
-<p>123<span class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span>456</p>
+<p>123<figure-inline class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure-inline>456</p>
<p>123</p><figure class="mw-default-size mw-halign-right" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure><p>456</p>
<p>123</p><figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a></figure><p>456</p>
!! end
@@ -14444,7 +14381,7 @@ Image with multiple widths -- use last
<p><a href="/wiki/File:Foobar.jpg" class="image" title="caption"><img alt="caption" src="http://example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg" width="300" height="34" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/450px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/600px-Foobar.jpg 2x" /></a>
</p>
!! html/parsoid
-<p><span typeof="mw:Image" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="34" width="300"/></a></span></p>
+<p><figure-inline typeof="mw:Image" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="34" width="300"/></a></figure-inline></p>
!! end
!! test
@@ -14461,7 +14398,7 @@ thumbsize=220
</p>
!! html/parsoid
<figure class="mw-default-size mw-halign-left" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>caption</figcaption></figure>
-<p><span class="mw-default-size mw-valign-middle" typeof="mw:Image" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><figure-inline class="mw-default-size mw-valign-middle" typeof="mw:Image" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure-inline></p>
!! end
!! test
@@ -14494,7 +14431,7 @@ parsoid=wt2html,wt2wt,html2html
<a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/177px-Foobar.jpg" width="177" height="20" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/265px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/353px-Foobar.jpg 2x" /></a>
</p>
!! html/parsoid
-<p><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/20px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="2" width="20"/></a></span> <span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/177px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="20" width="177"/></a></span></p>
+<p><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/20px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="2" width="20"/></a></figure-inline> <figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/177px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="20" width="177"/></a></figure-inline></p>
!! end
!! test
@@ -14505,7 +14442,7 @@ Image with link parameter, wiki target
<p><a href="/wiki/Main_Page" title="Main Page"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image"><a href="./Main_Page"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image"><a href="./Main_Page"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure-inline></p>
!! end
# parsoid T51293 (part 1)
@@ -14517,7 +14454,7 @@ Image with link parameter, URL target
<p><a href="http://example.com/" rel="nofollow"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image"><a href="http://example.com/"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image"><a href="http://example.com/"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure-inline></p>
!! end
# parsoid T51293 (part 2)
@@ -14529,7 +14466,7 @@ Image with link parameter, protocol-less URL target
<p><a href="//example.com/" rel="nofollow"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image"><a href="//example.com/"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image"><a href="//example.com/"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure-inline></p>
!! end
!! test
@@ -14565,7 +14502,7 @@ Image with link parameter, wgNoFollowLinks set to false
[[Image:foobar.jpg|link=http://example.com/]]
!! config
wgNoFollowLinks=false
-!! html
+!! html/php
<p><a href="http://example.com/"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! end
@@ -14576,7 +14513,7 @@ Image with link parameter, wgNoFollowDomainExceptions
[[Image:foobar.jpg|link=http://example.com/]]
!! config
wgNoFollowDomainExceptions='example.com'
-!! html
+!! html/php
<p><a href="http://example.com/"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! end
@@ -14601,7 +14538,7 @@ Image with empty link parameter
<p><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" />
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image"><span><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></span></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image"><span><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></span></figure-inline></p>
!! end
!! test
@@ -14612,7 +14549,7 @@ Image with link parameter (wiki target) and unnamed parameter
<p><a href="/wiki/Main_Page" title="Title"><img alt="Title" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"Title"}'><a href="./Main_Page"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"Title"}'><a href="./Main_Page"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure-inline></p>
!! end
!! test
@@ -14623,7 +14560,7 @@ Image with link parameter (URL target) and unnamed parameter
<p><a href="http://example.com/" title="Title" rel="nofollow"><img alt="Title" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"Title"}'><a href="http://example.com/"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"Title"}'><a href="http://example.com/"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure-inline></p>
!! end
!! test
@@ -14746,9 +14683,9 @@ Image with wiki markup in implicit alt
</p><p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="testing bold in alt" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"testing &#39;&#39;&#39;bold&#39;&#39;&#39; in alt"}]}' data-mw='{"caption":"testing &lt;b data-parsoid=&#39;{\"dsr\":[27,37,3,3]}&#39;>bold&lt;/b> in alt"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"Image:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"testing &#39;&#39;&#39;bold&#39;&#39;&#39; in alt"}]}' data-mw='{"caption":"testing &lt;b data-parsoid=&#39;{\"dsr\":[27,37,3,3]}&#39;>bold&lt;/b> in alt"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"Image:Foobar.jpg"}}'/></a></figure-inline></p>
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"alt","ak":"alt=testing &#39;&#39;&#39;bold&#39;&#39;&#39; in alt"}]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img alt="testing bold in alt" resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"alt":"testing bold in alt","resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"alt":"alt=testing &#39;&#39;&#39;bold&#39;&#39;&#39; in alt","resource":"Image:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"alt","ak":"alt=testing &#39;&#39;&#39;bold&#39;&#39;&#39; in alt"}]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img alt="testing bold in alt" resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"alt":"testing bold in alt","resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"alt":"alt=testing &#39;&#39;&#39;bold&#39;&#39;&#39; in alt","resource":"Image:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
!! test
@@ -14759,7 +14696,65 @@ Alt image option should handle most kinds of wikitext without barfing
<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="This is a link and a bold template." src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>This is the image caption</div></div></div>
!! html/parsoid
-<figure class="mw-default-size" typeof="mw:Image/Thumb mw:ExpandedAttrs" about="#mwt2" data-parsoid='{"optList":[{"ck":"thumbnail","ak":"thumb"},{"ck":"caption","ak":"This is the image caption"},{"ck":"alt","ak":"alt=This is a [[link]] and a {{echo|&#39;&#39;bold template&#39;&#39;}}."}]}' data-mw='{"attribs":[["thumbnail",{"html":"thumb"}],["alt",{"html":"alt=This is a &lt;a rel=\"mw:WikiLink\" href=\"./Link\" title=\"Link\" data-parsoid=&#39;{\"stx\":\"simple\",\"a\":{\"href\":\"./Link\"},\"sa\":{\"href\":\"link\"},\"dsr\":[65,73,2,2]}&#39;>link&lt;/a> and a &lt;i about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=&#39;{\"dsr\":[80,106,null,null],\"pi\":[[{\"k\":\"1\"}]]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"&amp;#39;&amp;#39;bold template&amp;#39;&amp;#39;\"}},\"i\":0}}]}&#39;>bold template&lt;/i>."}]]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img alt="This is a link and a bold template." resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220" data-parsoid='{"a":{"alt":"This is a link and a bold template.","resource":"./File:Foobar.jpg","height":"25","width":"220"},"sa":{"alt":"alt=This is a [[link]] and a {{echo|&#39;&#39;bold template&#39;&#39;}}.","resource":"Image:Foobar.jpg"}}'/></a><figcaption>This is the image caption</figcaption></figure>
+<figure class="mw-default-size" typeof="mw:Image/Thumb mw:ExpandedAttrs" about="#mwt2" data-parsoid='{"optList":[{"ck":"thumbnail","ak":"thumb"},{"ck":"caption","ak":"This is the image caption"},{"ck":"alt","ak":"alt=This is a [[link]] and a {{echo|&apos;&apos;bold template&apos;&apos;}}."}]}' data-mw='{"attribs":[["thumbnail",{"html":"thumb"}],["alt",{"html":"alt=This is a &lt;a rel=\"mw:WikiLink\" href=\"./Link\" title=\"Link\" data-parsoid=&apos;{\"stx\":\"simple\",\"a\":{\"href\":\"./Link\"},\"sa\":{\"href\":\"link\"},\"dsr\":[65,73,2,2]}&apos;>link&lt;/a> and a &lt;i about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=&apos;{\"dsr\":[80,106,null,null],\"pi\":[[{\"k\":\"1\"}]]}&apos; data-mw=&apos;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"&amp;apos;&amp;apos;bold template&amp;apos;&amp;apos;\"}},\"i\":0}}]}&#39;>bold template&lt;/i>."}]]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img alt="This is a link and a bold template." resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220" data-parsoid='{"a":{"alt":"This is a link and a bold template.","resource":"./File:Foobar.jpg","height":"25","width":"220"},"sa":{"alt":"alt=This is a [[link]] and a {{echo|&#39;&#39;bold template&#39;&#39;}}.","resource":"Image:Foobar.jpg"}}'/></a><figcaption>This is the image caption</figcaption></figure>
+!! end
+
+!! test
+Image with table with attributes in caption
+!! options
+parsoid=wt2html,html2html
+!! wikitext
+[[File:Foobar.jpg|thumb|
+{| class="123" |
+|- class="456" |
+| ha
+|}
+]]
+!! html/parsoid
+<figure class="mw-default-size" typeof="mw:Image/Thumb" data-parsoid='{"optList":[{"ck":"thumbnail","ak":"thumb"},{"ck":"caption","ak":"\n{| class=\"123\" |\n|- class=\"456\" |\n| ha\n|}\n"}]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{"href":"File:Foobar.jpg"}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"25","width":"220"},"sa":{"resource":"File:Foobar.jpg"}}'/></a><figcaption>
+<table class="123">
+<tbody><tr class="456" data-parsoid='{"startTagSrc":"|-"}'>
+<td> ha</td></tr>
+</tbody></table>
+</figcaption></figure>
+!! end
+
+!! test
+Image with table with rows from templates in caption
+!! wikitext
+[[File:Foobar.jpg|thumb|
+{|
+{{echo|{{!}} hi}}
+|}
+]]
+!! html/parsoid
+<figure class="mw-default-size" typeof="mw:Image/Thumb" data-parsoid='{"optList":[{"ck":"thumbnail","ak":"thumb"},{"ck":"caption","ak":"\n{|\n{{echo|{{!}} hi}}\n|}\n"}]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{"href":"File:Foobar.jpg"}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"25","width":"220"},"sa":{"resource":"File:Foobar.jpg"}}'/></a><figcaption>
+<table>
+<tbody about="#mwt4" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"{{!}} hi"}},"i":0}},"\n"]}'><tr><td> hi</td></tr>
+</tbody></table>
+</figcaption></figure>
+!! end
+
+!! test
+Image with nested tables in caption
+!! wikitext
+[[File:Foobar.jpg|thumb|Foo<br />
+{|
+|
+{|
+|z
+|}
+|}
+]]
+!! html/parsoid
+<figure class="mw-default-size" typeof="mw:Image/Thumb" data-parsoid='{"optList":[{"ck":"thumbnail","ak":"thumb"},{"ck":"caption","ak":"Foo&lt;br/>\n{|\n|\n{|\n|z\n|}\n|}\n"}]}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"25","width":"220"},"sa":{"resource":"File:Foobar.jpg"}}'/></a><figcaption data-parsoid='{"dsr":[null,50,null,null]}'>Foo<br data-parsoid='{"stx":"html","selfClose":true}'/>
+<table>
+<tbody><tr><td>
+<table>
+<tbody><tr><td>z</td></tr>
+</tbody></table></td></tr>
+</tbody></table>
+</figcaption></figure>
!! end
###################
@@ -14783,9 +14778,9 @@ parsoid=wt2html,wt2wt,html2html
</p><p><a href="/wiki/File:Foobar.jpg" class="image" title="caption"><img alt="caption" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a></span></p>
-<p><span class="mw-default-size" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a></span></p>
-<p><span class="mw-default-size" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a></figure-inline></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a></figure-inline></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a></figure-inline></p>
!! end
!! test
@@ -14850,8 +14845,8 @@ parsoid=wt2html,wt2wt,html2html
</p><p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="2000" height="227" class="thumbborder" /></a>
</p>
!! html/parsoid
-<p><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="227" width="2000"/></a></span></p>
-<p><span class="mw-image-border" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="227" width="2000"/></a></span></p>
+<p><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="227" width="2000"/></a></figure-inline></p>
+<p><figure-inline class="mw-image-border" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="227" width="2000"/></a></figure-inline></p>
!! end
!! test
@@ -14867,8 +14862,8 @@ parsoid=wt2html,wt2wt,html2html
</p><p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/1000px-Foobar.jpg" width="1000" height="113" class="thumbborder" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/1500px-Foobar.jpg 1.5x, http://example.com/images/3/3a/Foobar.jpg 2x" /></a>
</p>
!! html/parsoid
-<p><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/1000px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="113" width="1000"/></a></span></p>
-<p><span class="mw-image-border" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/1000px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="113" width="1000"/></a></span></p>
+<p><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/1000px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="113" width="1000"/></a></figure-inline></p>
+<p><figure-inline class="mw-image-border" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/1000px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="113" width="1000"/></a></figure-inline></p>
!! end
!! test
@@ -14911,7 +14906,7 @@ parsoid=wt2html,wt2wt,html2html
<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" width="50" height="6" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/75px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/100px-Foobar.jpg 2x" /></a>
</p>
!! html/parsoid
-<p><span typeof="mw:Image/Frameless"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></span></p>
+<p><figure-inline typeof="mw:Image/Frameless"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></figure-inline></p>
!! end
!! test
@@ -14927,8 +14922,8 @@ parsoid=wt2html,wt2wt,html2html
</p><p><a href="/wiki/File:Foobar.svg" class="image"><img alt="Foobar.svg" src="http://example.com/images/thumb/f/ff/Foobar.svg/2000px-Foobar.svg.png" width="2000" height="1500" srcset="http://example.com/images/thumb/f/ff/Foobar.svg/3000px-Foobar.svg.png 1.5x, http://example.com/images/thumb/f/ff/Foobar.svg/4000px-Foobar.svg.png 2x" /></a>
</p>
!! html/parsoid
-<p><span typeof="mw:Image/Frameless"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
-<p><span typeof="mw:Image/Frameless"><a href="./File:Foobar.svg"><img resource="./File:Foobar.svg" src="//example.com/images/thumb/f/ff/Foobar.svg/2000px-Foobar.svg.png" data-file-width="240" data-file-height="180" data-file-type="drawing" height="1500" width="2000"/></a></span></p>
+<p><figure-inline typeof="mw:Image/Frameless"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure-inline></p>
+<p><figure-inline typeof="mw:Image/Frameless"><a href="./File:Foobar.svg"><img resource="./File:Foobar.svg" src="//example.com/images/thumb/f/ff/Foobar.svg/2000px-Foobar.svg.png" data-file-width="240" data-file-height="180" data-file-type="drawing" height="1500" width="2000"/></a></figure-inline></p>
!! end
!! test
@@ -14986,7 +14981,7 @@ Frameless image caption with a free URL
<p><a href="/wiki/File:Foobar.jpg" class="image" title="http://example.com"><img alt="http://example.com" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"http://example.com"}]}' data-mw='{"caption":"&lt;a rel=\"mw:ExtLink\" href=\"http://example.com\" data-parsoid=&#39;{\"stx\":\"url\",\"dsr\":[18,36,0,0]}&#39;>http://example.com&lt;/a>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"http://example.com"}]}' data-mw='{"caption":"&lt;a rel=\"mw:ExtLink\" href=\"http://example.com\" data-parsoid=&#39;{\"stx\":\"url\",\"dsr\":[18,36,0,0]}&#39;>http://example.com&lt;/a>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
!! test
@@ -14999,7 +14994,7 @@ thumbsize=220
<div class="thumb tright"><div class="thumbinner" style="width:222px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" width="220" height="25" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/330px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/440px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a></div></div></div>
!! html/parsoid
-<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption><a rel="mw:ExtLink" href="http://example.com">http://example.com</a></figcaption></figure>
+<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption><a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a></figcaption></figure>
!! end
!! test
@@ -15013,7 +15008,7 @@ parsoid=wt2html,wt2wt,html2html
<div class="thumb tright"><div class="thumbinner" style="width:222px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Alteration" src="http://example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" width="220" height="25" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/330px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/440px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a></div></div></div>
!! html/parsoid
-<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img alt="Alteration" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption><a rel="mw:ExtLink" href="http://example.com">http://example.com</a></figcaption></figure>
+<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img alt="Alteration" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption><a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a></figcaption></figure>
!! end
!! test
@@ -15046,12 +15041,12 @@ SVG thumbnails with invalid language code
!! options
parsoid=wt2html,wt2wt,html2html
!! wikitext
-[[File:Foobar.svg|thumb|caption|lang=invalid.language.code]]
+[[File:Foobar.svg|thumb|caption|lang=invalid:language:code]]
!! html/php
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.svg" class="image"><img alt="" src="http://example.com/images/thumb/f/ff/Foobar.svg/180px-Foobar.svg.png" width="180" height="135" class="thumbimage" srcset="http://example.com/images/thumb/f/ff/Foobar.svg/270px-Foobar.svg.png 1.5x, http://example.com/images/thumb/f/ff/Foobar.svg/360px-Foobar.svg.png 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.svg" class="internal" title="Enlarge"></a></div>lang=invalid.language.code</div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.svg" class="image"><img alt="" src="http://example.com/images/thumb/f/ff/Foobar.svg/180px-Foobar.svg.png" width="180" height="135" class="thumbimage" srcset="http://example.com/images/thumb/f/ff/Foobar.svg/270px-Foobar.svg.png 1.5x, http://example.com/images/thumb/f/ff/Foobar.svg/360px-Foobar.svg.png 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.svg" class="internal" title="Enlarge"></a></div>lang=invalid:language:code</div></div></div>
!! html/parsoid
-<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.svg"><img resource="./File:Foobar.svg" src="//example.com/images/f/ff/Foobar.svg" data-file-width="240" data-file-height="180" data-file-type="drawing" height="165" width="220"/></a><figcaption>lang=invalid.language.code</figcaption></figure>
+<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.svg"><img resource="./File:Foobar.svg" src="//example.com/images/thumb/f/ff/Foobar.svg/220px-Foobar.svg.png" data-file-width="240" data-file-height="180" data-file-type="drawing" height="165" width="220"/></a><figcaption>lang=invalid:language:code</figcaption></figure>
!! end
!! test
@@ -15070,10 +15065,10 @@ T3887: A RFC with a thumbnail
!! wikitext
[[File:Foobar.jpg|thumb|This is RFC 12354]]
!! html/php
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>This is <a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc12354">RFC 12354</a></div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>This is <a class="external mw-magiclink-rfc" rel="nofollow" href="https://tools.ietf.org/html/rfc12354">RFC 12354</a></div></div></div>
!! html/parsoid
-<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>This is <a href="//tools.ietf.org/html/rfc12354" rel="mw:ExtLink">RFC 12354</a></figcaption></figure>
+<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>This is <a href="https://tools.ietf.org/html/rfc12354" rel="mw:ExtLink" class="external text">RFC 12354</a></figcaption></figure>
!! end
!! test
@@ -15084,7 +15079,7 @@ T3887: A mailto link with a thumbnail
<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>Please <a rel="nofollow" class="external free" href="mailto:nobody@example.com">mailto:nobody@example.com</a></div></div></div>
!! html/parsoid
-<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>Please <a rel="mw:ExtLink" href="mailto:nobody@example.com">mailto:nobody@example.com</a></figcaption></figure>
+<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>Please <a rel="mw:ExtLink" class="external free" href="mailto:nobody@example.com">mailto:nobody@example.com</a></figcaption></figure>
!! end
# Pending resolution to T2368
@@ -15096,7 +15091,7 @@ T2648: Frameless image caption with a link
<p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a link in it"><img alt="text with a link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"text with a [[link]] in it"}]}' data-mw='{"caption":"text with a &lt;a rel=\"mw:WikiLink\" href=\"./Link\" title=\"Link\" data-parsoid=&#39;{\"stx\":\"simple\",\"a\":{\"href\":\"./Link\"},\"sa\":{\"href\":\"link\"},\"dsr\":[30,38,2,2]}&#39;>link&lt;/a> in it"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"text with a [[link]] in it"}]}' data-mw='{"caption":"text with a &lt;a rel=\"mw:WikiLink\" href=\"./Link\" title=\"Link\" data-parsoid=&#39;{\"stx\":\"simple\",\"a\":{\"href\":\"./Link\"},\"sa\":{\"href\":\"link\"},\"dsr\":[30,38,2,2]}&#39;>link&lt;/a> in it"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
!! test
@@ -15107,7 +15102,7 @@ T2648: Frameless image caption with a link (suffix)
<p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a linkfoo in it"><img alt="text with a linkfoo in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"text with a [[link]]foo in it"}]}' data-mw='{"caption":"text with a &lt;a rel=\"mw:WikiLink\" href=\"./Link\" title=\"Link\" data-parsoid=&#39;{\"stx\":\"simple\",\"a\":{\"href\":\"./Link\"},\"sa\":{\"href\":\"link\"},\"dsr\":[30,41,2,5],\"tail\":\"foo\"}&#39;>linkfoo&lt;/a> in it"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"text with a [[link]]foo in it"}]}' data-mw='{"caption":"text with a &lt;a rel=\"mw:WikiLink\" href=\"./Link\" title=\"Link\" data-parsoid=&#39;{\"stx\":\"simple\",\"a\":{\"href\":\"./Link\"},\"sa\":{\"href\":\"link\"},\"dsr\":[30,41,2,5],\"tail\":\"foo\"}&#39;>linkfoo&lt;/a> in it"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
!! test
@@ -15118,7 +15113,7 @@ T2648: Frameless image caption with an interwiki link
<p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a MeatBall:Link in it"><img alt="text with a MeatBall:Link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"text with a [[MeatBall:Link]] in it"}]}' data-mw='{"caption":"text with a &lt;a rel=\"mw:ExtLink\" href=\"http://www.usemod.com/cgi-bin/mb.pl?Link\" title=\"meatball:Link\" data-parsoid=&#39;{\"stx\":\"simple\",\"a\":{\"href\":\"http://www.usemod.com/cgi-bin/mb.pl?Link\"},\"sa\":{\"href\":\"MeatBall:Link\"},\"isIW\":true,\"dsr\":[30,47,2,2]}&#39;>MeatBall:Link&lt;/a> in it"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"text with a [[MeatBall:Link]] in it"}]}' data-mw='{"caption":"text with a &lt;a rel=\"mw:WikiLink/Interwiki\" href=\"http://www.usemod.com/cgi-bin/mb.pl?Link\" title=\"meatball:Link\" data-parsoid=&#39;{\"stx\":\"simple\",\"a\":{\"href\":\"http://www.usemod.com/cgi-bin/mb.pl?Link\"},\"sa\":{\"href\":\"MeatBall:Link\"},\"isIW\":true,\"dsr\":[30,47,2,2]}&#39;>MeatBall:Link&lt;/a> in it"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
!! test
@@ -15129,7 +15124,7 @@ T2648: Frameless image caption with a piped interwiki link
<p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a link in it"><img alt="text with a link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"text with a [[MeatBall:Link|link]] in it"}]}' data-mw='{"caption":"text with a &lt;a rel=\"mw:ExtLink\" href=\"http://www.usemod.com/cgi-bin/mb.pl?Link\" title=\"meatball:Link\" data-parsoid=&#39;{\"stx\":\"piped\",\"a\":{\"href\":\"http://www.usemod.com/cgi-bin/mb.pl?Link\"},\"sa\":{\"href\":\"MeatBall:Link\"},\"isIW\":true,\"dsr\":[30,52,16,2]}&#39;>link&lt;/a> in it"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"text with a [[MeatBall:Link|link]] in it"}]}' data-mw='{"caption":"text with a &lt;a rel=\"mw:WikiLink/Interwiki\" href=\"http://www.usemod.com/cgi-bin/mb.pl?Link\" title=\"meatball:Link\" data-parsoid=&#39;{\"stx\":\"piped\",\"a\":{\"href\":\"http://www.usemod.com/cgi-bin/mb.pl?Link\"},\"sa\":{\"href\":\"MeatBall:Link\"},\"isIW\":true,\"dsr\":[30,52,16,2]}&#39;>link&lt;/a> in it"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
!! test
@@ -15137,7 +15132,7 @@ T107474: Frameless image caption with <nowiki>
!! wikitext
[[File:Foobar.jpg|<nowiki>text with a [[MeatBall:Link|link]] in it</nowiki>]]
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&lt;nowiki>text with a [[MeatBall:Link|link]] in it&lt;/nowiki>"}]}' data-mw='{"caption":"&lt;span typeof=\"mw:Nowiki\" data-parsoid=&#39;{\"dsr\":[18,75,8,9]}&#39;>text with a [[MeatBall:Link|link]] in it&lt;/span>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&lt;nowiki>text with a [[MeatBall:Link|link]] in it&lt;/nowiki>"}]}' data-mw='{"caption":"&lt;span typeof=\"mw:Nowiki\" data-parsoid=&#39;{\"dsr\":[18,75,8,9]}&#39;>text with a [[MeatBall:Link|link]] in it&lt;/span>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
!! test
@@ -15148,7 +15143,7 @@ Escape HTML special chars in image alt text
<p><a href="/wiki/File:Foobar.jpg" class="image" title="&amp; &lt; &gt; &quot;"><img alt="&amp; &lt; &gt; &quot;" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&amp; &lt; > \""}]}' data-mw='{"caption":"&amp;amp; &amp;lt; > \""}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&amp; &lt; > \""}]}' data-mw='{"caption":"&amp;amp; &amp;lt; > \""}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
!! test
@@ -15161,7 +15156,7 @@ language=zh
<p><a href="/wiki/File:Foobar.jpg" class="image" title="&amp; &lt; &gt; &quot;"><img alt="&amp; &lt; &gt; &quot;" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&amp; &lt; > \""}]}' data-mw='{"caption":"&amp;amp; &amp;lt; > \""}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&amp; &lt; > \""}]}' data-mw='{"caption":"&amp;amp; &amp;lt; > \""}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
!! test
@@ -15172,7 +15167,7 @@ Entities in file name and attributes
<p><a href="/index.php?title=Special:Upload&amp;wpDestFile=7%25_solution.gif" class="new" title="File:7% solution.gif">7% solution</a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"bogus","ak":"manualthumb=7%25 solution.gif"},{"ck":"link","ak":"link=7%25 solution"},{"ck":"caption","ak":"[[7%25 solution]]"}]}' data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}],"caption":"&lt;a rel=\"mw:WikiLink\" href=\"./7%25_solution\" title=\"7% solution\" data-parsoid=&#39;{\"stx\":\"simple\",\"a\":{\"href\":\"./7%25_solution\"},\"sa\":{\"href\":\"7%25 solution\"},\"dsr\":[74,91,2,2]}&#39;>7% solution&lt;/a>"}'><a href="./7%25_solution" data-parsoid='{"a":{"href":"./7%25_solution"},"sa":{"href":"link=7%25 solution"}}'><img resource="./File:7%25_solution.gif" src="./Special:FilePath/7%25_solution.gif" height="220" width="220" data-parsoid='{"a":{"resource":"./File:7%25_solution.gif","height":"220","width":"220"},"sa":{"resource":"File:7%25 solution.gif"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"bogus","ak":"manualthumb=7%25 solution.gif"},{"ck":"link","ak":"link=7%25 solution"},{"ck":"caption","ak":"[[7%25 solution]]"}]}' data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}],"caption":"&lt;a rel=\"mw:WikiLink\" href=\"./7%25_solution\" title=\"7% solution\" data-parsoid=&#39;{\"stx\":\"simple\",\"a\":{\"href\":\"./7%25_solution\"},\"sa\":{\"href\":\"7%25 solution\"},\"dsr\":[74,91,2,2]}&#39;>7% solution&lt;/a>"}'><a href="./7%25_solution" data-parsoid='{"a":{"href":"./7%25_solution"},"sa":{"href":"link=7%25 solution"}}'><img resource="./File:7%25_solution.gif" src="./Special:FilePath/7%25_solution.gif" height="220" width="220" data-parsoid='{"a":{"resource":"./File:7%25_solution.gif","height":"220","width":"220"},"sa":{"resource":"File:7%25 solution.gif"}}'/></a></figure-inline></p>
!! end
!! test
@@ -15183,7 +15178,7 @@ T2499: Alt text should have &#1234;, not &amp;1234;
<p><a href="/wiki/File:Foobar.jpg" class="image" title="♀"><img alt="♀" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&amp;#9792;"}]}' data-mw='{"caption":"&lt;span typeof=\"mw:Entity\" data-parsoid=&#39;{\"src\":\"&amp;amp;#9792;\",\"srcContent\":\"♀\",\"dsr\":[18,25,null,null]}&#39;>♀&lt;/span>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&amp;#9792;"}]}' data-mw='{"caption":"&lt;span typeof=\"mw:Entity\" data-parsoid=&#39;{\"src\":\"&amp;amp;#9792;\",\"srcContent\":\"♀\",\"dsr\":[18,25,null,null]}&#39;>♀&lt;/span>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
!! test
@@ -15207,7 +15202,7 @@ Image caption containing another image
<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>This is a caption with another <a href="/wiki/File:Thumb.png" class="image" title="image"><img alt="image" src="http://example.com/images/e/ea/Thumb.png" width="135" height="135" /></a> inside it!</div></div></div>
!! html/parsoid
-<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>This is a caption with another <span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"image"}'><a href="./File:Thumb.png"><img resource="./File:Thumb.png" src="//example.com/images/e/ea/Thumb.png" data-file-width="135" data-file-height="135" data-file-type="bitmap" height="135" width="135"/></a></span> inside it!</figcaption></figure>
+<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>This is a caption with another <figure-inline class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"image"}'><a href="./File:Thumb.png"><img resource="./File:Thumb.png" src="//example.com/images/e/ea/Thumb.png" data-file-width="135" data-file-height="135" data-file-type="bitmap" height="135" width="135"/></a></figure-inline> inside it!</figcaption></figure>
!! end
!! test
@@ -15219,7 +15214,7 @@ Image: caption containing a newline
<p><a href="/wiki/File:Foobar.jpg" class="image" title="This *is some text"><img alt="This *is some text" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"This\n*is some text"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"This\n*is some text"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure-inline></p>
!!end
!!test
@@ -15234,6 +15229,10 @@ Image: caption containing leading space
<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption> bar</figcaption></figure>
!!end
+# html/php output not have newlines after table, td, th, etc. because
+# Linker::makeThumbLink2() replaces the newlines with spaces since
+# the table is inside a caption.
+# FIXME: Verify if that circa 2004 fix is still required.
!! test
Image: caption containing a table
!! options
@@ -15241,21 +15240,21 @@ parsoid=wt2html,wt2wt,html2html
!! wikitext
[[Image:Foobar.jpg|thumb|200px|This is an example image thumbnail caption with a table
{|
-! Foo !! Bar
+!Foo!!Bar
|-
-| Foo1 || Bar1
+|Foo1||Bar1
|}
and some more text.]]
!! html/php
-<div class="thumb tright"><div class="thumbinner" style="width:202px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" width="200" height="23" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/400px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>This is an example image thumbnail caption with a table <table> <tr> <th> Foo </th> <th> Bar </th></tr> <tr> <td> Foo1 </td> <td> Bar1 </td></tr></table> and some more text.</div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:202px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" width="200" height="23" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/400px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>This is an example image thumbnail caption with a table <table> <tr> <th>Foo</th> <th>Bar </th></tr> <tr> <td>Foo1</td> <td>Bar1 </td></tr></table> and some more text.</div></div></div>
!! html/parsoid
<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="23" width="200"/></a><figcaption>This is an example image thumbnail caption with a table
<table>
<tbody>
-<tr><th>Foo </th><th>Bar</th></tr>
+<tr><th>Foo</th><th>Bar</th></tr>
<tr>
-<td>Foo1 </td>
+<td>Foo1</td>
<td>Bar1</td></tr></tbody></table>and some more text.</figcaption></figure>
!! end
@@ -15267,7 +15266,7 @@ T5090: External links other than http: in image captions
<div class="thumb tright"><div class="thumbinner" style="width:202px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" width="200" height="23" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/400px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>This caption has <a rel="nofollow" class="external text" href="irc://example.net">irc</a> and <a rel="nofollow" class="external text" href="https://example.com">Secure</a> ext links in it.</div></div></div>
!! html/parsoid
-<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="23" width="200"/></a><figcaption>This caption has <a rel="mw:ExtLink" href="irc://example.net">irc</a> and <a rel="mw:ExtLink" href="https://example.com">Secure</a> ext links in it.</figcaption></figure>
+<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="23" width="200"/></a><figcaption>This caption has <a rel="mw:ExtLink" class="external text" href="irc://example.net">irc</a> and <a rel="mw:ExtLink" class="external text" href="https://example.com">Secure</a> ext links in it.</figcaption></figure>
!! end
!! test
@@ -15280,7 +15279,7 @@ parsoid=wt2html,wt2wt,html2html
<p><a href="/wiki/File:Foobar.jpg" class="image" title="a"><img alt="a" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" class="b" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size b" typeof="mw:Image" data-mw='{"caption":"a"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><figure-inline class="mw-default-size b" typeof="mw:Image" data-mw='{"caption":"a"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure-inline></p>
!! end
!! test
@@ -15334,7 +15333,7 @@ parsoid=wt2html,wt2wt,html2html
<p><a href="/wiki/File:Foobar.jpg" class="image" title="caption"><img alt="caption" src="http://example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" width="220" height="25" class="extra thumbborder" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/330px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/440px-Foobar.jpg 2x" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size mw-image-border extra" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a></span></p>
+<p><figure-inline class="mw-default-size mw-image-border extra" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a></figure-inline></p>
!! end
# Note that 'right' is the default alignment, despite the misspelled 'righ' below
@@ -15387,7 +15386,7 @@ wgEnableUploads=0
<p><a href="/wiki/File:Foobaz.jpg" title="File:Foobaz.jpg">File:Foobaz.jpg</a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}]}'><a href="./File:Foobaz.jpg"><img resource="./File:Foobaz.jpg" src="./Special:FilePath/Foobaz.jpg" height="220" width="220"/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}]}'><a href="./File:Foobaz.jpg"><img resource="./File:Foobaz.jpg" src="./Special:FilePath/Foobaz.jpg" height="220" width="220"/></a></figure-inline></p>
!! end
# Parsoid-specific testing for images
@@ -15402,7 +15401,7 @@ Parsoid-specific image handling - simple image with size and middle alignment
!! wikitext
[[File:Foobar.jpg|middle|50px]]
!! html/parsoid
-<p><span class="mw-valign-middle" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></span></p>
+<p><figure-inline class="mw-valign-middle" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></figure-inline></p>
!! end
!! test
@@ -15413,7 +15412,7 @@ parsoid=wt2wt,wt2html,html2html
!! wikitext
[[Image:Foobar.jpg|middle|50px]]
!! html/parsoid
-<p><span class="mw-valign-middle" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></span></p>
+<p><figure-inline class="mw-valign-middle" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></figure-inline></p>
!! end
!! test
@@ -15422,7 +15421,7 @@ Parsoid-specific image handling - simple image with size and middle alignment
!! wikitext
[[File:Foobar.jpg|50px|middle]]
!! html/parsoid
-<p><span class="mw-valign-middle" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"width","ak":"50px"},{"ck":"middle","ak":"middle"}]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"6","width":"50"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-valign-middle" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"width","ak":"50px"},{"ck":"middle","ak":"middle"}]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"6","width":"50"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
!! test
@@ -15433,7 +15432,7 @@ parsoid=wt2html,wt2wt,html2html
!! wikitext
[[Image:Foobar.jpg|50px|middle]]
!! html/parsoid
-<p><span class="mw-valign-middle" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></span></p>
+<p><figure-inline class="mw-valign-middle" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></figure-inline></p>
!! end
!! test
@@ -15441,7 +15440,7 @@ Parsoid-specific image handling - simple image with both sizes, a baseline align
!! wikitext
[[File:Foobar.jpg|500x10px|baseline|caption]]
!! html/parsoid
-<p><span class="mw-valign-baseline" typeof="mw:Image" data-mw='{"caption":"caption"}' data-parsoid='{"optList":[{"ck":"width","ak":"500x10px"},{"ck":"baseline","ak":"baseline"},{"ck":"caption","ak":"caption"}],"size":"500x10"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/89px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="10" width="89" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"10","width":"89"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-valign-baseline" typeof="mw:Image" data-mw='{"caption":"caption"}' data-parsoid='{"optList":[{"ck":"width","ak":"500x10px"},{"ck":"baseline","ak":"baseline"},{"ck":"caption","ak":"caption"}],"size":"500x10"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/89px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="10" width="89" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"10","width":"89"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
!! test
@@ -15449,7 +15448,7 @@ Parsoid-specific image handling - simple image with border and size spec
!! wikitext
[[File:Foobar.jpg|50px|border|caption]]
!! html/parsoid
-<p><span class="mw-image-border" typeof="mw:Image" data-mw='{"caption":"caption"}' data-parsoid='{"optList":[{"ck":"width","ak":"50px"},{"ck":"border","ak":"border"},{"ck":"caption","ak":"caption"}]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"6","width":"50"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-image-border" typeof="mw:Image" data-mw='{"caption":"caption"}' data-parsoid='{"optList":[{"ck":"width","ak":"50px"},{"ck":"border","ak":"border"},{"ck":"caption","ak":"caption"}]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"6","width":"50"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
!! test
@@ -15513,7 +15512,7 @@ Parsoid-specific image handling - frameless image with specific size, border, an
!! wikitext
[[File:Foobar.jpg|frameless|442x50px|border|caption]]
!! html/parsoid
-<p><span class="mw-image-border" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}' data-parsoid='{"optList":[{"ck":"frameless","ak":"frameless"},{"ck":"width","ak":"442x50px"},{"ck":"border","ak":"border"},{"ck":"caption","ak":"caption"}],"size":"442x50"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/442px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="50" width="442" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"50","width":"442"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-image-border" typeof="mw:Image/Frameless" data-mw='{"caption":"caption"}' data-parsoid='{"optList":[{"ck":"frameless","ak":"frameless"},{"ck":"width","ak":"442x50px"},{"ck":"border","ak":"border"},{"ck":"caption","ak":"caption"}],"size":"442x50"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/442px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="50" width="442" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"50","width":"442"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
!! test
@@ -15521,7 +15520,7 @@ Parsoid-specific image handling - simple image with a formatted caption
!! wikitext
[[File:Foobar.jpg|<table><tr><td>a</td><td>b</td></tr><tr><td>c</td></tr></table>]]
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&lt;table>&lt;tr>&lt;td>a&lt;/td>&lt;td>b&lt;/td>&lt;/tr>&lt;tr>&lt;td>c&lt;/td>&lt;/tr>&lt;/table>"}]}' data-mw='{"caption":"&lt;table data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[18,81,7,8]}&#39;>&lt;tbody data-parsoid=&#39;{\"dsr\":[25,73,0,0]}&#39;>&lt;tr data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[25,54,4,5]}&#39;>&lt;td data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[29,39,4,5]}&#39;>a&lt;/td>&lt;td data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[39,49,4,5]}&#39;>b&lt;/td>&lt;/tr>&lt;tr data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[54,73,4,5]}&#39;>&lt;td data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[58,68,4,5]}&#39;>c&lt;/td>&lt;/tr>&lt;/tbody>&lt;/table>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&lt;table>&lt;tr>&lt;td>a&lt;/td>&lt;td>b&lt;/td>&lt;/tr>&lt;tr>&lt;td>c&lt;/td>&lt;/tr>&lt;/table>"}]}' data-mw='{"caption":"&lt;table data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[18,81,7,8]}&#39;>&lt;tbody data-parsoid=&#39;{\"dsr\":[25,73,0,0]}&#39;>&lt;tr data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[25,54,4,5]}&#39;>&lt;td data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[29,39,4,5]}&#39;>a&lt;/td>&lt;td data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[39,49,4,5]}&#39;>b&lt;/td>&lt;/tr>&lt;tr data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[54,73,4,5]}&#39;>&lt;td data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[58,68,4,5]}&#39;>c&lt;/td>&lt;/tr>&lt;/tbody>&lt;/table>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
!! test
@@ -15591,7 +15590,7 @@ foo
bar
!! html/parsoid
<p>foo
-<span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"caption"}'><a href="./File:Foobar.svg"><img resource="./File:Foobar.svg" src="//example.com/images/f/ff/Foobar.svg" lang="de" data-file-width="240" data-file-height="180" data-file-type="drawing" height="180" width="240"/></a></span>
+<figure-inline class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"caption"}'><a href="./File:Foobar.svg"><img resource="./File:Foobar.svg" src="//example.com/images/f/ff/Foobar.svg" lang="de" data-file-width="240" data-file-height="180" data-file-type="drawing" height="180" width="240"/></a></figure-inline>
bar</p>
!! end
@@ -15603,7 +15602,7 @@ T93580: 1. Templated <ref> inside block images
<references />
!! html/parsoid
-<figure class="mw-default-size" typeof="mw:Image/Thumb" data-parsoid='{"optList":[{"ck":"thumbnail","ak":"thumb"},{"ck":"caption","ak":"Caption with templated ref: {{echo|&lt;ref>foo&lt;/ref>}}"}]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"25","width":"220"},"sa":{"resource":"File:Foobar.jpg"}}'/></a><figcaption>Caption with templated ref: <span about="#mwt5" class="mw-ref" id="cite_ref-1" rel="dc:references" typeof="mw:Transclusion mw:Extension/ref" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;ref>foo&lt;/ref>"}},"i":0}}]}'><a href="./Main_Page#cite_note-1" style="counter-reset: mw-Ref 1;"><span class="mw-reflink-text">[1]</span></a></span></figcaption></figure>
+<figure class="mw-default-size" typeof="mw:Image/Thumb" data-parsoid='{"optList":[{"ck":"thumbnail","ak":"thumb"},{"ck":"caption","ak":"Caption with templated ref: {{echo|&lt;ref>foo&lt;/ref>}}"}]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"25","width":"220"},"sa":{"resource":"File:Foobar.jpg"}}'/></a><figcaption>Caption with templated ref: <sup about="#mwt5" class="mw-ref" id="cite_ref-1" rel="dc:references" typeof="mw:Transclusion mw:Extension/ref" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;ref>foo&lt;/ref>"}},"i":0}}]}'><a href="./Main_Page#cite_note-1" style="counter-reset: mw-Ref 1;"><span class="mw-reflink-text">[1]</span></a></sup></figcaption></figure>
<ol class="mw-references references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><a href="./Main_Page#cite_ref-1" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-1" class="mw-reference-text" data-parsoid="{}">foo</span></li></ol>
!! end
@@ -15615,9 +15614,9 @@ T93580: 2. <ref> inside inline images
<references />
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"Undisplayed caption in inline image with ref: &lt;ref>foo&lt;/ref>"}]}' data-mw='{"caption":"Undisplayed caption in inline image with ref: &lt;span about=\"#mwt2\" class=\"mw-ref\" id=\"cite_ref-1\" rel=\"dc:references\" typeof=\"mw:Extension/ref\" data-parsoid=&#39;{\"dsr\":[64,78,5,6]}&#39; data-mw=&#39;{\"name\":\"ref\",\"body\":{\"id\":\"mw-reference-text-cite_note-1\"},\"attrs\":{}}&#39;>&lt;a href=\"./Main_Page#cite_note-1\" style=\"counter-reset: mw-Ref 1;\" data-parsoid=\"{}\">&lt;span class=\"mw-reflink-text\" data-parsoid=\"{}\">[1]&lt;/span>&lt;/a>&lt;/span>&lt;meta typeof=\"mw:Extension/ref/Marker\" about=\"#mwt2\" data-parsoid=&#39;{\"group\":\"\",\"name\":\"\",\"content\":\"foo\",\"hasRefInRef\":false,\"dsr\":[64,78,5,6]}&#39;/>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"Undisplayed caption in inline image with ref: &lt;ref>foo&lt;/ref>"}]}' data-mw='{"caption":"Undisplayed caption in inline image with ref: &lt;sup about=\"#mwt2\" class=\"mw-ref\" id=\"cite_ref-1\" rel=\"dc:references\" typeof=\"mw:Extension/ref\" data-parsoid=&#39;{\"dsr\":[64,78,5,6]}&#39; data-mw=&#39;{\"name\":\"ref\",\"body\":{\"id\":\"mw-reference-text-cite_note-1\"},\"attrs\":{}}&#39;>&lt;a href=\"./Main_Page#cite_note-1\" style=\"counter-reset: mw-Ref 1;\" data-parsoid=\"{}\">&lt;span class=\"mw-reflink-text\" data-parsoid=\"{}\">[1]&lt;/span>&lt;/a>&lt;/sup>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{"href":"File:Foobar.jpg"}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
-<ol class="mw-references references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><a href="./Main_Page#cite_ref-1" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-1" class="mw-reference-text" data-parsoid="{}">foo</span></li></ol>
+<ol class="mw-references references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><a href="./Main_Page#cite_ref-1" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo</span></li></ol>
!! end
!! test
@@ -15627,9 +15626,9 @@ T93580: 3. Templated <ref> inside inline images
<references />
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"Undisplayed caption in inline image with ref: {{echo|&lt;ref>{{echo|foo}}&lt;/ref>}}"}]}' data-mw='{"caption":"Undisplayed caption in inline image with ref: &lt;span about=\"#mwt2\" class=\"mw-ref\" id=\"cite_ref-1\" rel=\"dc:references\" typeof=\"mw:Transclusion mw:Extension/ref\" data-parsoid=&#39;{\"dsr\":[64,96,null,null],\"pi\":[[{\"k\":\"1\"}]]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"&amp;lt;ref>{{echo|foo}}&amp;lt;/ref>\"}},\"i\":0}}]}&#39;>&lt;a href=\"./Main_Page#cite_note-1\" style=\"counter-reset: mw-Ref 1;\" data-parsoid=\"{}\">&lt;span class=\"mw-reflink-text\" data-parsoid=\"{}\">[1]&lt;/span>&lt;/a>&lt;/span>&lt;meta typeof=\"mw:Transclusion mw:Extension/ref/Marker\" about=\"#mwt2\" data-parsoid=&#39;{\"group\":\"\",\"name\":\"\",\"content\":\"foo\",\"hasRefInRef\":false,\"dsr\":[64,96,null,null],\"pi\":[[{\"k\":\"1\"}]]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"&amp;lt;ref>{{echo|foo}}&amp;lt;/ref>\"}},\"i\":0}}]}&#39;/>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"Undisplayed caption in inline image with ref: {{echo|&lt;ref>{{echo|foo}}&lt;/ref>}}"}]}' data-mw='{"caption":"Undisplayed caption in inline image with ref: &lt;sup about=\"#mwt2\" class=\"mw-ref\" id=\"cite_ref-1\" rel=\"dc:references\" typeof=\"mw:Transclusion mw:Extension/ref\" data-parsoid=&#39;{\"dsr\":[64,96,null,null],\"pi\":[[{\"k\":\"1\"}]]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"&amp;lt;ref>{{echo|foo}}&amp;lt;/ref>\"}},\"i\":0}}]}&#39;>&lt;a href=\"./Main_Page#cite_note-1\" style=\"counter-reset: mw-Ref 1;\" data-parsoid=\"{}\">&lt;span class=\"mw-reflink-text\" data-parsoid=\"{}\">[1]&lt;/span>&lt;/a>&lt;/sup>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{"href":"File:Foobar.jpg"}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
-<ol class="mw-references references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><a href="./Main_Page#cite_ref-1" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-1" class="mw-reference-text" data-parsoid="{}">foo</span></li></ol>
+<ol class="mw-references references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><a href="./Main_Page#cite_ref-1" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo</span></li></ol>
!! end
###
@@ -15768,7 +15767,7 @@ Render invalid page names as plain text (T53090)
[[.]]
[[..]]
[[foo././bar]]
-[[foo<a rel="mw:ExtLink" href="http://example.com"></a>xyz]]</p>
+[[foo<a rel="mw:ExtLink" class="external autonumber" href="http://example.com"></a>xyz]]</p>
<p>[[<span typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"./../foo"}},"i":0}}]}'>./../foo</span>|bar]]
[[<span typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo/."}},"i":0}}]}'>foo/.</span>|bar]]
@@ -15981,7 +15980,8 @@ Bar
<p>Foo <link rel="mw:PageProp/Category" href="./Category:Baz"/> Bar</p>
<p>Foo <link rel="mw:PageProp/Category" href="./Category:Baz"/> Bar</p>
<p>Foo <link rel="mw:PageProp/Category" href="./Category:Baz"/> Bar</p>
-<p>Foo <link rel="mw:PageProp/Category" href="./Category:Baz"/> <link rel="mw:PageProp/Category" href="./Category:Baz"/> <link rel="mw:PageProp/Category" href="./Category:Baz"/> Bar <link rel="mw:PageProp/Category" href="./Category:Baz"/> <link rel="mw:PageProp/Category" href="./Category:Baz"/> <link rel="mw:PageProp/Category" href="./Category:Baz"/> <link rel="mw:PageProp/Category" href="./Category:Baz"/> <link rel="mw:PageProp/Category" href="./Category:Baz" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[[Category:Baz]]"}},"i":0}}]}'/></p>
+<p>Foo <link rel="mw:PageProp/Category" href="./Category:Baz"/> <link rel="mw:PageProp/Category" href="./Category:Baz"/> <link rel="mw:PageProp/Category" href="./Category:Baz"/> Bar</p>
+<link rel="mw:PageProp/Category" href="./Category:Baz"/> <link rel="mw:PageProp/Category" href="./Category:Baz"/> <link rel="mw:PageProp/Category" href="./Category:Baz"/> <link rel="mw:PageProp/Category" href="./Category:Baz"/> <link rel="mw:PageProp/Category" href="./Category:Baz" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[[Category:Baz]]"}},"i":0}}]}'/>
<link rel="mw:PageProp/Category" href="./Category:Baz"/>
!! end
@@ -16143,6 +16143,68 @@ parsoid=wt2html
!! end
!! test
+9. Categories and newlines: should behave properly with linkprefix (T87753)
+!! options
+language=ar
+!! wikitext
+foo bar
+foo bar
+[[تصنيف:Foo]]
+[[تصنيف:Bar]]
+!! html/php
+<p>foo bar
+foo bar
+</p>
+!! html/parsoid
+<p>foo bar
+foo bar</p>
+<link rel="mw:PageProp/Category" href="./تصنيف:Foo"/>
+<link rel="mw:PageProp/Category" href="./تصنيف:Bar"/>
+!! end
+
+!! test
+10. No regressions on internal links following category (T174639)
+!! options
+parsoid=wt2html,html2html
+!! wikitext
+[[Category:Foo]]<div>a
+
+[[Foo]]</div>
+!! html/php
+<div>a
+<a href="/wiki/Foo" title="Foo">Foo</a></div>
+
+!! html/parsoid
+<link rel="mw:PageProp/Category" href="./Category:Foo"/><div>a
+
+<a rel="mw:WikiLink" href="./Foo" title="Foo">Foo</a></div>
+!! end
+
+# Note that Parsoid differs slightly from PHP due to T175421
+!! test
+11. Special case where only newlines separate links (T175416)
+!! options
+parsoid=wt2html,html2html
+!! wikitext
+[[Category:Foo]]
+
+[[Foo]][[es:Alimento]]
+
+[[Foo]]
+!! html/php
+<p><br />
+<a href="/wiki/Foo" title="Foo">Foo</a>
+</p><p><a href="/wiki/Foo" title="Foo">Foo</a>
+</p>
+!! html/parsoid
+<link rel="mw:PageProp/Category" href="./Category:Foo"/>
+
+<p><a rel="mw:WikiLink" href="./Foo" title="Foo">Foo</a></p><link rel="mw:PageProp/Language" href="http://es.wikipedia.org/wiki/Alimento"/>
+
+<p><a rel="mw:WikiLink" href="./Foo" title="Foo">Foo</a></p>
+!! end
+
+!! test
Category links with multiple namespaces
!! wikitext
[[Category:Project:Foo]]
@@ -16190,6 +16252,20 @@ x[[Category:Foo]]y
!! end
!! test
+Link prefix/suffixes aren't applied to language links
+!! options
+parsoid=wt2html
+language=is
+!! wikitext
+x[[es:Foo]]y
+!! html/php
+<p>xy
+</p>
+!! html/parsoid
+<p>x<link rel="mw:PageProp/Language" href="http://es.wikipedia.org/wiki/Foo" data-parsoid=""/>y</p>
+!! end
+
+!! test
Parsoid: Serialize link to file page with colon escape
!! options
parsoid
@@ -16288,7 +16364,7 @@ es:1 fr:1
!! test
Basic section headings
!! wikitext
-== Headline 1 ==
+==Headline 1==
Some text
==Headline 2==
@@ -16310,16 +16386,16 @@ Blah blah
!! test
Section headings with TOC
!! wikitext
-== Headline 1 ==
-=== Subheadline 1 ===
-===== Skipping a level =====
-====== Skipping a level ======
+==Headline 1==
+===Subheadline 1===
+=====Skipping a level=====
+======Skipping a level======
-== Headline 2 ==
+==Headline 2==
Some text
===Another headline===
!! html
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#Headline_1"><span class="tocnumber">1</span> <span class="toctext">Headline 1</span></a>
<ul>
@@ -16357,12 +16433,12 @@ Some text
TOC anchors don't collide
!! wikitext
__FORCETOC__
-== Headline 2 ==
-== Headline ==
-== Headline 2 ==
-== Headline ==
+==Headline 2==
+==Headline==
+==Headline 2==
+==Headline==
!! html/php
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#Headline_2"><span class="tocnumber">1</span> <span class="toctext">Headline 2</span></a></li>
<li class="toclevel-1 tocsection-2"><a href="#Headline"><span class="tocnumber">2</span> <span class="toctext">Headline</span></a></li>
@@ -16379,21 +16455,24 @@ __FORCETOC__
!! end
# perl -e 'print "="x$_," Level $_ heading","="x$_,"\n" for 1..10'
+# Parsoid html2wt direction adds <nowiki> for level 7 and up.
!! test
Handling of sections up to level 6 and beyond
+!! options
+parsoid=wt2html
!! wikitext
-= Level 1 Heading=
-== Level 2 Heading==
-=== Level 3 Heading===
-==== Level 4 Heading====
-===== Level 5 Heading=====
-====== Level 6 Heading======
-======= Level 7 Heading=======
-======== Level 8 Heading========
-========= Level 9 Heading=========
-========== Level 10 Heading==========
-!! html
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+=Level 1 Heading=
+==Level 2 Heading==
+===Level 3 Heading===
+====Level 4 Heading====
+=====Level 5 Heading=====
+======Level 6 Heading======
+=======Level 7 Heading=======
+========Level 8 Heading========
+=========Level 9 Heading=========
+==========Level 10 Heading==========
+!! html/php
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#Level_1_Heading"><span class="tocnumber">1</span> <span class="toctext">Level 1 Heading</span></a>
<ul>
@@ -16406,10 +16485,10 @@ Handling of sections up to level 6 and beyond
<li class="toclevel-5 tocsection-5"><a href="#Level_5_Heading"><span class="tocnumber">1.1.1.1.1</span> <span class="toctext">Level 5 Heading</span></a>
<ul>
<li class="toclevel-6 tocsection-6"><a href="#Level_6_Heading"><span class="tocnumber">1.1.1.1.1.1</span> <span class="toctext">Level 6 Heading</span></a></li>
-<li class="toclevel-6 tocsection-7"><a href="#.3D_Level_7_Heading.3D"><span class="tocnumber">1.1.1.1.1.2</span> <span class="toctext">= Level 7 Heading=</span></a></li>
-<li class="toclevel-6 tocsection-8"><a href="#.3D.3D_Level_8_Heading.3D.3D"><span class="tocnumber">1.1.1.1.1.3</span> <span class="toctext">== Level 8 Heading==</span></a></li>
-<li class="toclevel-6 tocsection-9"><a href="#.3D.3D.3D_Level_9_Heading.3D.3D.3D"><span class="tocnumber">1.1.1.1.1.4</span> <span class="toctext">=== Level 9 Heading===</span></a></li>
-<li class="toclevel-6 tocsection-10"><a href="#.3D.3D.3D.3D_Level_10_Heading.3D.3D.3D.3D"><span class="tocnumber">1.1.1.1.1.5</span> <span class="toctext">==== Level 10 Heading====</span></a></li>
+<li class="toclevel-6 tocsection-7"><a href="#.3DLevel_7_Heading.3D"><span class="tocnumber">1.1.1.1.1.2</span> <span class="toctext">=Level 7 Heading=</span></a></li>
+<li class="toclevel-6 tocsection-8"><a href="#.3D.3DLevel_8_Heading.3D.3D"><span class="tocnumber">1.1.1.1.1.3</span> <span class="toctext">==Level 8 Heading==</span></a></li>
+<li class="toclevel-6 tocsection-9"><a href="#.3D.3D.3DLevel_9_Heading.3D.3D.3D"><span class="tocnumber">1.1.1.1.1.4</span> <span class="toctext">===Level 9 Heading===</span></a></li>
+<li class="toclevel-6 tocsection-10"><a href="#.3D.3D.3D.3DLevel_10_Heading.3D.3D.3D.3D"><span class="tocnumber">1.1.1.1.1.5</span> <span class="toctext">====Level 10 Heading====</span></a></li>
</ul>
</li>
</ul>
@@ -16429,24 +16508,35 @@ Handling of sections up to level 6 and beyond
<h4><span class="mw-headline" id="Level_4_Heading">Level 4 Heading</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: Level 4 Heading">edit</a><span class="mw-editsection-bracket">]</span></span></h4>
<h5><span class="mw-headline" id="Level_5_Heading">Level 5 Heading</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: Level 5 Heading">edit</a><span class="mw-editsection-bracket">]</span></span></h5>
<h6><span class="mw-headline" id="Level_6_Heading">Level 6 Heading</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=6" title="Edit section: Level 6 Heading">edit</a><span class="mw-editsection-bracket">]</span></span></h6>
-<h6><span class="mw-headline" id=".3D_Level_7_Heading.3D">= Level 7 Heading=</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=7" title="Edit section: = Level 7 Heading=">edit</a><span class="mw-editsection-bracket">]</span></span></h6>
-<h6><span class="mw-headline" id=".3D.3D_Level_8_Heading.3D.3D">== Level 8 Heading==</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=8" title="Edit section: == Level 8 Heading==">edit</a><span class="mw-editsection-bracket">]</span></span></h6>
-<h6><span class="mw-headline" id=".3D.3D.3D_Level_9_Heading.3D.3D.3D">=== Level 9 Heading===</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=9" title="Edit section: === Level 9 Heading===">edit</a><span class="mw-editsection-bracket">]</span></span></h6>
-<h6><span class="mw-headline" id=".3D.3D.3D.3D_Level_10_Heading.3D.3D.3D.3D">==== Level 10 Heading====</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=10" title="Edit section: ==== Level 10 Heading====">edit</a><span class="mw-editsection-bracket">]</span></span></h6>
+<h6><span class="mw-headline" id=".3DLevel_7_Heading.3D">=Level 7 Heading=</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=7" title="Edit section: =Level 7 Heading=">edit</a><span class="mw-editsection-bracket">]</span></span></h6>
+<h6><span class="mw-headline" id=".3D.3DLevel_8_Heading.3D.3D">==Level 8 Heading==</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=8" title="Edit section: ==Level 8 Heading==">edit</a><span class="mw-editsection-bracket">]</span></span></h6>
+<h6><span class="mw-headline" id=".3D.3D.3DLevel_9_Heading.3D.3D.3D">===Level 9 Heading===</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=9" title="Edit section: ===Level 9 Heading===">edit</a><span class="mw-editsection-bracket">]</span></span></h6>
+<h6><span class="mw-headline" id=".3D.3D.3D.3DLevel_10_Heading.3D.3D.3D.3D">====Level 10 Heading====</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=10" title="Edit section: ====Level 10 Heading====">edit</a><span class="mw-editsection-bracket">]</span></span></h6>
+!! html/parsoid
+<h1 id="Level_1_Heading" data-parsoid='{}'>Level 1 Heading</h1>
+<h2 id="Level_2_Heading" data-parsoid='{}'>Level 2 Heading</h2>
+<h3 id="Level_3_Heading" data-parsoid='{}'>Level 3 Heading</h3>
+<h4 id="Level_4_Heading" data-parsoid='{}'>Level 4 Heading</h4>
+<h5 id="Level_5_Heading" data-parsoid='{}'>Level 5 Heading</h5>
+<h6 id="Level_6_Heading" data-parsoid='{}'>Level 6 Heading</h6>
+<h6 id="=Level_7_Heading=" data-parsoid='{}'><span id=".3DLevel_7_Heading.3D" typeof="mw:FallbackId"></span>=Level 7 Heading=</h6>
+<h6 id="==Level_8_Heading==" data-parsoid='{}'><span id=".3D.3DLevel_8_Heading.3D.3D" typeof="mw:FallbackId"></span>==Level 8 Heading==</h6>
+<h6 id="===Level_9_Heading===" data-parsoid='{}'><span id=".3D.3D.3DLevel_9_Heading.3D.3D.3D" typeof="mw:FallbackId"></span>===Level 9 Heading===</h6>
+<h6 id="====Level_10_Heading====" data-parsoid='{}'><span id=".3D.3D.3D.3DLevel_10_Heading.3D.3D.3D.3D" typeof="mw:FallbackId"></span>====Level 10 Heading====</h6>
!! end
!! test
TOC regression (T11764)
!! wikitext
-== title 1 ==
-=== title 1.1 ===
-==== title 1.1.1 ====
-=== title 1.2 ===
-== title 2 ==
-=== title 2.1 ===
+==title 1==
+===title 1.1===
+====title 1.1.1====
+===title 1.2===
+==title 2==
+===title 2.1===
!! html
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#title_1"><span class="tocnumber">1</span> <span class="toctext">title 1</span></a>
<ul>
@@ -16481,7 +16571,7 @@ TOC for heading containing <span id="..."></span> (T96153)
__FORCETOC__
==<span id="old-anchor"></span>New title==
!! html/php
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#New_title"><span class="tocnumber">1</span> <span class="toctext">New title</span></a></li>
</ul>
@@ -16496,14 +16586,14 @@ TOC with wgMaxTocLevel=3 (T8204)
!! options
wgMaxTocLevel=3
!! wikitext
-== title 1 ==
-=== title 1.1 ===
-==== title 1.1.1 ====
-=== title 1.2 ===
-== title 2 ==
-=== title 2.1 ===
+==title 1==
+===title 1.1===
+====title 1.1.1====
+===title 1.2===
+==title 2==
+===title 2.1===
!! html
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#title_1"><span class="tocnumber">1</span> <span class="toctext">title 1</span></a>
<ul>
@@ -16539,7 +16629,7 @@ wgMaxTocLevel=3
====Section 1.1.1.1====
==Section 2==
!! html
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a>
<ul>
@@ -16562,8 +16652,8 @@ wgMaxTocLevel=3
!! test
Resolving duplicate section names
!! wikitext
-== Foo bar ==
-== Foo bar ==
+==Foo bar==
+==Foo bar==
!! html
<h2><span class="mw-headline" id="Foo_bar">Foo bar</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Foo bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<h2><span class="mw-headline" id="Foo_bar_2">Foo bar</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Foo bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
@@ -16573,8 +16663,8 @@ Resolving duplicate section names
!! test
Resolving duplicate section names with differing case (T12721)
!! wikitext
-== Foo bar ==
-== Foo Bar ==
+==Foo bar==
+==Foo Bar==
!! html
<h2><span class="mw-headline" id="Foo_bar">Foo bar</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Foo bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<h2><span class="mw-headline" id="Foo_Bar_2">Foo Bar</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Foo Bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
@@ -16628,11 +16718,11 @@ Link inside a section heading
TOC regression (T14077)
!! wikitext
__TOC__
-== title 1 ==
-=== title 1.1 ===
-== title 2 ==
+==title 1==
+===title 1.1===
+==title 2==
!! html
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#title_1"><span class="tocnumber">1</span> <span class="toctext">title 1</span></a>
<ul>
@@ -16657,24 +16747,33 @@ http://example.com [[File:Foobar.jpg]]
<p><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a> <a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com">http://example.com</a> <span class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a> <figure-inline class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure-inline></p>
!!end
+# Parsoid doesn't wt2wt this cleanly because it adds <nowiki>s.
!! test
Short headings with trailing space should match behavior of Parser::doHeadings (T21910)
+!! options
+parsoid=wt2html,html2html
!! wikitext
===
The line above must have a trailing space!
=== <!--
--> <!-- -->
But just in case it doesn't...
-!! html
+!! html/php
<h1><span class="mw-headline" id=".3D">=</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: =">edit</a><span class="mw-editsection-bracket">]</span></span></h1>
<p>The line above must have a trailing space!
</p>
<h1><span class="mw-headline" id=".3D_2">=</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: =">edit</a><span class="mw-editsection-bracket">]</span></span></h1>
<p>But just in case it doesn't...
</p>
+!! html/parsoid
+<h1 id="="><span id=".3D" typeof="mw:FallbackId"></span>=</h1>
+<p>The line above must have a trailing space!</p>
+<h1 id="=_2"><span id=".3D_2" typeof="mw:FallbackId"></span>=</h1> <!--
+--> <!-- -->
+<p>But just in case it doesn't...</p>
!! end
!! test
@@ -16682,24 +16781,24 @@ Header with special characters (T27462)
!! wikitext
The tooltips shall not show entities to the user (ie. be double escaped)
-== text > text ==
+==text > text==
section 1
-== text < text ==
+==text < text==
section 2
-== text & text ==
+==text & text==
section 3
-== text ' text ==
+==text ' text==
section 4
-== text " text ==
+==text " text==
section 5
-!! html
+!! html/php
<p>The tooltips shall not show entities to the user (ie. be double escaped)
</p>
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#text_.3E_text"><span class="tocnumber">1</span> <span class="toctext">text &gt; text</span></a></li>
<li class="toclevel-1 tocsection-2"><a href="#text_.3C_text"><span class="tocnumber">2</span> <span class="toctext">text &lt; text</span></a></li>
@@ -16724,6 +16823,23 @@ section 5
<h2><span class="mw-headline" id="text_.22_text">text " text</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: text &quot; text">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>section 5
</p>
+!! html/parsoid
+<p>The tooltips shall not show entities to the user (ie. be double escaped)</p>
+
+<h2 id="text_>_text"><span id="text_.3E_text" typeof="mw:FallbackId"></span>text > text</h2>
+<p>section 1</p>
+
+<h2 id="text_&lt;_text"><span id="text_.3C_text" typeof="mw:FallbackId"></span>text &lt; text</h2>
+<p>section 2</p>
+
+<h2 id="text_&amp;_text"><span id="text_.26_text" typeof="mw:FallbackId"></span>text &amp; text</h2>
+<p>section 3</p>
+
+<h2 id="text_'_text"><span id="text_.27_text" typeof="mw:FallbackId"></span>text ' text</h2>
+<p>section 4</p>
+
+<h2 id='text_"_text'><span id="text_.22_text" typeof="mw:FallbackId"></span>text " text</h2>
+<p>section 5</p>
!! end
!! test
@@ -16731,22 +16847,22 @@ Header with space, plus and underscore as entity
!! wikitext
Id should not contain + for spaces
-== Space between Text ==
+==Space between Text==
section 1
-== Space-Entity&#32;between&#32;Text ==
+==Space-Entity&#32;between&#32;Text==
section 2
-== Plus+between+Text ==
+==Plus+between+Text==
section 3
-== Plus-Entity&#43;between&#43;Text ==
+==Plus-Entity&#43;between&#43;Text==
section 4
-== Underscore_between_Text ==
+==Underscore_between_Text==
section 5
-== Underscore-Entity&#95;between&#95;Text ==
+==Underscore-Entity&#95;between&#95;Text==
section 6
[[#Space between Text]]
@@ -16755,10 +16871,10 @@ section 6
[[#Plus-Entity&#43;between&#43;Text]]
[[#Underscore_between_Text]]
[[#Underscore-Entity&#95;between&#95;Text]]
-!! html
+!! html/php
<p>Id should not contain + for spaces
</p>
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#Space_between_Text"><span class="tocnumber">1</span> <span class="toctext">Space between Text</span></a></li>
<li class="toclevel-1 tocsection-2"><a href="#Space-Entity_between_Text"><span class="tocnumber">2</span> <span class="toctext">Space-Entity&#32;between&#32;Text</span></a></li>
@@ -16793,18 +16909,48 @@ section 6
<a href="#Underscore_between_Text">#Underscore_between_Text</a>
<a href="#Underscore-Entity_between_Text">#Underscore-Entity&#95;between&#95;Text</a>
</p>
+!! html/parsoid
+<p>Id should not contain + for spaces</p>
+
+<h2 id="Space_between_Text">Space between Text</h2>
+<p>section 1</p>
+
+<h2 id="Space-Entity_between_Text">Space-Entity<span typeof="mw:Entity" data-parsoid='{"src":"&amp;#32;","srcContent":" "}'> </span>between<span typeof="mw:Entity" data-parsoid='{"src":"&amp;#32;","srcContent":" "}'> </span>Text</h2>
+<p>section 2</p>
+
+<h2 id="Plus+between+Text"><span id="Plus.2Bbetween.2BText" typeof="mw:FallbackId"></span>Plus+between+Text</h2>
+<p>section 3</p>
+
+<h2 id="Plus-Entity+between+Text"><span id="Plus-Entity.2Bbetween.2BText" typeof="mw:FallbackId"></span>Plus-Entity<span typeof="mw:Entity" data-parsoid='{"src":"&amp;#43;","srcContent":"+"}'>+</span>between<span typeof="mw:Entity" data-parsoid='{"src":"&amp;#43;","srcContent":"+"}'>+</span>Text</h2>
+<p>section 4</p>
+
+<h2 id="Underscore_between_Text">Underscore_between_Text</h2>
+<p>section 5</p>
+
+<h2 id="Underscore-Entity_between_Text">Underscore-Entity<span typeof="mw:Entity" data-parsoid='{"src":"&amp;#95;","srcContent":"_"}'>_</span>between<span typeof="mw:Entity" data-parsoid='{"src":"&amp;#95;","srcContent":"_"}'>_</span>Text</h2>
+<p>section 6</p>
+
+<p><a rel="mw:WikiLink" href="./Main_Page#Space_between_Text" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#Space_between_Text"},"sa":{"href":"#Space between Text"}}'>#Space between Text</a>
+<a rel="mw:WikiLink" href="./Main_Page#Space-Entity_between_Text" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#Space-Entity_between_Text"},"sa":{"href":"#Space-Entity&amp;#32;between&amp;#32;Text"}}'>#Space-Entity between Text</a>
+<a rel="mw:WikiLink" href="./Main_Page#Plus+between+Text" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#Plus+between+Text"},"sa":{"href":"#Plus+between+Text"}}'>#Plus+between+Text</a>
+<a rel="mw:WikiLink" href="./Main_Page#Plus-Entity+between+Text" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#Plus-Entity+between+Text"},"sa":{"href":"#Plus-Entity&amp;#43;between&amp;#43;Text"}}'>#Plus-Entity+between+Text</a>
+<a rel="mw:WikiLink" href="./Main_Page#Underscore_between_Text" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#Underscore_between_Text"},"sa":{"href":"#Underscore_between_Text"}}'>#Underscore_between_Text</a>
+<a rel="mw:WikiLink" href="./Main_Page#Underscore-Entity_between_Text" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#Underscore-Entity_between_Text"},"sa":{"href":"#Underscore-Entity&amp;#95;between&amp;#95;Text"}}'>#Underscore-Entity_between_Text</a></p>
!! end
+# Parsoid html2wt disabled because it adds padding spaces around =
!! test
Headers with excess '=' characters
(Are similar tests necessary beyond the 1st level?)
+!! options
+parsoid=wt2html,wt2wt,html2html
!! wikitext
=foo==
==foo=
=''italic'' heading==
==''italic'' heading=
-!! html
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+!! html/php
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#foo.3D"><span class="tocnumber">1</span> <span class="toctext">foo=</span></a></li>
<li class="toclevel-1 tocsection-2"><a href="#.3Dfoo"><span class="tocnumber">2</span> <span class="toctext">=foo</span></a></li>
@@ -16818,6 +16964,11 @@ Headers with excess '=' characters
<h1><span class="mw-headline" id="italic_heading.3D"><i>italic</i> heading=</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: italic heading=">edit</a><span class="mw-editsection-bracket">]</span></span></h1>
<h1><span class="mw-headline" id=".3Ditalic_heading">=<i>italic</i> heading</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: =italic heading">edit</a><span class="mw-editsection-bracket">]</span></span></h1>
+!! html/parsoid
+<h1 id="foo="><span id="foo.3D" typeof="mw:FallbackId"></span>foo=</h1>
+<h1 id="=foo"><span id=".3Dfoo" typeof="mw:FallbackId"></span>=foo</h1>
+<h1 id="italic_heading="><span id="italic_heading.3D" typeof="mw:FallbackId"></span><i>italic</i> heading=</h1>
+<h1 id="=italic_heading"><span id=".3Ditalic_heading" typeof="mw:FallbackId"></span>=<i>italic</i> heading</h1>
!! end
!! test
@@ -16825,16 +16976,16 @@ HTML headers vs TOC (T25393)
(__NOEDITSECTION__ for clearer output, doesn't matter here)
!! wikitext
<h1>Header 1</h1>
-== Header 1.1 ==
-== Header 1.2 ==
+==Header 1.1==
+==Header 1.2==
<h1>Header 2
</h1>
-== Header 2.1 ==
-== Header 2.2 ==
+==Header 2.1==
+==Header 2.2==
__NOEDITSECTION__
-!! html
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+!! html/php
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1"><a href="#Header_1"><span class="tocnumber">1</span> <span class="toctext">Header 1</span></a>
<ul>
@@ -16854,10 +17005,21 @@ __NOEDITSECTION__
<h1><span class="mw-headline" id="Header_1">Header 1</span></h1>
<h2><span class="mw-headline" id="Header_1.1">Header 1.1</span></h2>
<h2><span class="mw-headline" id="Header_1.2">Header 1.2</span></h2>
-<h1><span class="mw-headline" id="Header_2">Header 2</span></h1>
+<h1><span class="mw-headline" id="Header_2">Header 2
+</span></h1>
<h2><span class="mw-headline" id="Header_2.1">Header 2.1</span></h2>
<h2><span class="mw-headline" id="Header_2.2">Header 2.2</span></h2>
+!! html/parsoid
+<h1 id="Header_1" data-parsoid='{"stx":"html"}'>Header 1</h1>
+<h2 id="Header_1.1" data-parsoid='{}'>Header 1.1</h2>
+<h2 id="Header_1.2" data-parsoid='{}'>Header 1.2</h2>
+
+<h1 id="Header_2" data-parsoid='{"stx":"html"}'>Header 2
+</h1>
+<h2 id="Header_2.1" data-parsoid='{}'>Header 2.1</h2>
+<h2 id="Header_2.2" data-parsoid='{}'>Header 2.2</h2>
+<meta property="mw:PageProp/noeditsection"/>
!! end
!! test
@@ -16870,11 +17032,17 @@ parsoid=wt2html,wt2wt
==baz==<!--
c2
c3-->
-!! html
-<h2><span class="mw-headline" id="foo">foo</span></h2>
-<h2><span class="mw-headline" id="bar">bar</span></h2>
-<h2><span class="mw-headline" id="baz">baz</span></h2>
+!! html/php
+<h2><span class="mw-headline" id="foo">foo</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: foo">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<h2><span class="mw-headline" id="bar">bar</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<h2><span class="mw-headline" id="baz">baz</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: baz">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+!! html/parsoid
+<h2 id="foo">foo</h2><!---->
+<h2 id="bar">bar</h2><!--c1-->
+<h2 id="baz">baz</h2><!--
+c2
+c3-->
!! end
!! test
@@ -16885,7 +17053,7 @@ http://example.com[[File:Foobar.jpg]]
<p><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com">http://example.com</a><span class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a><figure-inline class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></figure-inline></p>
!!end
!! test
@@ -16981,15 +17149,31 @@ parsoid=wt2html,html2html
!! test
div with multiple empty attribute values
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
!! options
parsoid=wt2html,html2html
!! wikitext
<div id= title=>HTML rocks</div>
!! html/php
-<div id="title.3D">HTML rocks</div>
+<div id="title=">HTML rocks</div>
+
+!! html/parsoid
+<div id="title=" data-parsoid='{"stx":"html"}'>HTML rocks</div>
+!! end
+# FIXME Parsoid doesn't actually match PHP here.
+# Probably we should use the synthetic <foo /> or <indicator>
+# extensions for this test, which are enabled when running parser tests.
+!! test
+Extension tag in attribute value
+!! wikitext
+<span title="<translate>123</translate>">ok</span>
+!! html/php+disabled
+<p>&lt;span title="&lt;translate&gt;123&lt;/translate&gt;"&gt;ok&lt;/span&gt;
+</p>
!! html/parsoid
-<div id="title.3D" data-parsoid='{"stx":"html"}'>HTML rocks</div>
+<p><span title="123" about="#mwt4" typeof="mw:ExpandedAttrs" data-parsoid='{"stx":"html","a":{"title":"123"},"sa":{"title":"&lt;translate>123&lt;/translate>"}}' data-mw='{"attribs":[[{"txt":"title"},{"html":"&lt;translate typeof=\"mw:Extension/translate\" about=\"#mwt3\" data-parsoid=&apos;{\"dsr\":[13,39,2,2]}&apos; data-mw=&apos;{\"name\":\"translate\",\"attrs\":{},\"body\":{\"extsrc\":\"123\"}}&apos;>123&lt;/translate>"}]]}'>ok</span></p>
!! end
!! test
@@ -16998,17 +17182,17 @@ table with multiple empty attribute values
parsoid=wt2html,html2html
!! wikitext
{| title= id=
-| hi
+|hi
|}
!! html/php
<table title="id=">
<tr>
-<td> hi
+<td>hi
</td></tr></table>
!! html/parsoid
<table title="id=">
-<tbody><tr><td> hi</td></tr>
+<tbody><tr><td>hi</td></tr>
</tbody></table>
!! end
@@ -17049,12 +17233,12 @@ HTML multiple attributes correction
Table multiple attributes correction
!! wikitext
{|
-!+ class="error" class="awesome"| status
+!+ class="error" class="awesome"|status
|}
!! html
<table>
<tr>
-<th class="awesome"> status
+<th class="awesome">status
</th></tr></table>
!!end
@@ -17099,11 +17283,9 @@ Remember AT&T?
text with character entity: eacute
!! wikitext
I always thought &eacute; was a cute letter.
-!! html
+!! html+tidy
<p>I always thought &#233; was a cute letter.
</p>
-!! html+tidy
-<p>I always thought é was a cute letter.</p>
!! end
!! test
@@ -17181,12 +17363,11 @@ Ensure that HTML adoption agency algorithm is properly implemented.
!! end
# This was T43545 in the PHP parser.
-# Note that tidy doesn't handle this correctly.
!! test
Nesting of <kbd>
!! wikitext
<kbd>X<kbd>Y</kbd>Z</kbd>
-!! html
+!! html+tidy
<p><kbd>X<kbd>Y</kbd>Z</kbd>
</p>
!! end
@@ -17195,22 +17376,20 @@ Nesting of <kbd>
# Note that there are some other nestable tags (b, i, etc) which are
# not covered; see T53081 for discussion.
-# Note that tidy doesn't handle this correctly.
!! test
Nesting of <em>
!! wikitext
<em>X<em>Y</em>Z</em>
-!! html
+!! html+tidy
<p><em>X<em>Y</em>Z</em>
</p>
!! end
-# Note that tidy doesn't handle this correctly.
!! test
Nesting of <strong>
!! wikitext
<strong>X<strong>Y</strong>Z</strong>
-!! html
+!! html+tidy
<p><strong>X<strong>Y</strong>Z</strong>
</p>
!! end
@@ -17220,10 +17399,10 @@ Nesting of <q>
!! wikitext
<q>X<q>Y</q>Z</q>
!! html+tidy
-<p><q>X<q>Y</q>Z</q></p>
+<p><q>X<q>Y</q>Z</q>
+</p>
!! end
-# Note that tidy doesn't handle this correctly.
!! test
Nesting of <ruby>
!! wikitext
@@ -17233,7 +17412,6 @@ Nesting of <ruby>
</p>
!! end
-# Note that tidy doesn't handle this correctly.
!! test
Nesting of <bdo>
!! wikitext
@@ -17278,6 +17456,7 @@ Media link with text
# FIXME: this is still bad HTML tag nesting
# FIXME: doBlockLevels won't wrap this in a paragraph because it contains a div
+# Parsoid & Remex fix the p-wrapping since they operate on the DOM.
!! test
Media link with nasty text
!! wikitext
@@ -17285,9 +17464,8 @@ Media link with nasty text
!! html/php
<a href="http://example.com/images/3/3a/Foobar.jpg" class="internal" title="Foobar.jpg">Safe Link&lt;div style="display:none"&gt;" onmouseover="alert(document.cookie)" onfoo="&lt;/div&gt;</a>
-!! html+php/tidy
-<p><a href="http://example.com/images/3/3a/Foobar.jpg" class="internal" title="Foobar.jpg">Safe Link</a></p>
-<div style="display:none">" onmouseover="alert(document.cookie)" onfoo="</div>
+!! html/php+tidy
+<p><a href="http://example.com/images/3/3a/Foobar.jpg" class="internal" title="Foobar.jpg">Safe Link</a></p><a href="http://example.com/images/3/3a/Foobar.jpg" class="internal" title="Foobar.jpg"><div style="display:none">" onmouseover="alert(document.cookie)" onfoo="</div></a>
!! html/parsoid
<p><a rel="mw:MediaLink" href="//example.com/images/3/3a/Foobar.jpg" title="Foobar.jpg" data-parsoid='{"autoInsertedEnd":true}'>Safe Link</a></p><div style="display:none" data-parsoid='{"stx":"html"}'><a rel="mw:MediaLink" href="//example.com/images/3/3a/Foobar.jpg" title="Foobar.jpg" data-parsoid='{"autoInsertedEnd":true,"autoInsertedStart":true}'>" onmouseover="alert(document.cookie)" onfoo="</a></div>
@@ -17315,7 +17493,7 @@ Image link to nonexistent file (T3850 - good)
<p><a href="/index.php?title=Special:Upload&amp;wpDestFile=No_such.jpg" class="new" title="File:No such.jpg">File:No such.jpg</a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}]}'><a href="./File:No_such.jpg"><img resource="./File:No_such.jpg" src="./Special:FilePath/No_such.jpg" height="220" width="220"/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}]}'><a href="./File:No_such.jpg"><img resource="./File:No_such.jpg" src="./Special:FilePath/No_such.jpg" height="220" width="220"/></a></figure-inline></p>
!! end
!! test
@@ -17567,9 +17745,11 @@ T4304: HTML attribute safety (unsafe breakout parameter 2; 2309)
T4304: HTML attribute safety (link)
!! wikitext
<div title="[[Main Page]]"></div>
-!! html
-<div title="&#91;&#91;Main Page]]"></div>
+!! html/php
+<div title="&#91;&#91;Main Page&#93;&#93;"></div>
+!! html/parsoid
+<div title="[[Main Page]]"></div>
!! end
!! test
@@ -17630,9 +17810,11 @@ T4304: HTML attribute safety (web link)
T4304: HTML attribute safety (named web link)
!! wikitext
<div title="[http://example.com/ link]"></div>
-!! html
-<div title="&#91;http&#58;//example.com/ link]"></div>
+!! html/php
+<div title="&#91;http&#58;//example.com/ link&#93;"></div>
+!! html/parsoid
+<div title="[http://example.com/ link]"></div>
!! end
!! test
@@ -17807,12 +17989,12 @@ MSIE 6 CSS safety test: Repetition markers (T57332)
Table attribute legitimate extension
!! wikitext
{|
-!+ style="<nowiki>color:blue</nowiki>"| status
+!+ style="<nowiki>color:blue</nowiki>"|status
|}
!! html
<table>
<tr>
-<th style="color:blue"> status
+<th style="color:blue">status
</th></tr></table>
!!end
@@ -17821,12 +18003,12 @@ Table attribute legitimate extension
Table attribute safety
!! wikitext
{|
-!+ style="<nowiki>border-width:expression(0+alert(document.cookie))</nowiki>"| status
+!+ style="<nowiki>border-width:expression(0+alert(document.cookie))</nowiki>"|status
|}
!! html
<table>
<tr>
-<th style="/* insecure input */"> status
+<th style="/* insecure input */">status
</th></tr></table>
!! end
@@ -17887,11 +18069,12 @@ Expansion of multi-line templates in attribute values (T8255 sanity check 2)
!! end
!! test
-Tags which are hidden from Tidy cannot pass through the Sanitizer
+Tags which are hidden from tidiers cannot pass through the Sanitizer
!! wikitext
<mw:toc><script>alert();</script></mw:toc>
!! html+tidy
-<p>&lt;mw:toc&gt;&lt;script&gt;alert();&lt;/script&gt;&lt;/mw:toc&gt;</p>
+<p>&lt;mw:toc&gt;&lt;script&gt;alert();&lt;/script&gt;&lt;/mw:toc&gt;
+</p>
!! end
###
@@ -18135,6 +18318,27 @@ this is a '''test'''
<p>this is a <b>test</b></p>
!! end
+!! test
+Parser hook: horizontal rule inside extension tag that outputs <pre>
+!! wikitext
+<tag>
+Hello
+<hr/>
+Goodbye
+</tag>
+!! html/php
+<pre>
+'
+Hello
+<hr/>
+Goodbye
+'
+array (
+)
+</pre>
+
+!! end
+
###
### (see tests/parser/parserTestsParserHook.php for the <statictag> extension)
###
@@ -18196,18 +18400,16 @@ Nested template calls
### Sanitizer
###
-# HTML+Tidy effectively strips out the empty tags completely
-# But since Parsoid doesn't it wraps the <s></s> tags in p-tags
-# which Tidy would have done for the PHP parser had there been content inside it.
+# Remex wraps empty tag runs with p-tags.
+# Parsoid strips them out during p-wrapping.
!! test
Sanitizer: Closing of open tags
!! wikitext
<s></s><table></table>
-!! html
-<s></s><table></table>
-
-!! html/parsoid
+!! html/php+tidy
<p><s></s></p><table></table>
+!! html/parsoid
+<s></s><table></table>
!! end
!! test
@@ -18226,6 +18428,8 @@ parsoid=wt2html
!! wikitext
</s>
!! html/php+tidy
+<p class="mw-empty-elt">
+</p>
!! html/parsoid
!! end
@@ -18235,21 +18439,33 @@ Sanitizer: Closing of closed but not open table tags
parsoid=wt2html
!! wikitext
Table not started</td></tr></table>
-!! html/php+tidy
-<p>Table not started</p>
-!! html/parsoid
-<p>Table not started</p>
+!! html+tidy
+<p>Table not started
+</p>
!! end
!! test
Sanitizer: Escaping of spaces, multibyte characters, colons & other stuff in id=""
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
!! wikitext
<span id="æ: v">byte</span>[[#æ: v|backlink]]
!! html/php
-<p><span id=".C3.A6:_v">byte</span><a href="#.C3.A6:_v">backlink</a>
+<p><span id="æ:_v">byte</span><a href="#æ:_v">backlink</a>
</p>
!! html/parsoid
-<p><span id=".C3.A6:_v" data-parsoid='{"stx":"html","a":{"id":".C3.A6:_v"},"sa":{"id":"æ: v"}}'>byte</span><a rel="mw:WikiLink" href="./Main_Page#.C3.A6:_v" data-parsoid='{"stx":"piped","a":{"href":"./Main_Page#.C3.A6:_v"},"sa":{"href":"#æ: v"}}'>backlink</a></p>
+<p><span id="æ:_v" data-parsoid='{"stx":"html","a":{"id":"æ:_v"},"sa":{"id":"æ: v"}}'>byte</span><a rel="mw:WikiLink" href="./Main_Page#æ:_v" data-parsoid='{"stx":"piped","a":{"href":"./Main_Page#æ:_v"},"sa":{"href":"#æ: v"}}'>backlink</a></p>
+!! end
+
+!! test
+Sanitizer: Escaping of spaces, multibyte characters, colons & other stuff in id="" (legacy)
+!! config
+wgFragmentMode=[ 'legacy' ]
+!! wikitext
+<span id="æ: v">byte</span>[[#æ: v|backlink]]
+!! html/php
+<p><span id=".C3.A6:_v">byte</span><a href="#.C3.A6:_v">backlink</a>
+</p>
!! end
# In HTML5, the restrictions are that id must contain at least one character,
@@ -18313,6 +18529,37 @@ parsoid=wt2html,wt2wt
!! end
!! test
+Sanitizer: Avoid unnecessary percent encoded characters in interwiki links
+!! wikitext
+[[meatball:Soft"Security]]
+!! html/php
+<p><a href="http://www.usemod.com/cgi-bin/mb.pl?Soft%22Security" class="extiw" title="meatball:Soft&quot;Security">meatball:Soft"Security</a>
+</p>
+!! html/parsoid
+<p><a rel="mw:WikiLink/Interwiki" href='http://www.usemod.com/cgi-bin/mb.pl?Soft"Security' title='meatball:Soft"Security'>meatball:Soft"Security</a></p>
+!! end
+
+!! test
+Sanitizer: angle brackets are invalid, even in interwiki links (T182338)
+!! wikitext
+[[meatball:Foo<Bar]]
+[[meatball:Foo>Bar]]
+[[meatball:Foo&lt;bar]]
+[[meatball:Foo&gt;bar]]
+!! html/php
+<p>[[meatball:Foo&lt;Bar]]
+[[meatball:Foo&gt;Bar]]
+[[meatball:Foo&lt;bar]]
+[[meatball:Foo&gt;bar]]
+</p>
+!! html/parsoid
+<p>[[meatball:Foo&lt;Bar]]
+[[meatball:Foo>Bar]]
+[[meatball:Foo<span typeof="mw:Entity" data-parsoid='{"src":"&amp;lt;","srcContent":"&lt;"}'>&lt;</span>bar]]
+[[meatball:Foo<span typeof="mw:Entity" data-parsoid='{"src":"&amp;gt;","srcContent":">"}'>></span>bar]]</p>
+!! end
+
+!! test
Language converter: output gets cut off unexpectedly (T7757)
!! options
language=zh
@@ -18345,10 +18592,14 @@ language=sr variant=sr-el
-{H|foAjrjvi=>sr-el:" onload="alert(1)" data-foo="}-
[[File:Foobar.jpg|alt=-{}-foAjrjvi-{}-]]
-!! html
+!! html/php
<p>
</p><p><a href="/wiki/%D0%94%D0%B0%D1%82%D0%BE%D1%82%D0%B5%D0%BA%D0%B0:Foobar.jpg" class="image"><img alt="&quot; onload=&quot;alert(1)&quot; data-foo=&quot;" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
+!! html/parsoid
+<p><meta typeof="mw:LanguageVariant" data-mw-variant='{"add":true,"oneway":[{"f":"foAjrjvi","l":"sr-el","t":"\" onload=\"alert(1)\" data-foo=\""}]}'/></p>
+
+<p><figure-inline class="mw-default-size" typeof="mw:Image"><a href="./Датотека:Foobar.jpg"><img alt="foAjrjvi" resource="./Датотека:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"alt":"foAjrjvi","resource":"./Датотека:Foobar.jpg","height":"220","width":"1941"},"sa":{"alt":"alt=-{}-foAjrjvi-{}-","resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
!! test
@@ -18361,10 +18612,6 @@ Self closed html pairs (T7487)
<div><font id="bug2"></font>In div text</div>
!! end
-#
-#
-#
-
!! test
Punctuation: nbsp before exclamation
!! wikitext
@@ -18422,9 +18669,9 @@ HTML bullet list, unclosed tags (T7497)
</ul>
!! html/php+tidy
<ul>
-<li>One</li>
-<li>Two</li>
-</ul>
+<li>One
+</li><li>Two
+</li></ul>
!! html/parsoid
<ul data-parsoid='{"stx":"html"}'>
<li data-parsoid='{"stx":"html","autoInsertedEnd":true}'>One</li>
@@ -18464,9 +18711,9 @@ HTML ordered list, unclosed tags (T7497)
</ol>
!! html/php+tidy
<ol>
-<li>One</li>
-<li>Two</li>
-</ol>
+<li>One
+</li><li>Two
+</li></ol>
!! html/parsoid
<ol data-parsoid='{"stx":"html"}'>
<li data-parsoid='{"stx":"html","autoInsertedEnd":true}'>One</li>
@@ -18521,30 +18768,15 @@ HTML nested bullet list, open tags (T7497)
<li>Sub-two
</ul>
</ul>
-!! html/php+tidy
-<ul>
-<li>One</li>
-<li>Two:
-<ul>
-<li>Sub-one</li>
-<li>Sub-two</li>
-</ul>
-</li>
-</ul>
-!! html/parsoid
+!! html+tidy
<ul>
<li>One
-</li>
-<li>Two:
+</li><li>Two:
<ul>
<li>Sub-one
-</li>
-<li>Sub-two
-</li>
-</ul>
-</li>
-</ul>
-
+</li><li>Sub-two
+</li></ul>
+</li></ul>
!! end
!! test
@@ -18641,11 +18873,11 @@ mailto:inline@mail.tld
</p><p><a rel="nofollow" class="external free" href="mailto:inline@mail.tld">mailto:inline@mail.tld</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://first/"></a> <a rel="mw:ExtLink" href="http://second"></a> <a rel="mw:ExtLink" href="ftp://ftp"></a></p>
-<p><a rel="mw:ExtLink" href="ftp://inlineftp">ftp://inlineftp</a></p>
-<p><a rel="mw:ExtLink" href="mailto:enclosed@mail.tld">With target</a></p>
-<p><a rel="mw:ExtLink" href="mailto:enclosed@mail.tld"></a></p>
-<p><a rel="mw:ExtLink" href="mailto:inline@mail.tld">mailto:inline@mail.tld</a></p>
+<p><a rel="mw:ExtLink" class="external autonumber" href="http://first/"></a> <a rel="mw:ExtLink" class="external autonumber" href="http://second"></a> <a rel="mw:ExtLink" class="external autonumber" href="ftp://ftp"></a></p>
+<p><a rel="mw:ExtLink" class="external free" href="ftp://inlineftp">ftp://inlineftp</a></p>
+<p><a rel="mw:ExtLink" class="external text" href="mailto:enclosed@mail.tld">With target</a></p>
+<p><a rel="mw:ExtLink" class="external autonumber" href="mailto:enclosed@mail.tld"></a></p>
+<p><a rel="mw:ExtLink" class="external free" href="mailto:inline@mail.tld">mailto:inline@mail.tld</a></p>
!! end
@@ -18669,32 +18901,33 @@ Fuzz testing: Parser13
!! end
+# Note that Parsoid output differs from the PHP parser here: the PHP
+# parser breaks the URL for the magic word, while in Parsoid the URL
+# production takes precedence.
!! test
Fuzz testing: Parser14
!! wikitext
-== onmouseover= ==
+==onmouseover===
http://__TOC__
-!! html
+!! html/php
<h2><span class="mw-headline" id="onmouseover.3D">onmouseover=</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: onmouseover=">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-http://<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+http://<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#onmouseover.3D"><span class="tocnumber">1</span> <span class="toctext">onmouseover=</span></a></li>
</ul>
</div>
-!! html+tidy
-<h2><span class="mw-headline" id="onmouseover.3D">onmouseover=</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: onmouseover=">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<p>http://</p>
-<div id="toc" class="toc">
-<div class="toctitle">
-<h2>Contents</h2>
-</div>
+!! html/php+tidy
+<h2><span class="mw-headline" id="onmouseover.3D">onmouseover=</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: onmouseover=">edit</a><span class="mw-editsection-bracket">]</span></span></h2><p>
+http://</p><div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#onmouseover.3D"><span class="tocnumber">1</span> <span class="toctext">onmouseover=</span></a></li>
</ul>
</div>
-<p></p>
+!! html/parsoid
+<h2 id="onmouseover="><span id="onmouseover.3D" typeof="mw:FallbackId"></span>onmouseover=</h2>
+<p><a rel="mw:ExtLink" class="external free" href="http://__TOC__" data-parsoid='{"stx":"url"}'>http://__TOC__</a></p>
!! end
!! test
@@ -18718,48 +18951,39 @@ parsoid=wt2html,html2html
</tr>
</table>
!! html/parsoid
-<h2>a</h2>
+<h2 id="a">a</h2>
<table style="__TOC__"></table>
!! end
# Known to produce bogus xml (extra </td>)
+# Don't add the html/php section since it generates broken HTML
!! test
Fuzz testing: Parser16
!! wikitext
{|
!https://||||||
-!! html
+!! html+tidy
<table>
-<tr>
+<tbody><tr>
<th>https://</th>
<th></th>
<th></th>
<th>
-</td>
-</tr>
-</table>
-!! html+tidy
-<table>
-<tr>
-<th>https://</th>
-<th></th>
-<th></th>
-<th></th>
-</tr>
-</table>
+</th></tr>
+</tbody></table>
!! end
!! test
Fuzz testing: Parser21
!! wikitext
{|
-! irc://{{ftp://a" onmouseover="alert('hello world');"
+!irc://{{ftp://a" onmouseover="alert('hello world');"
|
!! html
<table>
<tr>
-<th> <a rel="nofollow" class="external free" href="irc://{{ftp://a">irc://{{ftp://a</a>" onmouseover="alert('hello world');"
+<th><a rel="nofollow" class="external free" href="irc://{{ftp://a">irc://{{ftp://a</a>" onmouseover="alert('hello world');"
</th>
<td>
</td>
@@ -18852,7 +19076,7 @@ http://example.com <nowiki>junk</nowiki>
<p><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a> junk
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com" data-parsoid='{"stx":"url"}'>http://example.com</a> <span typeof="mw:Nowiki">junk</span></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com" data-parsoid='{"stx":"url"}'>http://example.com</a> <span typeof="mw:Nowiki">junk</span></p>
!! end
!!test
@@ -18863,7 +19087,7 @@ http://example.com<nowiki>junk</nowiki>
<p><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a>junk
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com" data-parsoid='{"stx":"url"}'>http://example.com</a><span typeof="mw:Nowiki">junk</span></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com" data-parsoid='{"stx":"url"}'>http://example.com</a><span typeof="mw:Nowiki">junk</span></p>
!! end
!! test
@@ -18874,12 +19098,9 @@ http://example.com<pre>junk</pre>
<a rel="nofollow" class="external free" href="http://example.com">http://example.com</a><pre>junk</pre>
!! html/php+tidy
-<p><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a></p>
-<pre>
-junk
-</pre>
+<p><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a></p><pre>junk</pre>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://example.com" data-parsoid='{"stx":"url"}'>http://example.com</a></p><pre typeof="mw:Extension/pre" about="#mwt2" data-mw='{"name":"pre","attrs":{},"body":{"extsrc":"junk"}}'>junk</pre>
+<p><a rel="mw:ExtLink" class="external free" href="http://example.com" data-parsoid='{"stx":"url"}'>http://example.com</a></p><pre typeof="mw:Extension/pre" about="#mwt2" data-mw='{"name":"pre","attrs":{},"body":{"extsrc":"junk"}}'>junk</pre>
!! end
!! test
@@ -18893,15 +19114,45 @@ Fuzz testing: image with bogus manual thumbnail
<figure class="mw-default-size" typeof="mw:Error mw:Image/Thumb" data-parsoid='{"optList":[{"ck":"manualthumb","ak":"thumbnail= "}]}' data-mw='{"errors":[{"key":"apierror-invalidtitle","message":"Invalid thumbnail title.","params":{"name":""}}],"thumb":""}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{"href":"Image:foobar.jpg"}}'><img resource="./File:Foobar.jpg" src="./Special:FilePath/Foobar.jpg" height="220" width="220" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"220"},"sa":{"resource":"Image:foobar.jpg"}}'/></a></figure>
!! end
+# Parsoid will emit the newline literally in wt2wt; see next test case.
!! test
Fuzz testing: encoded newline in generated HTML replacements (T8577)
+!! options
+parsoid=wt2html
!! wikitext
<pre dir="&#10;"></pre>
!! html/php
<pre dir="&#10;"></pre>
!! html/parsoid
-<pre typeof="mw:Extension/pre" about="#mwt2" dir="&amp;#10;" data-mw='{"name":"pre","attrs":{"dir":"&amp;#10;"},"body":{"extsrc":""}}'></pre>
+<pre typeof="mw:Extension/pre" about="#mwt2" dir="
+" data-mw='{"name":"pre","attrs":{"dir":"\n"},"body":{"extsrc":""}}'></pre>
+!! end
+
+!! test
+Fuzz testing: encoded newline in generated HTML replacements, html2wt (T8577)
+!! options
+parsoid=html2wt
+!! html/parsoid
+<pre typeof="mw:Extension/pre" about="#mwt2" dir="
+" data-mw='{"name":"pre","attrs":{"dir":"\n"},"body":{"extsrc":""}}'></pre>
+!! wikitext
+<pre dir="
+"></pre>
+!! html/php
+<pre dir=""></pre>
+
+!! end
+
+!! test
+Templates in extension attributes are not expanded
+!! wikitext
+<pre dir="{{echo|ltr}}"></pre>
+!! html/php
+<pre dir="{{echo|ltr}}"></pre>
+
+!! html/parsoid
+<pre typeof="mw:Extension/pre" about="#mwt2" dir="{{echo|ltr}}" data-mw='{"name":"pre","attrs":{"dir":"{{echo|ltr}}"},"body":{"extsrc":""}}'></pre>
!! end
!! test
@@ -19903,23 +20154,23 @@ xxx
!! test
Handling of &#x0A; in URLs
!! wikitext
-** irc://&#x0A;a
+*irc://&#x0A;a
!! html/php
-<ul><li><ul><li> <a rel="nofollow" class="external free" href="irc://%0Aa">irc://%0Aa</a></li></ul></li></ul>
+<ul><li><a rel="nofollow" class="external free" href="irc://%0Aa">irc://%0Aa</a></li></ul>
!! html/parsoid
-<ul><li><ul><li> <a rel="mw:ExtLink" href="irc://%0Aa" data-parsoid='{"stx":"url","a":{"href":"irc://%0Aa"},"sa":{"href":"irc://&amp;#x0A;a"}}'>irc://%0Aa</a></li></ul></li></ul>
+<ul><li><a rel="mw:ExtLink" class="external free" href="irc://%0Aa" data-parsoid='{"stx":"url","a":{"href":"irc://%0Aa"},"sa":{"href":"irc://&amp;#x0A;a"}}'>irc://%0Aa</a></li></ul>
!! end
!! test
Handling of %0A in URLs
!! wikitext
-** irc://%0Aa
+*irc://%0Aa
!! html/php
-<ul><li><ul><li> <a rel="nofollow" class="external free" href="irc://%0Aa">irc://%0Aa</a></li></ul></li></ul>
+<ul><li><a rel="nofollow" class="external free" href="irc://%0Aa">irc://%0Aa</a></li></ul>
!! html/parsoid
-<ul><li><ul><li> <a rel="mw:ExtLink" href="irc://%0Aa">irc://%0Aa</a></li></ul></li></ul>
+<ul><li><a rel="mw:ExtLink" class="external free" href="irc://%0Aa">irc://%0Aa</a></li></ul>
!! end
# The PHP parser strips the empty tags out for giggles; parsoid doesn't.
@@ -19931,7 +20182,7 @@ parsoid=wt2html
'''''
!! html/php
!! html/parsoid
-<p><b><i></i></b></p>
+<b><i></i></b>
!! end
# same html as previous, but wikitext adjusted to match parsoid html2wt
@@ -19989,51 +20240,51 @@ Say the magic word
!! options
title=[[Parser test]]
!! wikitext
-* {{PAGENAME}}
-* {{PAGENAMEE}}
-* {{FULLPAGENAME}}
-* {{FULLPAGENAMEE}}
-* {{BASEPAGENAME}}
-* {{BASEPAGENAMEE}}
-* {{SUBPAGENAME}}
-* {{SUBPAGENAMEE}}
-* {{ROOTPAGENAME}}
-* {{ROOTPAGENAMEE}}
-* {{TALKPAGENAME}}
-* {{TALKPAGENAMEE}}
-* {{SUBJECTPAGENAME}}
-* {{SUBJECTPAGENAMEE}}
-* {{NAMESPACEE}}
-* {{NAMESPACE}}
-* {{NAMESPACENUMBER}}
-* {{TALKSPACE}}
-* {{TALKSPACEE}}
-* {{SUBJECTSPACE}}
-* {{SUBJECTSPACEE}}
-* {{Dynamic|{{NUMBEROFUSERS}}|{{NUMBEROFPAGES}}|{{CURRENTVERSION}}|{{CONTENTLANGUAGE}}|{{DIRECTIONMARK}}|{{CURRENTTIMESTAMP}}|{{NUMBEROFARTICLES}}}}
-!! html
-<ul><li> Parser test</li>
-<li> Parser_test</li>
-<li> Parser test</li>
-<li> Parser_test</li>
-<li> Parser test</li>
-<li> Parser_test</li>
-<li> Parser test</li>
-<li> Parser_test</li>
-<li> Parser test</li>
-<li> Parser_test</li>
-<li> Talk:Parser test</li>
-<li> Talk:Parser_test</li>
-<li> Parser test</li>
-<li> Parser_test</li>
-<li> </li>
-<li> </li>
-<li> 0</li>
-<li> Talk</li>
-<li> Talk</li>
-<li> </li>
-<li> </li>
-<li> <a href="/index.php?title=Template:Dynamic&amp;action=edit&amp;redlink=1" class="new" title="Template:Dynamic (page does not exist)">Template:Dynamic</a></li></ul>
+*{{PAGENAME}}
+*{{PAGENAMEE}}
+*{{FULLPAGENAME}}
+*{{FULLPAGENAMEE}}
+*{{BASEPAGENAME}}
+*{{BASEPAGENAMEE}}
+*{{SUBPAGENAME}}
+*{{SUBPAGENAMEE}}
+*{{ROOTPAGENAME}}
+*{{ROOTPAGENAMEE}}
+*{{TALKPAGENAME}}
+*{{TALKPAGENAMEE}}
+*{{SUBJECTPAGENAME}}
+*{{SUBJECTPAGENAMEE}}
+*{{NAMESPACEE}}
+*{{NAMESPACE}}
+*{{NAMESPACENUMBER}}
+*{{TALKSPACE}}
+*{{TALKSPACEE}}
+*{{SUBJECTSPACE}}
+*{{SUBJECTSPACEE}}
+*{{Dynamic|{{NUMBEROFUSERS}}|{{NUMBEROFPAGES}}|{{CURRENTVERSION}}|{{CONTENTLANGUAGE}}|{{DIRECTIONMARK}}|{{CURRENTTIMESTAMP}}|{{NUMBEROFARTICLES}}}}
+!! html
+<ul><li>Parser test</li>
+<li>Parser_test</li>
+<li>Parser test</li>
+<li>Parser_test</li>
+<li>Parser test</li>
+<li>Parser_test</li>
+<li>Parser test</li>
+<li>Parser_test</li>
+<li>Parser test</li>
+<li>Parser_test</li>
+<li>Talk:Parser test</li>
+<li>Talk:Parser_test</li>
+<li>Parser test</li>
+<li>Parser_test</li>
+<li></li>
+<li></li>
+<li>0</li>
+<li>Talk</li>
+<li>Talk</li>
+<li></li>
+<li></li>
+<li><a href="/index.php?title=Template:Dynamic&amp;action=edit&amp;redlink=1" class="new" title="Template:Dynamic (page does not exist)">Template:Dynamic</a></li></ul>
!! end
### Note: Above tests excludes the "{{NUMBEROFADMINS}}" magic word because it generates a MySQL error when included.
@@ -20055,7 +20306,7 @@ File:File:Foobar.jpg
!! html/parsoid
<ul class="gallery mw-gallery-traditional" type="123" typeof="mw:Extension/gallery" about="#mwt2" data-mw='{"name":"gallery","attrs":{"type":"123","summary":"345"},"body":{"extsrc":"\nFile:File:Foobar.jpg\n"}}'>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Error mw:Image"><a href="./File:File:Foobar.jpg"><img resource="./File:File:Foobar.jpg" src="./Special:FilePath/File:Foobar.jpg" height="120" width="120"/></a></span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Error mw:Image"><a href="./File:File:Foobar.jpg"><img resource="./File:File:Foobar.jpg" src="./Special:FilePath/File:Foobar.jpg" height="120" width="120"/></a></figure-inline></div><div class="gallerytext"></div></li>
</ul>
!! end
@@ -20118,12 +20369,12 @@ image4 |300px| centre
!! html/parsoid
<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt3" data-mw='{"name":"gallery","attrs":{},"body":{}}'>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Error mw:Image"><a href="./File:Image1.png"><img resource="./File:Image1.png" src="./Special:FilePath/Image1.png" height="120" width="120"/></a></span></div><div class="gallerytext"></div></li>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Error mw:Image"><a href="./File:Image2.gif"><img resource="./File:Image2.gif" src="./Special:FilePath/Image2.gif" height="120" width="120"/></a></span></div><div class="gallerytext"></div></li>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Error mw:Image"><a href="./File:Image3"><img resource="./File:Image3" src="./Special:FilePath/Image3" height="120" width="120"/></a></span></div><div class="gallerytext"></div></li>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Error mw:Image"><a href="./File:Image4"><img resource="./File:Image4" src="./Special:FilePath/Image4" height="300" width="300"/></a></span></div><div class="gallerytext"></div></li>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Error mw:Image"><a href="./File:Image5.svg"><img resource="./File:Image5.svg" src="./Special:FilePath/Image5.svg" height="120" width="120"/></a></span></div><div class="gallerytext"> <a rel="mw:ExtLink" href="http://///////">http://///////</a></div></li>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Error mw:Image"><a href="./File:*_image6"><img resource="./File:*_image6" src="./Special:FilePath/*_image6" height="120" width="120"/></a></span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Error mw:Image"><a href="./File:Image1.png"><img resource="./File:Image1.png" src="./Special:FilePath/Image1.png" height="120" width="120"/></a></figure-inline></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Error mw:Image"><a href="./File:Image2.gif"><img resource="./File:Image2.gif" src="./Special:FilePath/Image2.gif" height="120" width="120"/></a></figure-inline></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Error mw:Image"><a href="./File:Image3"><img resource="./File:Image3" src="./Special:FilePath/Image3" height="120" width="120"/></a></figure-inline></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Error mw:Image"><a href="./File:Image4"><img resource="./File:Image4" src="./Special:FilePath/Image4" height="300" width="300"/></a></figure-inline></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Error mw:Image"><a href="./File:Image5.svg"><img resource="./File:Image5.svg" src="./Special:FilePath/Image5.svg" height="120" width="120"/></a></figure-inline></div><div class="gallerytext"> <a rel="mw:ExtLink" class="external free" href="http://///////">http://///////</a></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Error mw:Image"><a href="./File:*_image6"><img resource="./File:*_image6" src="./Special:FilePath/*_image6" height="120" width="120"/></a></figure-inline></div><div class="gallerytext"></div></li>
</ul>
!! end
@@ -20181,11 +20432,11 @@ image:foobar.jpg|Blabla|alt=This is a foo-bar.|blabla.
!! html/parsoid
<ul class="gallery mw-gallery-traditional" style="max-width: 226px; _width: 226px;" typeof="mw:Extension/gallery" about="#mwt3" data-mw='{"name":"gallery","attrs":{"widths":"70px","heights":"40px","perrow":"2"},"body":{}}'>
<li class="gallerycaption">Foo <a rel="mw:WikiLink" href="./Main_Page" title="Main Page">Main Page</a></li>
-<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span typeof="mw:Error mw:Image"><a href="./File:Nonexistent.jpg"><img resource="./File:Nonexistent.jpg" src="./Special:FilePath/Nonexistent.jpg" height="40" width="70"/></a></span></div><div class="gallerytext">caption</div></li>
-<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span typeof="mw:Error mw:Image"><a href="./File:Nonexistent.jpg"><img resource="./File:Nonexistent.jpg" src="./Special:FilePath/Nonexistent.jpg" height="40" width="70"/></a></span></div><div class="gallerytext"></div></li>
-<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></span></div><div class="gallerytext">some <b>caption</b> <a rel="mw:WikiLink" href="./Main_Page" title="Main Page">Main Page</a></div></li>
-<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></span></div><div class="gallerytext"></div></li>
-<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img alt="This is a foo-bar." resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></span></div><div class="gallerytext">blabla.</div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><figure-inline typeof="mw:Error mw:Image"><a href="./File:Nonexistent.jpg"><img resource="./File:Nonexistent.jpg" src="./Special:FilePath/Nonexistent.jpg" height="40" width="70"/></a></figure-inline></div><div class="gallerytext">caption</div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><figure-inline typeof="mw:Error mw:Image"><a href="./File:Nonexistent.jpg"><img resource="./File:Nonexistent.jpg" src="./Special:FilePath/Nonexistent.jpg" height="40" width="70"/></a></figure-inline></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></figure-inline></div><div class="gallerytext">some <b>caption</b> <a rel="mw:WikiLink" href="./Main_Page" title="Main Page">Main Page</a></div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></figure-inline></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img alt="This is a foo-bar." resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></figure-inline></div><div class="gallerytext">blabla.</div></li>
</ul>
!! end
@@ -20242,11 +20493,53 @@ image:foobar.jpg|Blabla|alt=This is a foo-bar.|blabla.
!! html/parsoid
<ul class="gallery mw-gallery-traditional" style="max-width: 226px; _width: 226px;" typeof="mw:Extension/gallery" about="#mwt3" data-mw='{"name":"gallery","attrs":{"widths":"70px","heights":"40px","perrow":"2","caption":"Foo [[Main Page]]"},"body":{"extsrc":"\nFile:Nonexistent.jpg|caption\nFile:Nonexistent.jpg\nimage:foobar.jpg|some &#39;&#39;&#39;caption&#39;&#39;&#39; [[Main Page]]\nimage:foobar.jpg\nimage:foobar.jpg|Blabla|alt=This is a foo-bar.|blabla.\n"}}'>
<li class="gallerycaption">Foo <a rel="mw:WikiLink" href="./Main_Page" title="Main Page">Main Page</a></li>
-<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span typeof="mw:Error mw:Image"><a href="./File:Nonexistent.jpg"><img resource="./File:Nonexistent.jpg" src="./Special:FilePath/Nonexistent.jpg" height="40" width="70"/></a></span></div><div class="gallerytext">caption</div></li>
-<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span typeof="mw:Error mw:Image"><a href="./File:Nonexistent.jpg"><img resource="./File:Nonexistent.jpg" src="./Special:FilePath/Nonexistent.jpg" height="40" width="70"/></a></span></div><div class="gallerytext"></div></li>
-<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></span></div><div class="gallerytext">some <b>caption</b> <a rel="mw:WikiLink" href="./Main_Page" title="Main Page">Main Page</a></div></li>
-<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></span></div><div class="gallerytext"></div></li>
-<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img alt="This is a foo-bar." resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></span></div><div class="gallerytext">blabla.</div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><figure-inline typeof="mw:Error mw:Image"><a href="./File:Nonexistent.jpg"><img resource="./File:Nonexistent.jpg" src="./Special:FilePath/Nonexistent.jpg" height="40" width="70"/></a></figure-inline></div><div class="gallerytext">caption</div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><figure-inline typeof="mw:Error mw:Image"><a href="./File:Nonexistent.jpg"><img resource="./File:Nonexistent.jpg" src="./Special:FilePath/Nonexistent.jpg" height="40" width="70"/></a></figure-inline></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></figure-inline></div><div class="gallerytext">some <b>caption</b> <a rel="mw:WikiLink" href="./Main_Page" title="Main Page">Main Page</a></div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></figure-inline></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img alt="This is a foo-bar." resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></figure-inline></div><div class="gallerytext">blabla.</div></li>
+</ul>
+!! end
+
+!! test
+Gallery (without px units)
+!! wikitext
+<gallery widths="70" heights="40">
+File:Foobar.jpg
+</gallery>
+!! html/php
+<ul class="gallery mw-gallery-traditional">
+ <li class="gallerybox" style="width: 105px"><div style="width: 105px">
+ <div class="thumb" style="width: 100px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" width="70" height="8" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/105px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/140px-Foobar.jpg 2x" /></a></div></div>
+ <div class="gallerytext">
+ </div>
+ </div></li>
+</ul>
+
+!! html/parsoid
+<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt2" data-mw='{"name":"gallery","attrs":{"widths":"70","heights":"40"},"body":{"extsrc":"\nFile:Foobar.jpg\n"}}'>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></figure-inline></div><div class="gallerytext"></div></li>
+</ul>
+!! end
+
+!! test
+Gallery (with invalid units)
+!! wikitext
+<gallery widths="70em" heights="40em">
+File:Foobar.jpg
+</gallery>
+!! html/php
+<ul class="gallery mw-gallery-traditional">
+ <li class="gallerybox" style="width: 155px"><div style="width: 155px">
+ <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/240px-Foobar.jpg 2x" /></a></div></div>
+ <div class="gallerytext">
+ </div>
+ </div></li>
+</ul>
+
+!! html/parsoid
+<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt2" data-mw='{"name":"gallery","attrs":{"widths":"70em","heights":"40em"},"body":{"extsrc":"\nFile:Foobar.jpg\n"}}'>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext"></div></li>
</ul>
!! end
@@ -20286,9 +20579,9 @@ image:foobar.jpg|link=Main Page#section|caption
!! html/parsoid
<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt2" data-mw='{"name":"gallery","attrs":{},"body":{}}'>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Image"><a href="./Main_Page"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"></div></li>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Image"><a href="./Main_Page#section"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"></div></li>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Image"><a href="./Main_Page#section"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext">caption</div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="./Main_Page"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="./Main_Page#section"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="./Main_Page#section"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext">caption</div></li>
</ul>
!! end
@@ -20318,7 +20611,7 @@ File:Foobar.jpg|{{echo|ho}}
!! html/parsoid
<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt6" data-mw='{"name":"gallery","attrs":{},"body":{}}'>
<li class="gallerycaption"><span about="#mwt3" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"hi"}},"i":0}}]}'>hi</span></li>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"><span about="#mwt5" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"ho"}},"i":0}}]}'>ho</span></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext"><span about="#mwt5" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"ho"}},"i":0}}]}'>ho</span></div></li>
</ul>
!! end
@@ -20353,8 +20646,8 @@ File:Foobar.jpg|alt=galleryalt|{{Test|unamedParam|alt=param}}
!! html/parsoid
<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt6" data-mw='{"name":"gallery","attrs":{},"body":{}}'>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"><span typeof="mw:Image" data-mw='{"caption":"desc"}'><a href="./File:Foobar.jpg"><img alt="inneralt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/20px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="2" width="20"/></a></span></div></li>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"><span about="#mwt4" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"Test","href":"./Template:Test"},"params":{"1":{"wt":"unamedParam"},"alt":{"wt":"param"}},"i":0}}]}'>This is a test template</span></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext"><figure-inline typeof="mw:Image" data-mw='{"caption":"desc"}'><a href="./File:Foobar.jpg"><img alt="inneralt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/20px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="2" width="20"/></a></figure-inline></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext"><span about="#mwt4" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"Test","href":"./Template:Test"},"params":{"1":{"wt":"unamedParam"},"alt":{"wt":"param"}},"i":0}}]}'>This is a test template</span></div></li>
</ul>
!! end
@@ -20407,10 +20700,10 @@ some <b>caption</b> <a href="/wiki/Main_Page" title="Main Page">Main Page</a>
!! html/parsoid
<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt3" data-mw='{"name":"gallery","attrs":{"showfilename":""},"body":{}}'>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Error mw:Image"><a href="./File:Nonexistent.jpg"><img resource="./File:Nonexistent.jpg" src="./Special:FilePath/Nonexistent.jpg" height="120" width="120"/></a></span></div><div class="gallerytext"><a href="./File:Nonexistent.jpg" class="galleryfilename galleryfilename-truncate" title="File:Nonexistent.jpg">File:Nonexistent.jpg</a>caption</div></li>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Error mw:Image"><a href="./File:Nonexistent.jpg"><img resource="./File:Nonexistent.jpg" src="./Special:FilePath/Nonexistent.jpg" height="120" width="120"/></a></span></div><div class="gallerytext"><a href="./File:Nonexistent.jpg" class="galleryfilename galleryfilename-truncate" title="File:Nonexistent.jpg">File:Nonexistent.jpg</a></div></li>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"><a href="./File:Foobar.jpg" class="galleryfilename galleryfilename-truncate" title="File:Foobar.jpg">File:Foobar.jpg</a>some <b>caption</b> <a rel="mw:WikiLink" href="./Main_Page" title="Main Page">Main Page</a></div></li>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"><a href="./File:Foobar.jpg" class="galleryfilename galleryfilename-truncate" title="File:Foobar.jpg">File:Foobar.jpg</a></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Error mw:Image"><a href="./File:Nonexistent.jpg"><img resource="./File:Nonexistent.jpg" src="./Special:FilePath/Nonexistent.jpg" height="120" width="120"/></a></figure-inline></div><div class="gallerytext"><a href="./File:Nonexistent.jpg" class="galleryfilename galleryfilename-truncate" title="File:Nonexistent.jpg">File:Nonexistent.jpg</a>caption</div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Error mw:Image"><a href="./File:Nonexistent.jpg"><img resource="./File:Nonexistent.jpg" src="./Special:FilePath/Nonexistent.jpg" height="120" width="120"/></a></figure-inline></div><div class="gallerytext"><a href="./File:Nonexistent.jpg" class="galleryfilename galleryfilename-truncate" title="File:Nonexistent.jpg">File:Nonexistent.jpg</a></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext"><a href="./File:Foobar.jpg" class="galleryfilename galleryfilename-truncate" title="File:Foobar.jpg">File:Foobar.jpg</a>some <b>caption</b> <a rel="mw:WikiLink" href="./Main_Page" title="Main Page">Main Page</a></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext"><a href="./File:Foobar.jpg" class="galleryfilename galleryfilename-truncate" title="File:Foobar.jpg">File:Foobar.jpg</a></div></li>
</ul>
!! end
@@ -20455,35 +20748,35 @@ foobar.jpg
!! html/parsoid
<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt2" data-mw='{"name":"gallery","attrs":{},"body":{}}'>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Error mw:Image"><a href="./File:Nonexistent.jpg"><img resource="./File:Nonexistent.jpg" src="./Special:FilePath/Nonexistent.jpg" height="120" width="120"/></a></span></div><div class="gallerytext"></div></li>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Error mw:Image"><a href="./File:Nonexistent.jpg"><img resource="./File:Nonexistent.jpg" src="./Special:FilePath/Nonexistent.jpg" height="120" width="120"/></a></span></div><div class="gallerytext"></div></li>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"></div></li>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Error mw:Image"><a href="./File:Nonexistent.jpg"><img resource="./File:Nonexistent.jpg" src="./Special:FilePath/Nonexistent.jpg" height="120" width="120"/></a></figure-inline></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Error mw:Image"><a href="./File:Nonexistent.jpg"><img resource="./File:Nonexistent.jpg" src="./Special:FilePath/Nonexistent.jpg" height="120" width="120"/></a></figure-inline></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext"></div></li>
</ul>
!! end
!! test
-Gallery override link with WikiLink (T36852)
+Gallery override link with wikilink (T36852)
!! options
parsoid={
"nativeGallery": true
}
!! wikitext
<gallery>
-File:Foobar.jpg|alt=galleryalt|link=InterWikiLink
+File:Foobar.jpg|alt=galleryalt|link=Wikilink
</gallery>
!! html/php
<ul class="gallery mw-gallery-traditional">
<li class="gallerybox" style="width: 155px"><div style="width: 155px">
- <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/InterWikiLink"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/240px-Foobar.jpg 2x" /></a></div></div>
+ <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/Wikilink"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/240px-Foobar.jpg 2x" /></a></div></div>
<div class="gallerytext">
</div>
</div></li>
</ul>
!! html/parsoid
-<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt2" data-parsoid='{"dsr":[0,70,2,2]}' data-mw='{"name":"gallery","attrs":{},"body":{}}'>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Image"><a href="./InterWikiLink"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"></div></li>
+<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt2" data-mw='{"name":"gallery","attrs":{},"body":{}}'>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="./Wikilink"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext"></div></li>
</ul>
!! end
@@ -20508,7 +20801,7 @@ File:Foobar.jpg|alt=galleryalt|link=http://www.example.org
!! html/parsoid
<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt2" data-mw='{"name":"gallery","attrs":{},"body":{}}'>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Image"><a href="http://www.example.org"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="http://www.example.org"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext"></div></li>
</ul>
!! end
@@ -20516,11 +20809,11 @@ File:Foobar.jpg|alt=galleryalt|link=http://www.example.org
Gallery override link with absolute external link with LanguageConverter
!! options
language=zh
-!! input
+!! wikitext
<gallery>
File:foobar.jpg|caption|alt=galleryalt|link=http://www.example.org
</gallery>
-!! result
+!! html/php
<ul class="gallery mw-gallery-traditional">
<li class="gallerybox" style="width: 155px"><div style="width: 155px">
<div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="http://www.example.org"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/240px-Foobar.jpg 2x" /></a></div></div>
@@ -20531,6 +20824,10 @@ File:foobar.jpg|caption|alt=galleryalt|link=http://www.example.org
</div></li>
</ul>
+!! html/parsoid
+<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt2" data-mw='{"name":"gallery","attrs":{},"body":{"extsrc":"\nFile:foobar.jpg|caption|alt=galleryalt|link=http://www.example.org\n"}}'>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="http://www.example.org"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext">caption</div></li>
+</ul>
!! end
!! test
@@ -20555,7 +20852,7 @@ File:Foobar.jpg|alt=galleryalt|link=" onclick="alert('malicious javascript code!
!! html/parsoid
<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt2" data-mw='{"name":"gallery","attrs":{},"body":{}}'>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Image"><a href="./%22_onclick=%22alert('malicious_javascript_code!');"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="./%22_onclick=%22alert('malicious_javascript_code!');"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext"></div></li>
</ul>
!! end
@@ -20582,7 +20879,7 @@ File:Foobar.jpg|link=<
!! html/parsoid
<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt2" data-mw='{"name":"gallery","attrs":{},"body":{}}'>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext">link=&lt;</div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext">link=&lt;</div></li>
</ul>
!! end
@@ -20625,7 +20922,7 @@ File:Foobar.jpg
!! html/parsoid
<ul class="gallery mw-gallery-traditional center" style="text-align: center;" typeof="mw:Extension/gallery" about="#mwt2" data-mw='{"name":"gallery","attrs":{"class":"center","style":"text-align: center;"},"body":{}}'>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext"></div></li>
</ul>
!! end
@@ -20650,7 +20947,7 @@ File:Foobar.jpg
!! html/parsoid
<ul class="gallery mw-gallery-slideshow" data-showthumbnails="1" typeof="mw:Extension/gallery" about="#mwt2" data-mw='{"name":"gallery","attrs":{"mode":"slideshow","showthumbnails":""},"body":{}}'>
-<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px;"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext"></div></li>
</ul>
!! end
@@ -20663,8 +20960,6 @@ parsoid=wt2html,wt2wt,html2html
!! html/php
<p>&#x4a;&#x61;&#x76;&#x61;&#x53;&#x63;&#114;&#x69;&#112;&#x74;
</p>
-!! html/php+tidy
-<p>JavaScript</p>
!! html/parsoid
<p><span typeof="mw:Entity">J</span><span typeof="mw:Entity">a</span><span typeof="mw:Entity">v</span><span typeof="mw:Entity">a</span><span typeof="mw:Entity">S</span><span typeof="mw:Entity">c</span><span typeof="mw:Entity">r</span><span typeof="mw:Entity">i</span><span typeof="mw:Entity">p</span><span typeof="mw:Entity">t</span></p>
!! end
@@ -20673,11 +20968,9 @@ parsoid=wt2html,wt2wt,html2html
HTML Hex character encoding bogus encoding (T28437 regression check)
!! wikitext
&#xsee;&#XSEE;
-!! html/php
+!! html
<p>&amp;#xsee;&amp;#XSEE;
</p>
-!! html/parsoid
-<p>&amp;#xsee;&amp;#XSEE;</p>
!! end
!! test
@@ -20689,8 +20982,6 @@ parsoid=wt2html,wt2wt,html2html
!! html/php
<p>&#xee;&#xee;
</p>
-!! html/php+tidy
-<p>îî</p>
!! html/parsoid
<p><span typeof="mw:Entity">î</span><span typeof="mw:Entity">î</span></p>
!! end
@@ -20709,8 +21000,7 @@ Illegal character references (T106578)
; Surrogate: &#xD83D;&#xDCA9;
; This is an okay astral character: &#x1F4A9;
!! html+tidy
-<dl>
-<dt>Null</dt>
+<dl><dt>Null</dt>
<dd>&amp;#00;</dd>
<dt>FF</dt>
<dd>&amp;#xC;</dd>
@@ -20723,8 +21013,7 @@ Illegal character references (T106578)
<dt>Surrogate</dt>
<dd>&amp;#xD83D;&amp;#xDCA9;</dd>
<dt>This is an okay astral character</dt>
-<dd>💩</dd>
-</dl>
+<dd>&#x1f4a9;</dd></dl>
!! end
!! test
@@ -20741,11 +21030,9 @@ __FORCETOC__
ISBN code coverage
!! wikitext
ISBN 978-0-1234-56&#x20;789
-!! html
+!! html/php
<p><a href="/wiki/Special:BookSources/9780123456" class="internal mw-magiclink-isbn">ISBN 978-0-1234-56</a>&#x20;789
</p>
-!! html+tidy
-<p><a href="/wiki/Special:BookSources/9780123456" class="internal mw-magiclink-isbn">ISBN 978-0-1234-56</a> 789</p>
!! html/parsoid
<p><a href="./Special:BookSources/9780123456" rel="mw:WikiLink" data-parsoid='{"stx":"magiclink"}'>ISBN 978-0-1234-56</a><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#x20;","srcContent":" "}'> </span>789</p>
!! end
@@ -20816,7 +21103,7 @@ T24905: <abbr> followed by ISBN followed by </a>
<p><abbr>(fr)</abbr> <a href="/wiki/Special:BookSources/2753300917" class="internal mw-magiclink-isbn">ISBN 2753300917</a> <a rel="nofollow" class="external text" href="http://www.example.com">example.com</a>
</p>
!! html/parsoid
-<p><abbr data-parsoid='{"stx":"html"}'>(fr)</abbr> <a href="./Special:BookSources/2753300917" rel="mw:WikiLink" data-parsoid='{"stx":"magiclink"}'>ISBN 2753300917</a> <a rel="mw:ExtLink" href="http://www.example.com">example.com</a></p>
+<p><abbr data-parsoid='{"stx":"html"}'>(fr)</abbr> <a href="./Special:BookSources/2753300917" rel="mw:WikiLink" data-parsoid='{"stx":"magiclink"}'>ISBN 2753300917</a> <a rel="mw:ExtLink" class="external text" href="http://www.example.com">example.com</a></p>
!! end
!! test
@@ -20824,7 +21111,7 @@ Double RFC
!! wikitext
RFC RFC 1234
!! html
-<p>RFC <a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc1234">RFC 1234</a>
+<p>RFC <a class="external mw-magiclink-rfc" rel="nofollow" href="https://tools.ietf.org/html/rfc1234">RFC 1234</a>
</p>
!! end
@@ -20841,66 +21128,78 @@ RFC [[RFC 1234]]
RFC code coverage
!! wikitext
RFC 983&#x20;987
-!! html
-<p><a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc983">RFC 983</a>&#x20;987
+!! html/php
+<p><a class="external mw-magiclink-rfc" rel="nofollow" href="https://tools.ietf.org/html/rfc983">RFC 983</a>&#x20;987
</p>
-!! html+tidy
-<p><a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc983">RFC 983</a> 987</p>
+!! html/parsoid
+<p><a href="https://tools.ietf.org/html/rfc983" rel="mw:ExtLink" class="external text" data-parsoid='{"stx":"magiclink"}'>RFC 983</a><span typeof="mw:Entity" data-parsoid='{"src":"&amp;#x20;","srcContent":" "}'> </span>987</p>
!! end
!! test
Centre-aligned image
!! wikitext
[[Image:foobar.jpg|centre]]
-!! html
+!! html/php
<div class="center"><div class="floatnone"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div></div>
-!!end
+!! html/parsoid
+<figure class="mw-default-size mw-halign-center" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"center","ak":"centre"}]}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"Image:foobar.jpg"}}'/></a></figure>
+!! end
!! test
None-aligned image
!! wikitext
[[Image:foobar.jpg|none]]
-!! html
+!! html/php
<div class="floatnone"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div>
-!!end
+!! html/parsoid
+<figure class="mw-default-size mw-halign-none" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"Image:foobar.jpg"}}'/></a></figure>
+!! end
!! test
Width + Height sized image (using px) (height is ignored)
!! wikitext
[[Image:foobar.jpg|640x480px]]
-!! html
+!! html/php
<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" width="640" height="73" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/960px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg 2x" /></a>
</p>
-!!end
+!! html/parsoid
+<p><figure-inline typeof="mw:Image" data-parsoid='{"optList":[{"ck":"width","ak":"640x480px"}]}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="73" width="640" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"73","width":"640"},"sa":{"resource":"Image:foobar.jpg"}}'/></a></figure-inline></p>
+!! end
!! test
Width-sized image (using px, no following whitespace)
!! wikitext
[[Image:foobar.jpg|640px]]
-!! html
+!! html/php
<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" width="640" height="73" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/960px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg 2x" /></a>
</p>
-!!end
+!! html/parsoid
+<p><figure-inline typeof="mw:Image" data-parsoid='{"optList":[{"ck":"width","ak":"640px"}]}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="73" width="640" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"73","width":"640"},"sa":{"resource":"Image:foobar.jpg"}}'/></a></figure-inline></p>
+!! end
!! test
Width-sized image (using px, with following whitespace - test regression from r39467)
!! wikitext
[[Image:foobar.jpg|640px ]]
-!! html
+!! html/php
<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" width="640" height="73" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/960px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg 2x" /></a>
</p>
+!! html/parsoid
+<p><figure-inline typeof="mw:Image" data-parsoid='{"optList":[{"ck":"width","ak":"640px "}]}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="73" width="640" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"73","width":"640"},"sa":{"resource":"Image:foobar.jpg"}}'/></a></figure-inline></p>
!!end
!! test
Width-sized image (using px, with preceding whitespace - test regression from r39467)
!! wikitext
[[Image:foobar.jpg| 640px]]
-!! html
+!! html/php
<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" width="640" height="73" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/960px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg 2x" /></a>
</p>
-!!end
+!! html/parsoid
+<p><figure-inline typeof="mw:Image" data-parsoid='{"optList":[{"ck":"width","ak":" 640px"}]}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="73" width="640" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"73","width":"640"},"sa":{"resource":"Image:foobar.jpg"}}'/></a></figure-inline></p>
+!! end
!! test
Image with page parameter
@@ -20912,7 +21211,7 @@ djvu
<p><a href="/index.php?title=File:LoremIpsum.djvu&amp;page=2" class="image"><img alt="LoremIpsum.djvu" src="http://example.com/images/thumb/5/5f/LoremIpsum.djvu/page2-2480px-LoremIpsum.djvu.jpg" width="2480" height="3508" srcset="http://example.com/images/thumb/5/5f/LoremIpsum.djvu/page2-3720px-LoremIpsum.djvu.jpg 1.5x, http://example.com/images/thumb/5/5f/LoremIpsum.djvu/page2-4960px-LoremIpsum.djvu.jpg 2x" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"page","ak":"page=2"}]}' data-mw='{"page":"2"}'><a href="./File:LoremIpsum.djvu" data-parsoid='{"a":{"href":"./File:LoremIpsum.djvu"},"sa":{"href":"File:LoremIpsum.djvu"}}'><img resource="./File:LoremIpsum.djvu" src="//example.com/images/5/5f/LoremIpsum.djvu" data-file-width="2480" data-file-height="3508" data-file-type="bitmap" height="3508" width="2480" data-parsoid='{"a":{"resource":"./File:LoremIpsum.djvu","height":"3508","width":"2480"},"sa":{"resource":"File:LoremIpsum.djvu"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"page","ak":"page=2"}]}' data-mw='{"page":"2"}'><a href="./File:LoremIpsum.djvu" data-parsoid='{"a":{"href":"./File:LoremIpsum.djvu"},"sa":{"href":"File:LoremIpsum.djvu"}}'><img resource="./File:LoremIpsum.djvu" src="//example.com/images/5/5f/LoremIpsum.djvu" data-file-width="2480" data-file-height="3508" data-file-type="bitmap" height="3508" width="2480" data-parsoid='{"a":{"resource":"./File:LoremIpsum.djvu","height":"3508","width":"2480"},"sa":{"resource":"File:LoremIpsum.djvu"}}'/></a></figure-inline></p>
!! end
!! test
@@ -20946,7 +21245,7 @@ Images with the "|" character in the comment
<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>An <a rel="nofollow" class="external text" href="http://test/?param1=%7Cleft%7C&amp;param2=%7Cx">external</a> URL</div></div></div>
!! html/parsoid
-<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>An <a rel="mw:ExtLink" href="http://test/?param1=%7Cleft%7C&amp;param2=%7Cx" data-parsoid='{"a":{"href":"http://test/?param1=%7Cleft%7C&amp;param2=%7Cx"},"sa":{"href":"http://test/?param1=|left|&amp;param2=|x"}}'>external</a> URL</figcaption></figure>
+<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption>An <a rel="mw:ExtLink" class="external text" href="http://test/?param1=%7Cleft%7C&amp;param2=%7Cx" data-parsoid='{"a":{"href":"http://test/?param1=%7Cleft%7C&amp;param2=%7Cx"},"sa":{"href":"http://test/?param1=|left|&amp;param2=|x"}}'>external</a> URL</figcaption></figure>
!! end
!! test
@@ -21078,20 +21377,20 @@ parsoid=wt2html
!! test
Definition list code coverage
!! wikitext
-; title : def
-; title : def
+;title : def
+;title : def
;title: def
!! html/php
-<dl><dt> title &#160;</dt>
-<dd> def</dd>
-<dt> title&#160;</dt>
-<dd> def</dd>
+<dl><dt>title &#160;</dt>
+<dd>def</dd>
+<dt>title&#160;</dt>
+<dd>def</dd>
<dt>title</dt>
-<dd> def</dd></dl>
+<dd>def</dd></dl>
!! html/parsoid
-<dl><dt> title <span typeof="mw:Placeholder"> </span></dt><dd> def</dd>
-<dt> title<span typeof="mw:Placeholder"> </span></dt><dd> def</dd>
+<dl><dt>title <span typeof="mw:Placeholder"> </span></dt><dd> def</dd>
+<dt>title<span typeof="mw:Placeholder"> </span></dt><dd> def</dd>
<dt>title</dt><dd> def</dd></dl>
!! end
@@ -21170,7 +21469,7 @@ Out-of-order TOC heading levels
=====5=====
==2==
!! html
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#2"><span class="tocnumber">1</span> <span class="toctext">2</span></a>
<ul>
@@ -21270,47 +21569,94 @@ ISBN 1 234 56789 0 - 2006
!! test
anchorencode
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
!! wikitext
{{anchorencode:foo bar©#%n}}
-!! html
+!! html/php
+<p>foo_bar©#%n
+</p>
+!! html/parsoid
+<p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"anchorencode:foo bar©#%n","function":"anchorencode"},"params":{},"i":0}}]}'>foo_bar©#%n</p>
+!! end
+
+!! test
+anchorencode (legacy)
+!! config
+wgFragmentMode=[ 'legacy' ]
+!! wikitext
+{{anchorencode:foo bar©#%n}}
+!! html/php
<p>foo_bar.C2.A9.23.25n
</p>
!! end
!! test
anchorencode trims spaces
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
!! wikitext
{{anchorencode: __pretty__please__}}
-!! html
+!! html/php
<p>pretty_please
</p>
+!! html/parsoid
+<p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"anchorencode: __pretty__please__","function":"anchorencode"},"params":{},"i":0}}]}'>pretty_please</p>
!! end
!! test
anchorencode deals with links
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
!! wikitext
{{anchorencode: [[hello|world]] [[hi]]}}
-!! html
+!! html/php
<p>world_hi
</p>
+!! html/parsoid
+<p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"anchorencode: [[hello|world]] [[hi]]","function":"anchorencode"},"params":{},"i":0}}]}'>world_hi</p>
!! end
!! test
anchorencode deals with templates
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
!! wikitext
-{{anchorencode: {{Foo}} }}
-!! html
-<p>FOO
+{{anchorencode: {{Foo}} x}}
+!! html/php
+<p>FOO_x
</p>
+!! html/parsoid
+<p about="#mwt2" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"anchorencode: {{Foo}} x","function":"anchorencode"},"params":{},"i":0}}]}'>FOO_x</p>
!! end
!! test
anchorencode encodes like the TOC generator: (T20431)
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
!! wikitext
-=== _ +:.3A%3A&&amp;]] ===
+===_ +:.3A%3A _ &&amp;]] x===
+{{anchorencode: _ +:.3A%3A _ &&amp;]] x}}
+__NOEDITSECTION__
+!! html/php
+<h3><span id=".2B:.3A.253A_.26.26.5D.5D_x"></span><span class="mw-headline" id="+:.3A%3A_&amp;&amp;]]_x">_ +:.3A%3A _ &amp;&amp;]] x</span></h3>
+<p>+:.3A%3A_&amp;&amp;&#93;&#93;_x
+</p>
+!! html/parsoid
+<h3 id="+:.3A%3A_&amp;&amp;]]_x"><span id=".2B:.3A.253A_.26.26.5D.5D_x" typeof="mw:FallbackId"></span>_ +:.3A%3A _ &amp;<span typeof="mw:Entity" data-parsoid='{"src":"&amp;amp;","srcContent":"&amp;","dsr":[18,23,null,null]}'>&amp;</span>]] x</h3>
+<p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"anchorencode: _ +:.3A%3A _ &amp;&amp;amp;]] x","function":"anchorencode"},"params":{},"i":0}}]}'>+:.3A%3A_&amp;&amp;<span typeof="mw:Entity">]</span><span typeof="mw:Entity">]</span>_x</p>
+<meta property="mw:PageProp/noeditsection"/>
+!! end
+
+!! test
+anchorencode encodes like the TOC generator: (T20431) (legacy)
+!! config
+wgFragmentMode=[ 'legacy' ]
+!! wikitext
+===_ +:.3A%3A&&amp;]]===
{{anchorencode: _ +:.3A%3A&&amp;]] }}
__NOEDITSECTION__
-!! html
+!! html/php
<h3><span class="mw-headline" id=".2B:.3A.253A.26.26.5D.5D">_ +:.3A%3A&amp;&amp;]]</span></h3>
<p>.2B:.3A.253A.26.26.5D.5D
</p>
@@ -21586,24 +21932,26 @@ language=sr variant=sr-ec
!! test
-{}- tags within headlines (within html for parserConvert())
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
!! options
language=sr variant=sr-ec
!! wikitext
-== -{Naslov}- ==
+==-{Naslov}-==
Note that even an unprotected headline ID is not affected by language
conversion:
-== Latinski ==
+==Latinski==
!! html/php
-<h2><span class="mw-headline" id="-.7BNaslov.7D-">Naslov</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Уреди одељак „Naslov“">уреди</a><span class="mw-editsection-bracket">]</span></span></h2>
+<h2><span id="-.7BNaslov.7D-"></span><span class="mw-headline" id="-{Naslov}-">Naslov</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Уреди одељак „Naslov“">уреди</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>Ноте тхат евен ан унпротецтед хеадлине ИД ис нот аффецтед бy лангуаге
цонверсион:
</p>
<h2><span class="mw-headline" id="Latinski">Латински</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Уреди одељак „Латински“">уреди</a><span class="mw-editsection-bracket">]</span></span></h2>
!! html/parsoid
-<h2 id="-.7BNaslov.7D-"><span typeof="mw:LanguageVariant" data-mw-variant='{"disabled":{"t":"Naslov"}}'></span></h2>
+<h2 id="-{Naslov}-"><span id="-.7BNaslov.7D-" typeof="mw:FallbackId"></span><span typeof="mw:LanguageVariant" data-mw-variant='{"disabled":{"t":"Naslov"}}'></span></h2>
<p>Note that even an unprotected headline ID is not affected by language
conversion:</p>
@@ -22046,7 +22394,7 @@ Nested raw: -{R|nested {{echo|hi}} templates}-
Strings evaluating false shouldn't be ignored by Language converter (T51072)
!! options
language=zh variant=zh-cn
-!! input
+!! wikitext
-{zh-cn:0;zh-sg:1;zh-tw:2;zh-hk:3}-
!! html/php
<p>0
@@ -22059,7 +22407,7 @@ language=zh variant=zh-cn
Conversion rules from [numeric-only string] to [something else] (T48634)
!! options
language=zh variant=zh-cn
-!! input
+!! wikitext
-{H|0=>zh-cn:B}--{H|0=>zh-cn:C;0=>zh-cn:D}--{H|0=>zh-hans:A}-012345-{A|zh-tw:0;zh-cn:E;}-012345
!! html/php
<p>D12345EE12345
@@ -22072,7 +22420,7 @@ language=zh variant=zh-cn
Two-way converter rule entries with an empty value should be ignored (T53551)
!! options
language=zh variant=zh-cn
-!! input
+!! wikitext
-{H|zh-cn:foo;zh-tw:;}-foobar
!! html/php
<p>foobar
@@ -22085,7 +22433,7 @@ language=zh variant=zh-cn
One-way converter rule entries with an empty "from" string should be ignored (T53551)
!! options
language=zh variant=zh-cn
-!! input
+!! wikitext
-{H|=>zh-cn:foo;}-foobar
!! html/php
<p>foobar
@@ -22098,7 +22446,7 @@ language=zh variant=zh-cn
Empty converter rule entries shouldn't be inserted into the conversion table (T53551)
!! options
language=zh variant=zh-cn
-!! input
+!! wikitext
-{H|}-foobar
!! html/php
<p>foobar
@@ -22117,7 +22465,7 @@ Nested: -{zh-hans:Hi -{zh-cn:China;zh-sg:Singapore;}-;zh-hant:Hello -{zh-tw:Taiw
<p>Nested: Hello Hong Kong!
</p>
!! html/parsoid
-<p>Nested: <span typeof="mw:LanguageVariant" data-parsoid='{"tSp":[7]}' data-mw-variant='{"twoway":[{"l":"zh-hans","t":"Hi &lt;span typeof=\"mw:LanguageVariant\" data-mw-variant=&#39;{\"twoway\":[{\"l\":\"zh-cn\",\"t\":\"China\"},{\"l\":\"zh-sg\",\"t\":\"Singapore\"}]}&#39; data-parsoid=&#39;{\"fl\":[],\"tSp\":[7],\"dsr\":[21,53,null,2]}&#39;>&lt;/span>"},{"l":"zh-hant","t":"Hello &lt;span typeof=\"mw:LanguageVariant\" data-mw-variant=&#39;{\"twoway\":[{\"l\":\"zh-tw\",\"t\":\"Taiwan\"},{\"l\":\"zh-hk\",\"t\":\"H&amp;lt;span typeof=\\\"mw:LanguageVariant\\\" data-mw-variant=&amp;#39;{\\\"disabled\\\":{\\\"t\\\":\\\"ong\\\"}}&amp;#39; data-parsoid=&amp;#39;{\\\"fl\\\":[],\\\"dsr\\\":[90,97,null,2]}&amp;#39;>&amp;lt;/span> K&amp;lt;span typeof=\\\"mw:LanguageVariant\\\" data-mw-variant=&amp;#39;{\\\"disabled\\\":{\\\"t\\\":\\\"\\\"}}&amp;#39; data-parsoid=&amp;#39;{\\\"fl\\\":[],\\\"dsr\\\":[99,103,null,2]}&amp;#39;>&amp;lt;/span>ong\"}]}&#39; data-parsoid=&#39;{\"fl\":[],\"tSp\":[7],\"dsr\":[68,109,null,2]}&#39;>&lt;/span>"}]}'></span>!</p>
+<p>Nested: <span typeof="mw:LanguageVariant" data-parsoid='{"tSp":[7]}' data-mw-variant='{"twoway":[{"l":"zh-hans","t":"Hi &lt;span typeof=\"mw:LanguageVariant\" data-mw-variant=&apos;{\"twoway\":[{\"l\":\"zh-cn\",\"t\":\"China\"},{\"l\":\"zh-sg\",\"t\":\"Singapore\"}]}&apos; data-parsoid=&apos;{\"fl\":[],\"tSp\":[7],\"dsr\":[21,53,null,2]}&apos;>&lt;/span>"},{"l":"zh-hant","t":"Hello &lt;span typeof=\"mw:LanguageVariant\" data-mw-variant=&apos;{\"twoway\":[{\"l\":\"zh-tw\",\"t\":\"Taiwan\"},{\"l\":\"zh-hk\",\"t\":\"H&amp;lt;span typeof=\\\"mw:LanguageVariant\\\" data-mw-variant=&amp;apos;{\\\"disabled\\\":{\\\"t\\\":\\\"ong\\\"}}&amp;apos; data-parsoid=&amp;apos;{\\\"fl\\\":[],\\\"dsr\\\":[90,97,null,2]}&amp;apos;>&amp;lt;/span> K&amp;lt;span typeof=\\\"mw:LanguageVariant\\\" data-mw-variant=&amp;apos;{\\\"disabled\\\":{\\\"t\\\":\\\"\\\"}}&amp;apos; data-parsoid=&amp;apos;{\\\"fl\\\":[],\\\"dsr\\\":[99,103,null,2]}&amp;apos;>&amp;lt;/span>ong\"}]}&apos; data-parsoid=&apos;{\"fl\":[],\"tSp\":[7],\"dsr\":[68,109,null,2]}&apos;>&lt;/span>"}]}'></span>!</p>
!! end
!! test
@@ -22130,7 +22478,7 @@ language=zh variant=zh-cn
<p><span title="X">A</span>
</p>
!! html/parsoid
-<p><span typeof="mw:LanguageVariant" data-mw-variant='{"filter":{"l":["zh","zh-hans","zh-hant"],"t":"&lt;span title=\"\" about=\"#mwt1\" typeof=\"mw:ExpandedAttrs\" data-parsoid=&#39;{\"stx\":\"html\",\"a\":{\"title\":\"\"},\"sa\":{\"title\":\"-{X}-\"},\"dsr\":[21,49,20,7]}&#39; data-mw=&#39;{\"attribs\":[[{\"txt\":\"title\"},{\"html\":\"&amp;lt;span typeof=\\\"mw:LanguageVariant\\\" data-mw-variant=&amp;#39;{\\\"disabled\\\":{\\\"t\\\":\\\"X\\\"}}&amp;#39; data-parsoid=&amp;#39;{\\\"fl\\\":[],\\\"dsr\\\":[34,39,null,2]}&amp;#39;>&amp;lt;/span>\"}]]}&#39;>A&lt;/span>"}}'></span></p>
+<p><span typeof="mw:LanguageVariant" data-mw-variant='{"filter":{"l":["zh","zh-hans","zh-hant"],"t":"&lt;span title=\"\" about=\"#mwt1\" typeof=\"mw:ExpandedAttrs\" data-parsoid=&#39;{\"stx\":\"html\",\"a\":{\"title\":\"\"},\"sa\":{\"title\":\"-{X}-\"},\"dsr\":[21,49,20,7]}&#39; data-mw=&#39;{\"attribs\":[[{\"txt\":\"title\"},{\"html\":\"&amp;lt;span typeof=\\\"mw:LanguageVariant\\\" data-mw-variant=&amp;apos;{\\\"disabled\\\":{\\\"t\\\":\\\"X\\\"}}&amp;apos; data-parsoid=&amp;apos;{\\\"fl\\\":[],\\\"dsr\\\":[34,39,null,2]}&amp;apos;>&amp;lt;/span>\"}]]}&#39;>A&lt;/span>"}}'></span></p>
!! end
!! test
@@ -22143,7 +22491,7 @@ language=zh variant=zh-cn
<p><span title="X">A</span>
</p>
!! html/parsoid
-<p><span typeof="mw:LanguageVariant" data-mw-variant='{"disabled":{"t":"&lt;span title=\"\" about=\"#mwt1\" typeof=\"mw:ExpandedAttrs\" data-parsoid=&#39;{\"stx\":\"html\",\"a\":{\"title\":\"\"},\"sa\":{\"title\":\"-{X}-\"},\"dsr\":[2,30,20,7]}&#39; data-mw=&#39;{\"attribs\":[[{\"txt\":\"title\"},{\"html\":\"&amp;lt;span typeof=\\\"mw:LanguageVariant\\\" data-mw-variant=&amp;#39;{\\\"disabled\\\":{\\\"t\\\":\\\"X\\\"}}&amp;#39; data-parsoid=&amp;#39;{\\\"fl\\\":[],\\\"dsr\\\":[15,20,null,2]}&amp;#39;>&amp;lt;/span>\"}]]}&#39;>A&lt;/span>"}}'></span></p>
+<p><span typeof="mw:LanguageVariant" data-mw-variant='{"disabled":{"t":"&lt;span title=\"\" about=\"#mwt1\" typeof=\"mw:ExpandedAttrs\" data-parsoid=&#39;{\"stx\":\"html\",\"a\":{\"title\":\"\"},\"sa\":{\"title\":\"-{X}-\"},\"dsr\":[2,30,20,7]}&#39; data-mw=&#39;{\"attribs\":[[{\"txt\":\"title\"},{\"html\":\"&amp;lt;span typeof=\\\"mw:LanguageVariant\\\" data-mw-variant=&amp;apos;{\\\"disabled\\\":{\\\"t\\\":\\\"X\\\"}}&amp;apos; data-parsoid=&amp;apos;{\\\"fl\\\":[],\\\"dsr\\\":[15,20,null,2]}&amp;apos;>&amp;lt;/span>\"}]]}&#39;>A&lt;/span>"}}'></span></p>
!! end
# Parsoid and PHP disagree on how to parse this example: Parsoid
@@ -22160,10 +22508,10 @@ language=zh variant=zh-cn
<span>a-{H|0=>zh-cn:x<span>y;0=>zh-tw:b<div>c}-d
!! html/php+tidy
-<p><span>ab</span></p>
-<div><span>cd <span>ab</span></span>
-<div><span>cd <span>ad</span></span></div>
-</div>
+<span>ab<div>cd
+<span>ab<div>cd
+<span>ad
+</span></div></span></div></span>
!! html/parsoid
<p><span data-parsoid='{"stx":"html","autoInsertedEnd":true}'>a</span></p><div typeof="mw:LanguageVariant" data-mw-variant='{"disabled":{"t":"b&lt;div data-parsoid=&#39;{\"stx\":\"html\",\"autoInsertedEnd\":true,\"dsr\":[10,16,5,0]}&#39;>c&lt;/div>"}}'></div><p>d</p>
@@ -22236,7 +22584,7 @@ parsoid={
|}
!! end
-# Tests LanguageVariantText._fromSelser
+# Tests LanguageVariantText._fromSelSer
!! test
LanguageConverter selser (4)
!! options
@@ -22282,20 +22630,20 @@ gopher://www.google.com
!! html/php
<p><a rel="nofollow" class="external free" href="http://www.google.com">http://www.google.com</a>
<a rel="nofollow" class="external free" href="gopher://www.google.com">gopher://www.google.com</a>
-<a rel="nofollow" class="external free" href="http://www.google.com">http://www.google.com</a>
-<a rel="nofollow" class="external free" href="gopher://www.google.com">gopher://www.google.com</a>
+<a rel="nofollow" class="external text" href="http://www.google.com">http://www.google.com</a>
+<a rel="nofollow" class="external text" href="gopher://www.google.com">gopher://www.google.com</a>
<a rel="nofollow" class="external text" href="https://www.google.com">irc://www.google.com</a>
<a rel="nofollow" class="external text" href="ftp://www.google.com">www.гоогле.цом/фтп://дир</a>
<a rel="nofollow" class="external text" href="//www.google.com">www.гоогле.цом</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://www.google.com">http://www.google.com</a>
-<a rel="mw:ExtLink" href="gopher://www.google.com">gopher://www.google.com</a>
-<a rel="mw:ExtLink" href="http://www.google.com">http://www.google.com</a>
-<a rel="mw:ExtLink" href="gopher://www.google.com">gopher://www.google.com</a>
-<a rel="mw:ExtLink" href="https://www.google.com">irc://www.google.com</a>
-<a rel="mw:ExtLink" href="ftp://www.google.com">www.google.com/ftp://dir</a>
-<a rel="mw:ExtLink" href="//www.google.com">www.google.com</a></p>
+<p><a rel="mw:ExtLink" class="external free" href="http://www.google.com">http://www.google.com</a>
+<a rel="mw:ExtLink" class="external free" href="gopher://www.google.com">gopher://www.google.com</a>
+<a rel="mw:ExtLink" class="external free" href="http://www.google.com">http://www.google.com</a>
+<a rel="mw:ExtLink" class="external free" href="gopher://www.google.com">gopher://www.google.com</a>
+<a rel="mw:ExtLink" class="external text" href="https://www.google.com">irc://www.google.com</a>
+<a rel="mw:ExtLink" class="external text" href="ftp://www.google.com">www.google.com/ftp://dir</a>
+<a rel="mw:ExtLink" class="external text" href="//www.google.com">www.google.com</a></p>
!! end
!! test
@@ -22416,15 +22764,9 @@ File:foobar.jpg|{{Test|unamedParam|alt=-{R|param}-}}|alt=galleryalt
</ul>
!! html/parsoid
-<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" data-mw='{"name":"gallery","attrs":{},"body":{"extsrc":"\nFile:foobar.jpg|[[File:foobar.jpg|20px|desc|alt=-{R|foo}-|-{R|bar}-]]|alt=-{R|bat}-\nFile:foobar.jpg|{{Test|unamedParam|alt=-{R|param}-}}|alt=galleryalt\n"}}'>
-<li class="gallerybox">
-<div class="thumb"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img alt="" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div>
-<div class="gallerytext"><span typeof="mw:Image" data-mw='{"caption":"&lt;span typeof=\"mw:LanguageVariant\" data-mw-variant=&#39;{\"disabled\":{\"t\":\"bar\"}}&#39; data-parsoid=&#39;{\"fl\":[\"R\"],\"dsr\":[68,77,null,2]}&#39;>&lt;/span>"}'><a href="./File:Foobar.jpg"><img alt="" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/20px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="2" width="20"/></a></span></div>
-</li>
-<li class="gallerybox">
-<div class="thumb"><span typeof="mw:Image"><a href="./File:Foobar.jpg"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div>
-<div class="gallerytext"><span typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"Test","href":"./Template:Test"},"params":{"1":{"wt":"unamedParam"},"alt":{"wt":"-{R|param}-"}},"i":0}}]}'>This is a test template</span></div>
-</li>
+<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt6" data-mw='{"name":"gallery","attrs":{},"body":{"extsrc":"\nFile:foobar.jpg|[[File:foobar.jpg|20px|desc|alt=-{R|foo}-|-{R|bar}-]]|alt=-{R|bat}-\nFile:foobar.jpg|{{Test|unamedParam|alt=-{R|param}-}}|alt=galleryalt\n"}}'>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img alt="" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext"><figure-inline typeof="mw:Image" data-mw='{"caption":"&lt;span typeof=\"mw:LanguageVariant\" data-mw-variant=&#39;{\"disabled\":{\"t\":\"bar\"}}&#39; data-parsoid=&#39;{\"fl\":[\"R\"],\"dsr\":[68,77,null,2]}&#39;>&lt;/span>"}'><a href="./File:Foobar.jpg"><img alt="" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/20px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="2" width="20"/></a></figure-inline></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><figure-inline typeof="mw:Image"><a href="./File:Foobar.jpg"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></figure-inline></div><div class="gallerytext"><span about="#mwt4" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"Test","href":"./Template:Test"},"params":{"1":{"wt":"unamedParam"},"alt":{"wt":"-{R|param}-"}},"i":0}}]}'>This is a test template</span></div></li>
</ul>
!! end
@@ -22455,10 +22797,9 @@ language=zh variant=zh-cn
;<b>foo:bar
;-{zh-cn:AAA
!! html/php+tidy
-<dl>
-<dt><b>foo:bar</b></dt>
-<dt><b>-{zh-cn:AAA</b></dt>
-</dl>
+<dl><dt><b>foo:bar</b></dt><b>
+<dt>-{zh-cn:AAA</dt></b></dl><p><b>
+</b></p>
!! html/parsoid
<dl><dt data-parsoid='{"dsr":[0,11,1,0]}'><b data-parsoid='{"stx":"html","autoInsertedEnd":true}'>foo:bar</b></dt><b data-parsoid='{"stx":"html","autoInsertedEnd":true,"autoInsertedStart":true}'>
<dt data-parsoid='{"dsr":[12,20,1,0]}'>-{zh-cn</dt>
@@ -22497,7 +22838,7 @@ parsoid=wt2html,wt2wt,html2html
<table>
<tr>
-<td> B
+<td>B
</td></tr></table>
!! html/parsoid
@@ -22611,28 +22952,37 @@ a:b=>c xyz
!! end
!! test
+T179579: Nowiki and lc interaction
+!! options
+parsoid=wt2html
+language=sr
+!! wikitext
+-{</nowiki>123}-
+
+-{123<nowiki>|</nowiki>456}-
+!! html/parsoid
+<p><span typeof="mw:LanguageVariant" data-mw-variant='{"disabled":{"t":"&amp;lt;/nowiki>123"}}' data-parsoid='{"fl":[],"src":"-{&lt;/nowiki>123}-"}'></span></p>
+
+<p><span typeof="mw:LanguageVariant" data-mw-variant='{"disabled":{"t":"123&lt;span typeof=\"mw:Nowiki\" data-parsoid=&#39;{\"dsr\":[23,41,8,9]}&#39;>|&lt;/span>456"}}' data-parsoid='{"fl":[],"src":"-{123&lt;nowiki>|&lt;/nowiki>456}-"}'></span></p>
+!! end
+
+!! test
T2529: Uncovered bullet
!! wikitext
-* Foo {{bullet}}
+*Foo {{bullet}}
!! html
-<ul><li> Foo </li>
-<li> Bar</li></ul>
+<ul><li>Foo</li>
+<li>Bar</li></ul>
!! end
-# Plain MediaWiki does not remove empty lists, but tidy actually does.
-# Templates in Wikipedia rely on this behavior, as tidy has always been
-# enabled there. These tests are normally run *without* tidy, so specify the
-# full output here.
-# To test realistic parsing behavior, apply a tidy-like transformation to both
-# the expected output and your parser's output.
!! test
-T2529: Uncovered bullet leaving empty list, normally removed by tidy
+T2529: Uncovered bullet in a deeply nested list
!! wikitext
-******* Foo {{bullet}}
+*******Foo {{bullet}}
!! html
-<ul><li><ul><li><ul><li><ul><li><ul><li><ul><li><ul><li> Foo </li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li>
-<li> Bar</li></ul>
+<ul><li><ul><li><ul><li><ul><li><ul><li><ul><li><ul><li>Foo</li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li>
+<li>Bar</li></ul>
!! end
@@ -22648,12 +22998,12 @@ y
</p>
<table>
<tr>
-<td> 1 </td>
-<td> 2
+<td>1</td>
+<td>2
</td></tr>
<tr>
-<td> 3 </td>
-<td> 4
+<td>3</td>
+<td>4
</td></tr></table>
<p>y
</p>
@@ -22662,10 +23012,10 @@ y
!! test
T2529: Uncovered bullet in parser function result
!! wikitext
-* Foo {{lc:{{bullet}} }}
+*Foo {{lc:{{bullet}} }}
!! html
-<ul><li> Foo </li>
-<li> bar</li></ul>
+<ul><li>Foo</li>
+<li>bar</li></ul>
!! end
@@ -22818,14 +23168,17 @@ Line two
!! end
+# doBlockLevels screws up this output and Remex cleans up as much as it can.
+# Parsoid seems to do a better job here since its p-wrapper is probably smarter.
!! test
Nesting tags, paragraphs on lines which begin with <div>
!! wikitext
<div></div><strong>A
B</strong>
!! html/php+tidy
-<p><strong>A</strong></p>
-<p><strong>B</strong></p>
+<div></div><p><strong>A
+</strong></p><strong></strong><p><strong>B</strong>
+</p>
!! html/parsoid
<div></div>
<p><strong>A
@@ -22845,9 +23198,8 @@ Line two</blockquote>
Line two</blockquote>
!! html+tidy
-<blockquote>
-<p>Line one Line two</p>
-</blockquote>
+<blockquote><p>Line one
+Line two</p></blockquote>
!! end
!! test
@@ -22865,10 +23217,12 @@ Line two</blockquote>
!! html+tidy
<blockquote>
-<p>Line one</p>
-Line two</blockquote>
+<p>Line one
+</p><p>
+Line two</p></blockquote>
!! end
+# Parsoid's output is broken on this because of Tidy-compatibility cruft
!! test
T8200: paragraphs inside blockquotes (extra line break on close)
!! wikitext
@@ -22883,9 +23237,9 @@ Line two
</blockquote>
!! html+tidy
-<blockquote>
-<p>Line one</p>
-<p>Line two</p>
+<blockquote><p>Line one
+</p><p>Line two
+</p>
</blockquote>
!! end
@@ -22904,13 +23258,9 @@ Line two
</p>
</blockquote>
-!! html+tidy
-<blockquote>
-<p>Line one</p>
-<p>Line two</p>
-</blockquote>
!! end
+# FIXME: Why does/should the blockquote+div combo suppress p-wrapping here?
!! test
Paragraphs inside blockquotes/divs (no extra line breaks)
!! wikitext
@@ -22989,9 +23339,11 @@ wgLinkHolderBatchSize=0
Free external link invading image caption
!! wikitext
[[Image:Foobar.jpg|thumb|http://x|hello]]
-!! html
+!! html/php
<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>hello</div></div></div>
+!! html/parsoid
+<figure class="mw-default-size" typeof="mw:Image/Thumb" data-parsoid='{"optList":[{"ck":"thumbnail","ak":"thumb"},{"ck":"bogus","ak":"http://x"},{"ck":"caption","ak":"hello"}]}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"25","width":"220"},"sa":{"resource":"Image:Foobar.jpg"}}'/></a><figcaption>hello</figcaption></figure>
!! end
!! test
@@ -23004,25 +23356,29 @@ language=fa
<p><a rel="nofollow" class="external autonumber" href="http://en.wikipedia.org/">[۱]</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="http://en.wikipedia.org/"></a></p>
+<p><a rel="mw:ExtLink" class="external autonumber" href="http://en.wikipedia.org/"></a></p>
!! end
!! test
Multibyte character in padleft
!! wikitext
{{padleft:-Hello|7|Æ}}
-!! html
+!! html/php
<p>Æ-Hello
</p>
+!! html/parsoid
+<p typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"padleft:-Hello","function":"padleft"},"params":{"1":{"wt":"7"},"2":{"wt":"Æ"}},"i":0}}]}'>Æ-Hello</p>
!! end
!! test
Multibyte character in padright
!! wikitext
{{padright:Hello-|7|Æ}}
-!! html
+!! html/php
<p>Hello-Æ
</p>
+!! html/parsoid
+<p typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"padright:Hello-","function":"padright"},"params":{"1":{"wt":"7"},"2":{"wt":"Æ"}},"i":0}}]}'>Hello-Æ</p>
!! end
!!test
@@ -23262,7 +23618,7 @@ comment
Bad images - basic functionality
!! wikitext
[[File:Bad.jpg]]
-!! DISABLED/html/php
+!! html/php+disabled
!! html/parsoid
<p><span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"bad-image","message":"This image is blacklisted in this context."}]}'><a href="./File:Bad.jpg"><img resource="./File:Bad.jpg" height="220" width="220"/></a></span></p>
!! end
@@ -23273,7 +23629,7 @@ Bad images - T18039: text after bad image disappears
Foo bar
[[File:Bad.jpg]]
Bar foo
-!! DISABLED/html/php
+!! html/php+disabled
<p>Foo bar
</p><p>Bar foo
</p>
@@ -23451,13 +23807,13 @@ showindicators
<indicator name="02">[[Main Page]]</indicator>
<indicator name="03">[[File:Foobar.jpg|25px|link=]]</indicator>
<indicator name="04">[[File:Foobar.jpg|25px]]</indicator>
-<indicator name="05">* foo
-* bar</indicator>
+<indicator name="05">*foo
+*bar</indicator>
<indicator name="06"><nowiki>foo</nowiki></indicator>
<indicator name="07"> Preformatted</indicator>
<indicator name="08"><div>Broken tag</indicator>
<indicator name="09">{| class=wikitable
-| cell
+|cell
|}</indicator>
<indicator name="10">Two
@@ -23467,8 +23823,8 @@ paragraphs</indicator>
02=<a href="/wiki/Main_Page" title="Main Page">Main Page</a>
03=<img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/25px-Foobar.jpg" width="25" height="3" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/38px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg 2x" />
04=<a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/25px-Foobar.jpg" width="25" height="3" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/38px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg 2x" /></a>
-05=<ul><li> foo</li>
-<li> bar</li></ul>
+05=<ul><li>foo</li>
+<li>bar</li></ul>
06=foo
07=<pre>Preformatted
@@ -23477,7 +23833,7 @@ paragraphs</indicator>
09=<table class="wikitable">
<tr>
-<td> cell
+<td>cell
</td></tr></table>
10=<p>Two
@@ -23594,7 +23950,7 @@ percent-encoding and + signs in internal links (T28410)
!! html/parsoid
<p><a rel="mw:WikiLink" href="./User:+%25" title="User:+%" data-parsoid='{"stx":"simple","a":{"href":"./User:+%25"},"sa":{"href":"User:+%"}}'>User:+%</a> <a rel="mw:WikiLink" href="./Page+title%25" title="Page+title%" data-parsoid='{"stx":"simple","a":{"href":"./Page+title%25"},"sa":{"href":"Page+title%"}}'>Page+title%</a>
<a rel="mw:WikiLink" href="./%25+" title="%+" data-parsoid='{"stx":"simple","a":{"href":"./%25+"},"sa":{"href":"%+"}}'>%+</a> <a rel="mw:WikiLink" href="./%25+" title="%+" data-parsoid='{"stx":"piped","a":{"href":"./%25+"},"sa":{"href":"%+"}}'>%20</a> <a rel="mw:WikiLink" href="./%25+" title="%+" data-parsoid='{"stx":"simple","a":{"href":"./%25+"},"sa":{"href":"%+ "}}'>%+ </a> <a rel="mw:WikiLink" href="./%25+r" title="%+r" data-parsoid='{"stx":"simple","a":{"href":"./%25+r"},"sa":{"href":"%+r"}}'>%+r</a>
-<a rel="mw:WikiLink" href="./%25" title="%" data-parsoid='{"stx":"simple","a":{"href":"./%25"},"sa":{"href":"%"}}'>%</a> <a rel="mw:WikiLink" href="./+" title="+" data-parsoid='{"stx":"simple","a":{"href":"./+"},"sa":{"href":"+"}}'>+</a> <span class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"bogus","ak":"foo"},{"ck":"caption","ak":"[[bar]]"}]}' data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}],"caption":"&lt;a rel=\"mw:WikiLink\" href=\"./Bar\" title=\"Bar\" data-parsoid=&#39;{\"stx\":\"simple\",\"a\":{\"href\":\"./Bar\"},\"sa\":{\"href\":\"bar\"},\"dsr\":[94,101,2,2]}&#39;>bar&lt;/a>"}'><a href="./File:%25+abc9" data-parsoid='{"a":{"href":"./File:%25+abc9"},"sa":{}}'><img resource="./File:%25+abc9" src="./Special:FilePath/%25+abc9" height="220" width="220" data-parsoid='{"a":{"resource":"./File:%25+abc9","height":"220","width":"220"},"sa":{"resource":"File:%+abc%39"}}'/></a></span>
+<a rel="mw:WikiLink" href="./%25" title="%" data-parsoid='{"stx":"simple","a":{"href":"./%25"},"sa":{"href":"%"}}'>%</a> <a rel="mw:WikiLink" href="./+" title="+" data-parsoid='{"stx":"simple","a":{"href":"./+"},"sa":{"href":"+"}}'>+</a> <figure-inline class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"bogus","ak":"foo"},{"ck":"caption","ak":"[[bar]]"}]}' data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}],"caption":"&lt;a rel=\"mw:WikiLink\" href=\"./Bar\" title=\"Bar\" data-parsoid=&#39;{\"stx\":\"simple\",\"a\":{\"href\":\"./Bar\"},\"sa\":{\"href\":\"bar\"},\"dsr\":[94,101,2,2]}&#39;>bar&lt;/a>"}'><a href="./File:%25+abc9" data-parsoid='{"a":{"href":"./File:%25+abc9"},"sa":{}}'><img resource="./File:%25+abc9" src="./Special:FilePath/%25+abc9" height="220" width="220" data-parsoid='{"a":{"resource":"./File:%25+abc9","height":"220","width":"220"},"sa":{"resource":"File:%+abc%39"}}'/></a></figure-inline>
<a rel="mw:WikiLink" href="./3E" title="3E" data-parsoid='{"stx":"simple","a":{"href":"./3E"},"sa":{"href":"%33%45"}}'>3E</a> <a rel="mw:WikiLink" href="./3E+" title="3E+" data-parsoid='{"stx":"simple","a":{"href":"./3E+"},"sa":{"href":"%33%45+"}}'>3E+</a></p>
!! end
@@ -23608,8 +23964,8 @@ Special characters in embedded file links (T29679)
<a href="/index.php?title=Special:Upload&amp;wpDestFile=Does_not_exist.jpg" class="new" title="File:Does not exist.jpg">Title with &amp; ampersand</a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}]}'><a href="./File:Contains_&amp;_ampersand.jpg"><img resource="./File:Contains_&amp;_ampersand.jpg" src="./Special:FilePath/Contains_&amp;_ampersand.jpg" height="220" width="220"/></a></span>
-<span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}],"caption":"Title with &amp;amp; ampersand"}'><a href="./File:Does_not_exist.jpg"><img resource="./File:Does_not_exist.jpg" src="./Special:FilePath/Does_not_exist.jpg" height="220" width="220"/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}]}'><a href="./File:Contains_&amp;_ampersand.jpg"><img resource="./File:Contains_&amp;_ampersand.jpg" src="./Special:FilePath/Contains_&amp;_ampersand.jpg" height="220" width="220"/></a></figure-inline>
+<figure-inline class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}],"caption":"Title with &amp;amp; ampersand"}'><a href="./File:Does_not_exist.jpg"><img resource="./File:Does_not_exist.jpg" src="./Special:FilePath/Does_not_exist.jpg" height="220" width="220"/></a></figure-inline></p>
!! end
!! test
@@ -23744,9 +24100,9 @@ T28375: TOC with italics
title=[[Main Page]]
!! wikitext
__TOC__
-== ''Lost'' episodes ==
+==''Lost'' episodes==
!! html/php
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#Lost_episodes"><span class="tocnumber">1</span> <span class="toctext"><i>Lost</i> episodes</span></a></li>
</ul>
@@ -23756,7 +24112,7 @@ __TOC__
!! html/parsoid
<meta property="mw:PageProp/toc" data-parsoid='{}'/>
-<h2 data-parsoid='{}'> <i>Lost</i> episodes </h2>
+<h2 id="Lost_episodes" data-parsoid='{}'><i>Lost</i> episodes</h2>
!! end
!! test
@@ -23765,9 +24121,9 @@ T28375: TOC with bold
title=[[Main Page]]
!! wikitext
__TOC__
-== '''should be bold''' then normal text ==
+=='''should be bold''' then normal text==
!! html/php
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#should_be_bold_then_normal_text"><span class="tocnumber">1</span> <span class="toctext"><b>should be bold</b> then normal text</span></a></li>
</ul>
@@ -23777,7 +24133,7 @@ __TOC__
!! html/parsoid
<meta property="mw:PageProp/toc" data-parsoid='{}'/>
-<h2 data-parsoid='{}'> <b>should be bold</b> then normal text </h2>
+<h2 id="should_be_bold_then_normal_text" data-parsoid='{}'><b>should be bold</b> then normal text</h2>
!! end
!! test
@@ -23786,9 +24142,9 @@ T35845: Headings become cursive in TOC when they contain an image
title=[[Main Page]]
!! wikitext
__TOC__
-== Image [[Image:foobar.jpg]] ==
+==Image [[Image:foobar.jpg]]==
!! html/php
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#Image"><span class="tocnumber">1</span> <span class="toctext">Image</span></a></li>
</ul>
@@ -23798,7 +24154,7 @@ __TOC__
!! html/parsoid
<meta property="mw:PageProp/toc" data-parsoid='{}'/>
-<h2 data-parsoid='{}'> Image <span class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"Image:foobar.jpg"}}'/></a></span> </h2>
+<h2 id="Image" data-parsoid='{}'>Image <figure-inline class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"Image:foobar.jpg"}}'/></a></figure-inline></h2>
!! end
!! test
@@ -23807,9 +24163,9 @@ T35845 (2): Headings become bold in TOC when they contain a blockquote
title=[[Main Page]]
!! wikitext
__TOC__
-== <blockquote>Quote</blockquote> ==
+==<blockquote>Quote</blockquote>==
!! html/php
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#Quote"><span class="tocnumber">1</span> <span class="toctext">Quote</span></a></li>
</ul>
@@ -23818,49 +24174,43 @@ __TOC__
<h2><span class="mw-headline" id="Quote"><blockquote>Quote</blockquote></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Main_Page&amp;action=edit&amp;section=1" title="Edit section: Quote">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
!! html/php+tidy
-<p></p>
-<div id="toc" class="toc">
-<div class="toctitle">
-<h2>Contents</h2>
-</div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#Quote"><span class="tocnumber">1</span> <span class="toctext">Quote</span></a></li>
</ul>
</div>
-<p></p>
-<h2><span class="mw-headline" id="Quote"></span></h2>
-<blockquote>
-<p><span class="mw-headline" id="Quote">Quote</span></p>
-</blockquote>
-<p><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Main_Page&amp;action=edit&amp;section=1" title="Edit section: Quote">edit</a><span class="mw-editsection-bracket">]</span></span></p>
+
+<h2><span class="mw-headline" id="Quote"><blockquote><p>Quote</p></blockquote></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Main_Page&amp;action=edit&amp;section=1" title="Edit section: Quote">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
!! html/parsoid
<meta property="mw:PageProp/toc" data-parsoid='{}'/>
-<h2 data-parsoid='{}'> <blockquote>Quote</blockquote> </h2>
+<h2 id="Quote" data-parsoid='{}'><blockquote>Quote</blockquote></h2>
!! end
!! test
Unclosed tags in TOC
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
!! options
title=[[Main Page]]
!! wikitext
__TOC__
-== Proof: 2 < 3 ==
+==Proof: 2 < 3==
<small>Hanc marginis exiguitas non caperet.</small>
QED
!! html/php
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
-<li class="toclevel-1 tocsection-1"><a href="#Proof:_2_.3C_3"><span class="tocnumber">1</span> <span class="toctext">Proof: 2 &lt; 3</span></a></li>
+<li class="toclevel-1 tocsection-1"><a href="#Proof:_2_&lt;_3"><span class="tocnumber">1</span> <span class="toctext">Proof: 2 &lt; 3</span></a></li>
</ul>
</div>
-<h2><span class="mw-headline" id="Proof:_2_.3C_3">Proof: 2 &lt; 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Main_Page&amp;action=edit&amp;section=1" title="Edit section: Proof: 2 &lt; 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<h2><span id="Proof:_2_.3C_3"></span><span class="mw-headline" id="Proof:_2_&lt;_3">Proof: 2 &lt; 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Main_Page&amp;action=edit&amp;section=1" title="Edit section: Proof: 2 &lt; 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<p><small>Hanc marginis exiguitas non caperet.</small>
QED
</p>
!! html/parsoid
<meta property="mw:PageProp/toc" data-parsoid='{}'/>
-<h2 data-parsoid='{}'> Proof: 2 &lt; 3 </h2>
+<h2 id="Proof:_2_&lt;_3" data-parsoid='{}'><span id="Proof:_2_.3C_3" typeof="mw:FallbackId"></span>Proof: 2 &lt; 3</h2>
<p><small>Hanc marginis exiguitas non caperet.</small>
QED</p>
!! end
@@ -23869,11 +24219,11 @@ QED</p>
Multiple tags in TOC
!! wikitext
__TOC__
-== <i>Foo</i> <b>Bar</b> ==
+==<i>Foo</i> <b>Bar</b>==
-== <i>Foo</i> <blockquote>Bar</blockquote> ==
+==<i>Foo</i> <blockquote>Bar</blockquote>==
!! html/php
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#Foo_Bar"><span class="tocnumber">1</span> <span class="toctext"><i>Foo</i> <b>Bar</b></span></a></li>
<li class="toclevel-1 tocsection-2"><a href="#Foo_Bar_2"><span class="tocnumber">2</span> <span class="toctext"><i>Foo</i> Bar</span></a></li>
@@ -23884,27 +24234,20 @@ __TOC__
<h2><span class="mw-headline" id="Foo_Bar_2"><i>Foo</i> <blockquote>Bar</blockquote></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Foo Bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
!! html/php+tidy
-<p></p>
-<div id="toc" class="toc">
-<div class="toctitle">
-<h2>Contents</h2>
-</div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#Foo_Bar"><span class="tocnumber">1</span> <span class="toctext"><i>Foo</i> <b>Bar</b></span></a></li>
<li class="toclevel-1 tocsection-2"><a href="#Foo_Bar_2"><span class="tocnumber">2</span> <span class="toctext"><i>Foo</i> Bar</span></a></li>
</ul>
</div>
-<p></p>
+
<h2><span class="mw-headline" id="Foo_Bar"><i>Foo</i> <b>Bar</b></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Foo Bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<h2><span class="mw-headline" id="Foo_Bar_2"><i>Foo</i></span></h2>
-<blockquote>
-<p><span class="mw-headline" id="Foo_Bar_2">Bar</span></p>
-</blockquote>
-<p><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Foo Bar">edit</a><span class="mw-editsection-bracket">]</span></span></p>
+<h2><span class="mw-headline" id="Foo_Bar_2"><i>Foo</i> <blockquote><p>Bar</p></blockquote></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Foo Bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
!! html/parsoid
<meta property="mw:PageProp/toc" data-parsoid='{}'/>
-<h2 data-parsoid='{}'> <i data-parsoid='{"stx":"html"}'>Foo</i> <b data-parsoid='{"stx":"html"}'>Bar</b> </h2>
-<h2> <i data-parsoid='{"stx":"html"}'>Foo</i> <blockquote>Bar</blockquote> </h2>
+<h2 id="Foo_Bar" data-parsoid='{}'><i data-parsoid='{"stx":"html"}'>Foo</i> <b data-parsoid='{"stx":"html"}'>Bar</b></h2>
+
+<h2 id="Foo_Bar_2" data-parsoid='{}'><i data-parsoid='{"stx":"html"}'>Foo</i> <blockquote>Bar</blockquote></h2>
!! end
# Don't expect Parsoid to roundtrip this until the php parser comes closer to
@@ -23915,11 +24258,11 @@ Tags with parameters in TOC
parsoid=wt2html
!! wikitext
__TOC__
-== <sup class="in-h2">Hello</sup> ==
+==<sup class="in-h2">Hello</sup>==
-== <sup class="a > b">Evilbye</sup> ==
+==<sup class="a > b">Evilbye</sup>==
!! html/php
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#Hello"><span class="tocnumber">1</span> <span class="toctext"><sup>Hello</sup></span></a></li>
<li class="toclevel-1 tocsection-2"><a href="#b.22.3EEvilbye"><span class="tocnumber">2</span> <span class="toctext"><sup> b"&gt;Evilbye</sup></span></a></li>
@@ -23931,26 +24274,26 @@ __TOC__
!! html/parsoid
<meta property="mw:PageProp/toc" />
-<h2> <sup class="in-h2" data-parsoid='{"stx":"html"}'>Hello</sup> </h2>
+<h2 id="Hello"><sup class="in-h2" data-parsoid='{"stx":"html"}'>Hello</sup></h2>
-<h2> <sup class="a " data-parsoid='{"stx":"html"}'> b">Evilbye</sup> </h2>
+<h2 id='b">Evilbye'><span id="b.22.3EEvilbye" typeof="mw:FallbackId"></span><sup class="a " data-parsoid='{"stx":"html"}'> b">Evilbye</sup></h2>
!! end
!! test
span tags with directionality in TOC
!! wikitext
__TOC__
-== <span dir="ltr">C++</span> ==
+==<span dir="ltr">C++</span>==
-== <span dir="rtl">זבנג!</span> ==
+==<span dir="rtl">זבנג!</span>==
-== <span style="font-style: italic">The attributes on these span tags must be deleted from the TOC</span> ==
+==<span style="font-style: italic">The attributes on these span tags must be deleted from the TOC</span>==
-== <span style="font-style: italic" dir="ltr">All attributes on these span tags must be deleted from the TOC</span> ==
+==<span style="font-style: italic" dir="ltr">All attributes on these span tags must be deleted from the TOC</span>==
-== <span dir="ltr" style="font-style: italic">Attributes after dir on these span tags must be deleted from the TOC</span> ==
+==<span dir="ltr" style="font-style: italic">Attributes after dir on these span tags must be deleted from the TOC</span>==
!! html/php
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#C.2B.2B"><span class="tocnumber">1</span> <span class="toctext"><span dir="ltr">C++</span></span></a></li>
<li class="toclevel-1 tocsection-2"><a href="#.D7.96.D7.91.D7.A0.D7.92.21"><span class="tocnumber">2</span> <span class="toctext"><span dir="rtl">זבנג!</span></span></a></li>
@@ -23968,20 +24311,20 @@ __TOC__
!! html/parsoid
<meta property="mw:PageProp/toc" data-parsoid='{}'/>
-<h2 data-parsoid='{}'> <span dir="ltr">C++</span> </h2>
-<h2> <span dir="rtl">זבנג!</span> </h2>
-<h2> <span style="font-style: italic">The attributes on these span tags must be deleted from the TOC</span> </h2>
-<h2> <span style="font-style: italic" dir="ltr">All attributes on these span tags must be deleted from the TOC</span> </h2>
-<h2> <span dir="ltr" style="font-style: italic">Attributes after dir on these span tags must be deleted from the TOC</span> </h2>
+<h2 id="C++" data-parsoid='{}'><span id="C.2B.2B" typeof="mw:FallbackId"></span><span dir="ltr">C++</span></h2>
+<h2 id="זבנג!"><span id=".D7.96.D7.91.D7.A0.D7.92.21" typeof="mw:FallbackId"></span><span dir="rtl">זבנג!</span></h2>
+<h2 id="The_attributes_on_these_span_tags_must_be_deleted_from_the_TOC"><span style="font-style: italic">The attributes on these span tags must be deleted from the TOC</span></h2>
+<h2 id="All_attributes_on_these_span_tags_must_be_deleted_from_the_TOC"><span style="font-style: italic" dir="ltr">All attributes on these span tags must be deleted from the TOC</span></h2>
+<h2 id="Attributes_after_dir_on_these_span_tags_must_be_deleted_from_the_TOC"><span dir="ltr" style="font-style: italic">Attributes after dir on these span tags must be deleted from the TOC</span></h2>
!! end
!! test
T74884: bdi element in ToC
!! wikitext
__TOC__
-== <bdi>test</bdi> ==
+==<bdi>test</bdi>==
!! html/php
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#test"><span class="tocnumber">1</span> <span class="toctext"><bdi>test</bdi></span></a></li>
</ul>
@@ -23991,16 +24334,16 @@ __TOC__
!! html/parsoid
<meta property="mw:PageProp/toc" data-parsoid='{}'/>
-<h2 data-parsoid='{}'> <bdi>test</bdi> </h2>
+<h2 id="test" data-parsoid='{}'><bdi>test</bdi></h2>
!! end
!! test
T35715: s/strike element in ToC
!! wikitext
__TOC__
-== <s>test</s> test <strike>test</strike> ==
+==<s>test</s> test <strike>test</strike>==
!! html/php
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#test_test_test"><span class="tocnumber">1</span> <span class="toctext"><s>test</s> test <strike>test</strike></span></a></li>
</ul>
@@ -24010,19 +24353,16 @@ __TOC__
!! html/parsoid
<meta property="mw:PageProp/toc" data-parsoid='{}'/>
-<h2 data-parsoid='{}'> <s>test</s> test <strike>test</strike> </h2>
+<h2 id="test_test_test" data-parsoid='{}'><s>test</s> test <strike>test</strike></h2>
!! end
-# Note that the html output does not have the <p></p>, but the
-# html+tidy output *does*. This is because the empty <p></p> is
-# removed by the sanitizer, but only when tidy is *not* enabled (!).
!! test
Empty <p> tag in TOC, removed by Sanitizer (T92892)
!! wikitext
__TOC__
-== x ==
+==x==
!! html/php
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#x"><span class="tocnumber">1</span> <span class="toctext">x</span></a></li>
</ul>
@@ -24030,21 +24370,9 @@ __TOC__
<h2><span class="mw-headline" id="x">x</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: x">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-!! html/php+tidy
-<p></p>
-<div id="toc" class="toc">
-<div class="toctitle">
-<h2>Contents</h2>
-</div>
-<ul>
-<li class="toclevel-1 tocsection-1"><a href="#x"><span class="tocnumber">1</span> <span class="toctext">x</span></a></li>
-</ul>
-</div>
-<p></p>
-<h2><span class="mw-headline" id="x">x</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: x">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
!! html/parsoid
<meta property="mw:PageProp/toc" data-parsoid='{}'/>
-<h2 data-parsoid='{}'> x </h2>
+<h2 id="x" data-parsoid='{}'>x</h2>
!! end
!! article
@@ -24167,9 +24495,11 @@ Strip marker in padright
Strip marker in anchorencode
!! wikitext
{{anchorencode:x<nowiki/>y}}
-!! html
+!! html/php
<p>xy
</p>
+!! html/parsoid
+<p about="#mwt2" typeof="mw:Transclusion" data-parsoid='{"pi":[[]]}' data-mw='{"parts":[{"template":{"target":{"wt":"anchorencode:x&lt;nowiki/>y","function":"anchorencode"},"params":{},"i":0}}]}'>xy</p>
!! end
!! test
@@ -24194,17 +24524,17 @@ new support for bdi element (T33817)
Ignore pipe between table row attributes
!! wikitext
{|
-| quux
+|quux
|- id=foo | style='color: red'
-| bar
+|bar
|}
!! html
<table>
<tr>
-<td> quux
+<td>quux
</td></tr>
<tr id="foo" style="color: red">
-<td> bar
+<td>bar
</td></tr></table>
!! end
@@ -24219,14 +24549,45 @@ Language parser function
!! end
!!test
+Padleft and padright (default 0-padding)
+!! wikitext
+{{padleft:xyz|5}}
+{{padright:xyz|5}}
+!! html/php
+<p>00xyz
+xyz00
+</p>
+!! html/parsoid
+<p><span typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"padleft:xyz","function":"padleft"},"params":{"1":{"wt":"5"}},"i":0}}]}'>00xyz</span>
+<span typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"padright:xyz","function":"padright"},"params":{"1":{"wt":"5"}},"i":0}}]}'>xyz00</span></p>
+!! end
+
+!!test
+Padleft and padright (partial fill)
+!! wikitext
+{{padleft:xyz|6|ab}}
+{{padright:xyz|6|ab}}
+!! html/php
+<p>abaxyz
+xyzaba
+</p>
+!! html/parsoid
+<p><span typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"padleft:xyz","function":"padleft"},"params":{"1":{"wt":"6"},"2":{"wt":"ab"}},"i":0}}]}'>abaxyz</span>
+<span typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"padright:xyz","function":"padright"},"params":{"1":{"wt":"6"},"2":{"wt":"ab"}},"i":0}}]}'>xyzaba</span></p>
+!! end
+
+!!test
Padleft and padright as substr
!! wikitext
{{padleft:|3|abcde}}
{{padright:|3|abcde}}
-!! html
+!! html/php
<p>abc
abc
</p>
+!! html/parsoid
+<p><span typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"padleft:","function":"padleft"},"params":{"1":{"wt":"3"},"2":{"wt":"abcde"}},"i":0}}]}'>abc</span>
+<span typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"padright:","function":"padright"},"params":{"1":{"wt":"3"},"2":{"wt":"abcde"}},"i":0}}]}'>abc</span></p>
!! end
!! test
@@ -24261,7 +24622,7 @@ T36939 - Case insensitive link parsing ([HttP://])
<p><a rel="nofollow" class="external autonumber" href="HttP://MediaWiki.Org/">[1]</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="HttP://MediaWiki.Org/"></a></p>
+<p><a rel="mw:ExtLink" class="external autonumber" href="HttP://MediaWiki.Org/"></a></p>
!! end
!!test
@@ -24281,7 +24642,7 @@ HttP://MediaWiki.Org/
<p><a rel="nofollow" class="external free" href="HttP://MediaWiki.Org/">HttP://MediaWiki.Org/</a>
</p>
!! html/parsoid
-<p><a rel="mw:ExtLink" href="HttP://MediaWiki.Org/">HttP://MediaWiki.Org/</a></p>
+<p><a rel="mw:ExtLink" class="external free" href="HttP://MediaWiki.Org/">HttP://MediaWiki.Org/</a></p>
!! end
!!test
@@ -24290,11 +24651,11 @@ Disable TOC
notoc
!! wikitext
Lead
-== Section 1 ==
-== Section 2 ==
-== Section 3 ==
-== Section 4 ==
-== Section 5 ==
+==Section 1==
+==Section 2==
+==Section 3==
+==Section 4==
+==Section 5==
!! html
<p>Lead
</p>
@@ -24397,9 +24758,7 @@ parsoid=wt2html,wt2wt
!! wikitext
'''<small>[[Image:Foobar.jpg|right|300px]]</small>'''
!! html/parsoid
-<p><b><small></small></b></p>
-<figure class="mw-halign-right" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="34" width="300"/></a></figure>
-<p></p>
+<b><small><figure class="mw-halign-right" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="34" width="300"/></a></figure></small></b>
!! end
#### ----------------------------------------------------------------
@@ -24466,6 +24825,7 @@ Empty LI and TR nodes should not be stripped from top-level content
* a
*
* b
+
{|
|-
|-
@@ -24641,17 +25001,60 @@ Headings: 4b. No escaping needed (inside p-tags)
!! options
parsoid=html2wt
!! html/parsoid
-<p>===
-=foo= x
+<p>=foo= x
=foo= <s></s>
</p>
!! wikitext
-===
=foo= x
=foo= <s></s>
+!! html/php
+<p>=foo= x
+=foo= <s></s>
+</p>
!!end
!! test
+Headings: 4c. Short headings (1)
+!! options
+parsoid=html2wt
+!! html/parsoid
+<p>===
+</p>
+!! wikitext
+<nowiki>===</nowiki>
+!! html/php
+<p>===
+</p>
+!! end
+
+# in the html2wt direction we emit '= = =' or '=<nowiki>=</nowiki>='
+!! test
+Headings: 4d. Short headings (2)
+!! options
+parsoid=wt2html,html2html
+!! wikitext
+=
+==
+===
+====
+=====
+!! html/php
+<p>=
+==
+</p>
+<h1><span class="mw-headline" id=".3D">=</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: =">edit</a><span class="mw-editsection-bracket">]</span></span></h1>
+<h1><span class="mw-headline" id=".3D.3D">==</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: ==">edit</a><span class="mw-editsection-bracket">]</span></span></h1>
+<h2><span class="mw-headline" id=".3D_2">=</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: =">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+
+!! html/parsoid
+<p>=
+==</p>
+<h1 id="="><span id=".3D" typeof="mw:FallbackId"></span>=</h1>
+<h1 id="=="><span id=".3D.3D" typeof="mw:FallbackId"></span>==</h1>
+<h2 id="=_2"><span id=".3D_2" typeof="mw:FallbackId"></span>=</h2>
+!! end
+
+!! test
Headings: 5. Empty headings
!! options
parsoid=html2wt
@@ -24777,6 +25180,21 @@ a
!! end
+!! test
+Headings: Used as horizontal rule
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
+!! options
+parsoid=wt2html
+!! wikitext
+===============
+!! html/php
+<h6><span id=".3D.3D.3D"></span><span class="mw-headline" id="===">===</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: ===">edit</a><span class="mw-editsection-bracket">]</span></span></h6>
+
+!! html/parsoid
+<h6 id="==="><span id=".3D.3D.3D" typeof="mw:FallbackId"></span>===</h6>
+!! end
+
#### --------------- Lists ---------------
#### 0. Outside nests (*foo, etc.)
#### 1. Nested inside html <ul><li>*foo</li></ul>
@@ -25083,15 +25501,12 @@ parsoid=html2wt
|}
!! html/php+tidy
<table>
+<tbody><tr>
+<td>foo|bar
+</td></tr>
<tr>
-<td>foo|bar</td>
-</tr>
-<tr>
-<td>x
-<div>a|b</div>
-</td>
-</tr>
-</table>
+<td>x<div>a|b</div>
+</td></tr></tbody></table>
!! end
!! test
@@ -25366,9 +25781,9 @@ parsoid=html2wt
!! html/php
<table>
<tr>
-<td> &lt;foo
+<td>&lt;foo
</td>
-<td> bar&gt;
+<td>bar&gt;
</td></tr></table>
!! end
@@ -25598,9 +26013,9 @@ Links 8. Add <nowiki/>s between text-nodes and RFC-links when required (T66300)
!! options
parsoid=html2wt
!! html/parsoid
-<p><a href="//tools.ietf.org/html/rfc123" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>RFC 123</a>4
-<a href="//tools.ietf.org/html/rfc123" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>RFC 123</a>y
-X<a href="//tools.ietf.org/html/rfc123" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>RFC 123</a>y</p>
+<p><a href="https://tools.ietf.org/html/rfc123" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>RFC 123</a>4
+<a href="https://tools.ietf.org/html/rfc123" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>RFC 123</a>y
+X<a href="https://tools.ietf.org/html/rfc123" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>RFC 123</a>y</p>
!! wikitext
RFC 123<nowiki/>4
RFC 123<nowiki/>y
@@ -25612,18 +26027,18 @@ Links 9. Don't add spurious <nowiki/>s between text-nodes and RFC-links (T66300)
!! options
parsoid=html2wt
!! html/parsoid
-<p><a href="//tools.ietf.org/html/rfc123" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>RFC 123</a>?foo
-<a href="//tools.ietf.org/html/rfc123" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>RFC 123</a>&amp;foo
--<a href="//tools.ietf.org/html/rfc123" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>RFC 123</a>-
+<p><a href="https://tools.ietf.org/html/rfc123" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>RFC 123</a>?foo
+<a href="https://tools.ietf.org/html/rfc123" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>RFC 123</a>&amp;foo
+-<a href="https://tools.ietf.org/html/rfc123" rel="mw:ExtLink" data-parsoid='{"stx":"magiclink"}'>RFC 123</a>-
</p>
!! wikitext
RFC 123?foo
RFC 123&foo
-RFC 123-
!! html/php
-<p><a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc123">RFC 123</a>?foo
-<a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc123">RFC 123</a>&amp;foo
--<a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc123">RFC 123</a>-
+<p><a class="external mw-magiclink-rfc" rel="nofollow" href="https://tools.ietf.org/html/rfc123">RFC 123</a>?foo
+<a class="external mw-magiclink-rfc" rel="nofollow" href="https://tools.ietf.org/html/rfc123">RFC 123</a>&amp;foo
+-<a class="external mw-magiclink-rfc" rel="nofollow" href="https://tools.ietf.org/html/rfc123">RFC 123</a>-
</p>
!! end
@@ -26200,8 +26615,8 @@ parsoid=wt2html,html2html
!! wikitext
<div title="Hello world />Foo
!! html/php+tidy
-<div title="Hello world"></div>
-<p>Foo</p>
+<div title="Hello world"></div><p>Foo
+</p>
!! html/parsoid
<div title="Hello world " data-parsoid='{"stx":"html","selfClose":true}'></div><p>Foo</p>
!! end
@@ -26258,12 +26673,12 @@ parsoid=wt2html,html2html
Accept empty td cell attribute
!! wikitext
{|
-| align="center" | foo || |
+| align="center" |foo|| |
|}
!! html
<table>
<tr>
-<td align="center"> foo </td>
+<td align="center">foo</td>
<td>
</td></tr></table>
@@ -26273,13 +26688,13 @@ Accept empty td cell attribute
Non-empty attributes in th-cells
!! wikitext
{|
-! Foo !! style="color: red" | Bar
+!Foo!! style="color: red" |Bar
|}
!! html
<table>
<tr>
-<th> Foo </th>
-<th style="color: red"> Bar
+<th>Foo</th>
+<th style="color: red">Bar
</th></tr></table>
!!end
@@ -26288,13 +26703,13 @@ Non-empty attributes in th-cells
Accept empty attributes in th-cells
!! wikitext
{|
-!| foo !!| bar
+!|foo!!|bar
|}
!! html
<table>
<tr>
-<th> foo </th>
-<th> bar
+<th>foo</th>
+<th>bar
</th></tr></table>
!!end
@@ -26303,17 +26718,17 @@ Accept empty attributes in th-cells
Empty table rows go away
!! wikitext
{|
-| Hello
-| there
+|Hello
+|there
|- class="foo"
|-
|}
!! html
<table>
<tr>
-<td> Hello
+<td>Hello
</td>
-<td> there
+<td>there
</td></tr>
</table>
@@ -26343,11 +26758,9 @@ RT-ed inter-element separators should be valid separators
</tbody></table>
!!end
-# Parsoid-only since PHP parser relies on Tidy for correct output
+# Parsoid-only test of a DOM pass
!!test
Trailing newlines in a deep dom-subtree that ends a wikitext line should be migrated out
-!!options
-parsoid
!! wikitext
{|
|<small>foo
@@ -26357,7 +26770,7 @@ bar
{|
|<small>foo<small>
|}
-!! html
+!! html/parsoid
<table>
<tbody><tr data-parsoid='{"autoInsertedEnd":true,"autoInsertedStart":true}'><td data-parsoid='{"autoInsertedEnd":true}'><small data-parsoid='{"stx":"html","autoInsertedEnd":true}'>foo
<p>bar</p></small></td></tr>
@@ -26457,13 +26870,13 @@ Indent and comment before table row
!! wikitext
{|
<!--hi-->|-
- | there
+ |there
|}
!! html/php
<table>
<tr>
-<td> there
+<td>there
</td></tr></table>
!! html/parsoid
@@ -27237,7 +27650,7 @@ Image: upright option is ignored on inline and frame images (parsoid)
!! wikitext
[[File:Foobar.jpg|500x500px|upright=0.5|caption]]
!! html/parsoid
-<p><span typeof="mw:Image" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/500px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="57" width="500"/></a></span></p>
+<p><figure-inline typeof="mw:Image" data-mw='{"caption":"caption"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/500px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="57" width="500"/></a></figure-inline></p>
!! end
!! test
@@ -27245,7 +27658,7 @@ Image: in template parameter with empty parameter
!! wikitext
{{echo|[[File:Foobar.jpg|link=]]}}
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Transclusion mw:Image" about="#mwt1" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[[File:Foobar.jpg|link=]]"}},"i":0}}]}'><span><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></span></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Transclusion mw:Image" about="#mwt1" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[[File:Foobar.jpg|link=]]"}},"i":0}}]}'><span><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></span></figure-inline></p>
!! end
!! test
@@ -27298,7 +27711,7 @@ Image: Invalid title as link
<p><a href="/wiki/File:Foobar.jpg" class="image" title="link=&lt;"><img alt="link=&lt;" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"link","ak":"link=&lt;"}]}' data-mw='{"caption":"link=&amp;lt;"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><figure-inline class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"link","ak":"link=&lt;"}]}' data-mw='{"caption":"link=&amp;lt;"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></figure-inline></p>
!! end
!! test
@@ -27396,7 +27809,7 @@ parsoid=html2wt
!! html/parsoid
<ul><li>a<br>b</li><li>c</li></ul>
!! wikitext
-* a<br>b
+* a<br />b
* c
!! end
@@ -27884,9 +28297,9 @@ Edited RFC links not serializable as RFC links should serialize as extlinks
!! options
parsoid=html2wt
!! html/parsoid
-<a href="//tools.ietf.org/html/rfc123" rel="mw:ExtLink">New RFC</a>
+<a href="https://tools.ietf.org/html/rfc123" rel="mw:ExtLink">New RFC</a>
!! wikitext
-[//tools.ietf.org/html/rfc123 New RFC]
+[https://tools.ietf.org/html/rfc123 New RFC]
!! end
!! test
@@ -27929,7 +28342,7 @@ WTS of autolinks with nowikis (round-trip)
!! wikitext
x<nowiki/>http://cscott.net<nowiki/>x
!! html/parsoid
-<p>x<a rel="mw:ExtLink" href="http://cscott.net">http://cscott.net</a>x</p>
+<p>x<a rel="mw:ExtLink" class="external free" href="http://cscott.net">http://cscott.net</a>x</p>
!! end
# this is the "easy" test because it leaves in place all the
@@ -27997,18 +28410,25 @@ Magic links inside links (not autolinked)
[http://foo.com PMID 1234]
[http://foo.com ISBN 123456789x]
!! html+tidy
-<p><a href="/wiki/Foo" title="Foo">http://example.com</a> <a href="/wiki/Foo" title="Foo">RFC 1234</a> <a href="/wiki/Foo" title="Foo">PMID 1234</a> <a href="/wiki/Foo" title="Foo">ISBN 123456789x</a></p>
-<p><a rel="nofollow" class="external text" href="http://foo.com">http://example.com</a> <a rel="nofollow" class="external text" href="http://foo.com">RFC 1234</a> <a rel="nofollow" class="external text" href="http://foo.com">PMID 1234</a> <a rel="nofollow" class="external text" href="http://foo.com">ISBN 123456789x</a></p>
+<p><a href="/wiki/Foo" title="Foo">http://example.com</a>
+<a href="/wiki/Foo" title="Foo">RFC 1234</a>
+<a href="/wiki/Foo" title="Foo">PMID 1234</a>
+<a href="/wiki/Foo" title="Foo">ISBN 123456789x</a>
+</p><p><a rel="nofollow" class="external text" href="http://foo.com">http://example.com</a>
+<a rel="nofollow" class="external text" href="http://foo.com">RFC 1234</a>
+<a rel="nofollow" class="external text" href="http://foo.com">PMID 1234</a>
+<a rel="nofollow" class="external text" href="http://foo.com">ISBN 123456789x</a>
+</p>
!! html/parsoid
<p><a rel="mw:WikiLink" href="./Foo" title="Foo">http://example.com</a>
<a rel="mw:WikiLink" href="./Foo" title="Foo">RFC 1234</a>
<a rel="mw:WikiLink" href="./Foo" title="Foo">PMID 1234</a>
<a rel="mw:WikiLink" href="./Foo" title="Foo">ISBN 123456789x</a></p>
-<p><a rel="mw:ExtLink" href="http://foo.com">http://example.com</a>
-<a rel="mw:ExtLink" href="http://foo.com">RFC 1234</a>
-<a rel="mw:ExtLink" href="http://foo.com">PMID 1234</a>
-<a rel="mw:ExtLink" href="http://foo.com">ISBN 123456789x</a></p>
+<p><a rel="mw:ExtLink" class="external text" href="http://foo.com">http://example.com</a>
+<a rel="mw:ExtLink" class="external text" href="http://foo.com">RFC 1234</a>
+<a rel="mw:ExtLink" class="external text" href="http://foo.com">PMID 1234</a>
+<a rel="mw:ExtLink" class="external text" href="http://foo.com">ISBN 123456789x</a></p>
!! end
!! test
@@ -28019,38 +28439,14 @@ Magic links inside image captions (autolinked)
[[File:Foobar.jpg|thumb|PMID 1234]]
[[File:Foobar.jpg|thumb|ISBN 123456789x]]
!! html+tidy
-<div class="thumb tright">
-<div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a>
-<div class="thumbcaption">
-<div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>
-<a rel="nofollow" class="external free" href="http://example.com">http://example.com</a></div>
-</div>
-</div>
-<div class="thumb tright">
-<div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a>
-<div class="thumbcaption">
-<div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>
-<a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc1234">RFC 1234</a></div>
-</div>
-</div>
-<div class="thumb tright">
-<div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a>
-<div class="thumbcaption">
-<div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>
-<a class="external mw-magiclink-pmid" rel="nofollow" href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract">PMID 1234</a></div>
-</div>
-</div>
-<div class="thumb tright">
-<div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a>
-<div class="thumbcaption">
-<div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>
-<a href="/wiki/Special:BookSources/123456789X" class="internal mw-magiclink-isbn">ISBN 123456789x</a></div>
-</div>
-</div>
-!! html/parsoid
-<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption><a rel="mw:ExtLink" href="http://example.com">http://example.com</a></figcaption></figure>
-<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption><a href="//tools.ietf.org/html/rfc1234" rel="mw:ExtLink">RFC 1234</a></figcaption></figure>
-<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption><a href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract" rel="mw:ExtLink">PMID 1234</a></figcaption></figure>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a></div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div><a class="external mw-magiclink-rfc" rel="nofollow" href="https://tools.ietf.org/html/rfc1234">RFC 1234</a></div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div><a class="external mw-magiclink-pmid" rel="nofollow" href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract">PMID 1234</a></div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div><a href="/wiki/Special:BookSources/123456789X" class="internal mw-magiclink-isbn">ISBN 123456789x</a></div></div></div>
+!! html/parsoid
+<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption><a rel="mw:ExtLink" class="external free" href="http://example.com">http://example.com</a></figcaption></figure>
+<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption><a href="https://tools.ietf.org/html/rfc1234" rel="mw:ExtLink" class="external text">RFC 1234</a></figcaption></figure>
+<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption><a href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract" rel="mw:ExtLink" class="external text">PMID 1234</a></figcaption></figure>
<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption><a href="./Special:BookSources/123456789X" rel="mw:WikiLink">ISBN 123456789x</a></figcaption></figure>
!! end
@@ -28113,31 +28509,34 @@ parsoid=html2wt,wt2wt
|<nowiki>- </nowiki>
|-
|<small>-</small>
-|<br>
+|<br />
-
-|<br>
+|<br />
-
|}
!! html/php+tidy
<table>
+<tbody><tr>
+<th>-
+</th>
+<th>-
+</th></tr>
<tr>
-<th>-</th>
-<th>-</th>
-</tr>
-<tr>
-<td>-</td>
-<td>-</td>
-</tr>
+<td>-
+</td>
+<td>-
+</td></tr>
<tr>
-<td><small>-</small></td>
-<td><br />
-<p>-</p>
+<td><small>-</small>
</td>
<td><br />
-<p>-</p>
+<p>-
+</p>
</td>
-</tr>
-</table>
+<td><br />
+<p>-
+</p>
+</td></tr></tbody></table>
!! end
!! test
@@ -28149,17 +28548,17 @@ parsoid=html2wt
<tbody>
<tr><td>a
b
-</td><td data-parsoid='{"stx_v":"row"}'>c</td></tr>
+</td><td data-parsoid='{"stx":"row"}'>c</td></tr>
<tr><td><p>x</p>
-</td><td data-parsoid='{"stx_v":"row", "startTagSrc": "{{!}}{{!}}"}'>y</td></tr>
+</td><td data-parsoid='{"stx":"row", "startTagSrc": "{{!}}{{!}}"}'>y</td></tr>
</tbody></table>
<table>
<tbody>
<tr><th>a
b
-</th><th data-parsoid='{"stx_v":"row"}'>c</th></tr>
+</th><th data-parsoid='{"stx":"row"}'>c</th></tr>
<tr><th><p>x</h>
-</th><th data-parsoid='{"stx_v":"row"}'>y</th></tr>
+</th><th data-parsoid='{"stx":"row"}'>y</th></tr>
</tbody></table>
!! wikitext
{|
@@ -28321,7 +28720,15 @@ parsoid=wt2html
!! wikitext
{{echo|hi}}[http://example.com [[ho]]]
!! html/parsoid
-<p><span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"hi"}},"i":0}}]}'>hi</span><a rel="mw:ExtLink" href="http://example.com"></a><a rel="mw:WikiLink" href="./Ho" title="Ho" data-parsoid='{"misnested":true}'>ho</a></p>
+<p><span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"hi"}},"i":0}}]}'>hi</span><a rel="mw:ExtLink" class="external autonumber" href="http://example.com"></a><a rel="mw:WikiLink" href="./Ho" title="Ho" data-parsoid='{"misnested":true}'>ho</a></p>
+!! end
+
+!! test
+Catch regression when unpacking with trailing content
+!! wikitext
+{{echo|Foo <references/> bar}}
+!! html/parsoid
+<p about="#mwt2" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"Foo &lt;references/> bar"}},"i":0}}]}'>Foo </p><ol class="mw-references references" typeof="mw:Extension/references" about="#mwt2" data-mw='{"name":"references","attrs":{}}'></ol><p about="#mwt2"> bar</p>
!! end
!! test
@@ -28557,7 +28964,7 @@ parsoid={
!! html/parsoid
<h2>foo<br/>bar</h2>
!! wikitext
-== foo<br> bar ==
+== foo<br /> bar ==
!! end
!! test
@@ -29088,6 +29495,64 @@ y
}}
!! end
+!! test
+New list is serialized on newlines
+!! options
+parsoid=html2wt
+!! html/parsoid
+<p>The quick brown fox jumps over the lazy dog.</p><ul>
+<li>Yesterday</li>
+<li>Today</li>
+<li>Tomorrow</li>
+</ul><p>The quick onyx goblin jumps over the lazy dwarf.</p>
+!! wikitext
+The quick brown fox jumps over the lazy dog.
+
+* Yesterday
+* Today
+* Tomorrow
+
+The quick onyx goblin jumps over the lazy dwarf.
+!! end
+
+!! test
+New lists in formatting elements serialized w/o newlines
+!! options
+parsoid=html2wt
+!! html/parsoid
+<small>
+
+<ul>
+<li>123</li>
+</ul>
+
+</small>
+
+<small><ul><li>hi</li></ul></small>
+!! wikitext
+<small>
+* 123
+</small>
+
+<small>
+* hi
+</small>
+!! end
+
+!! test
+New list in table doesn't need newlines
+!! options
+parsoid=html2wt
+!! html/parsoid
+<table><tr><td><ul><li>test</li><li>123</li></td></tr></table>
+!! wikitext
+{|
+|
+* test
+* 123
+|}
+!! end
+
# ---------------------------------------------------
# End of tests spec'ing wikitext serialization norms |
# ---------------------------------------------------
@@ -29267,17 +29732,15 @@ parsoid={
!! test
Empty LI (T49673)
!! wikitext
-* a
+*a
*
*
-* b
-!! html/php+tidy
-<ul>
-<li>a</li>
+*b
+!! html+tidy
+<ul><li>a</li>
<li class="mw-empty-elt"></li>
<li class="mw-empty-elt"></li>
-<li>b</li>
-</ul>
+<li>b</li></ul>
!! end
!! test
@@ -29285,13 +29748,9 @@ Thumbnail output
!! wikitext
[[File:Thumb.png|thumb]]
!! html/php+tidy
-<div class="thumb tright">
-<div class="thumbinner" style="width:137px;"><a href="/wiki/File:Thumb.png" class="image"><img alt="Thumb.png" src="http://example.com/images/e/ea/Thumb.png" width="135" height="135" class="thumbimage" /></a>
-<div class="thumbcaption">
-<div class="magnify"><a href="/wiki/File:Thumb.png" class="internal" title="Enlarge"></a></div>
-</div>
-</div>
-</div>
+<div class="thumb tright"><div class="thumbinner" style="width:137px;"><a href="/wiki/File:Thumb.png" class="image"><img alt="Thumb.png" src="http://example.com/images/e/ea/Thumb.png" width="135" height="135" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Thumb.png" class="internal" title="Enlarge"></a></div></div></div></div>
+!! html/parsoid
+<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Thumb.png"><img resource="./File:Thumb.png" src="//example.com/images/e/ea/Thumb.png" data-file-width="135" data-file-height="135" data-file-type="bitmap" height="135" width="135"/></a></figure>
!! end
!! test
@@ -29305,16 +29764,14 @@ unclosed internal link XSS (T137264)
<p>[[#%3Cscript%3Ealert(1)%3C/script%3E|</p>
!! end
-# Use $wgRawHtml to inject a <style> tag, since you normally can't in wikitext
-# (Parsoid doesn't support $wgRawHtml==true)
!! test
Validating that <style> isn't eaten by tidy (T167349)
!! options
-wgRawHtml=1
+styletag=1
!! wikitext
<div class="foo">
-<html><style>.foo::before { content: "<foo>"; }</style></html>
-<html><style data-mw-foobar="baz">.foo::after { content: "<bar>"; }</style></html>
+<style>.foo::before { content: "<foo>"; }</style>
+<style data-mw-foobar="baz">.foo::after { content: "<bar>"; }</style>
</div>
!! html/php+tidy
<div class="foo">
@@ -29324,9 +29781,107 @@ wgRawHtml=1
!! end
!! test
+Validating that <style> isn't wrapped in a paragraph (T186965)
+!! options
+styletag=1
+!! wikitext
+A style tag, by itself or with other style/link tags, shouldn't be wrapped in a paragraph
+
+<style>.foo::before { content: "<foo>"; }</style>
+
+<style>.foo::before { content: "<foo>"; }</style> <link rel="foo" href="bar"/><style>.foo::before { content: "<foo>"; }</style>
+
+But if it's on a line with other content, let it be wrapped.
+
+<style>.foo::before { content: "<foo>"; }</style> bar
+
+foo <style>.foo::before { content: "<foo>"; }</style>
+
+foo <style>.foo::before { content: "<foo>"; }</style> bar
+
+And the same if we have non-paragraph-breaking whitespace
+
+foo
+<style>.foo::before { content: "<foo>"; }</style>
+bar
+!! html/php
+<p>A style tag, by itself or with other style/link tags, shouldn't be wrapped in a paragraph
+</p>
+<style>.foo::before { content: "<foo>"; }</style>
+<style>.foo::before { content: "<foo>"; }</style> <link rel="foo" href="bar"/><style>.foo::before { content: "<foo>"; }</style>
+<p>But if it's on a line with other content, let it be wrapped.
+</p><p><style>.foo::before { content: "<foo>"; }</style> bar
+</p><p>foo <style>.foo::before { content: "<foo>"; }</style>
+</p><p>foo <style>.foo::before { content: "<foo>"; }</style> bar
+</p><p>And the same if we have non-paragraph-breaking whitespace
+</p><p>foo
+<style>.foo::before { content: "<foo>"; }</style>
+bar
+</p>
+!! end
+
+!! test
+Validating that <link> isn't wrapped in a paragraph (T186965)
+!! options
+styletag=1
+!! wikitext
+A link tag, by itself or with other style/link tags, shouldn't be wrapped in a paragraph
+
+<link rel="foo" href="bar"/>
+
+<link rel="foo" href="bar"/> <style>.foo::before { content: "<foo>"; }</style><link rel="foo" href="bar"/>
+
+But if it's on a line with other content, let it be wrapped.
+
+<link rel="foo" href="bar"/> bar
+
+foo <link rel="foo" href="bar"/>
+
+foo <link rel="foo" href="bar"/> bar
+
+And the same if we have non-paragraph-breaking whitespace
+
+foo
+<link rel="foo" href="bar"/>
+bar
+!! html/php
+<p>A link tag, by itself or with other style/link tags, shouldn't be wrapped in a paragraph
+</p>
+<link rel="foo" href="bar"/>
+<link rel="foo" href="bar"/> <style>.foo::before { content: "<foo>"; }</style><link rel="foo" href="bar"/>
+<p>But if it's on a line with other content, let it be wrapped.
+</p><p><link rel="foo" href="bar"/> bar
+</p><p>foo <link rel="foo" href="bar"/>
+</p><p>foo <link rel="foo" href="bar"/> bar
+</p><p>And the same if we have non-paragraph-breaking whitespace
+</p><p>foo
+<link rel="foo" href="bar"/>
+bar
+</p>
+!! end
+
+!! test
Decoding of HTML entities in headings and links for IDs and link fragments (T103714)
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
+!! wikitext
+==A&B&amp;C&amp;amp;D&amp;amp;amp;E==
+[[#A&B&amp;C&amp;amp;D&amp;amp;amp;E]]
+!! html/php
+<h2><span id="A.26B.26C.26amp.3BD.26amp.3Bamp.3BE"></span><span class="mw-headline" id="A&amp;B&amp;C&amp;amp;D&amp;amp;amp;E">A&amp;B&amp;C&amp;amp;D&amp;amp;amp;E</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: A&amp;B&amp;C&amp;amp;D&amp;amp;amp;E">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p><a href="#A&amp;B&amp;C&amp;amp;D&amp;amp;amp;E">#A&amp;B&amp;C&amp;amp;D&amp;amp;amp;E</a>
+</p>
+!! html/parsoid
+<h2 id="A&amp;B&amp;C&amp;amp;D&amp;amp;amp;E"><span id="A.26B.26C.26amp.3BD.26amp.3Bamp.3BE" typeof="mw:FallbackId" data-parsoid="{}"></span>A&amp;B<span typeof="mw:Entity" data-parsoid='{"src":"&amp;amp;","srcContent":"&amp;"}'>&amp;</span>C<span typeof="mw:Entity" data-parsoid='{"src":"&amp;amp;","srcContent":"&amp;"}'>&amp;</span>amp;D<span typeof="mw:Entity" data-parsoid='{"src":"&amp;amp;","srcContent":"&amp;"}'>&amp;</span>amp;amp;E</h2>
+<p><a rel="mw:WikiLink" href="./Main_Page#A&amp;B&amp;C&amp;amp;D&amp;amp;amp;E" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#A&amp;B&amp;C&amp;amp;D&amp;amp;amp;E"},"sa":{"href":"#A&amp;B&amp;amp;C&amp;amp;amp;D&amp;amp;amp;amp;E"}}'>#A&amp;B&amp;C&amp;amp;D&amp;amp;amp;E</a></p>
+!! end
+
+!! test
+Decoding of HTML entities in headings and links for IDs and link fragments (T103714) (legacy)
+!! config
+wgFragmentMode=[ 'legacy' ]
!! wikitext
-== A&B&amp;C&amp;amp;D&amp;amp;amp;E ==
+==A&B&amp;C&amp;amp;D&amp;amp;amp;E==
[[#A&B&amp;C&amp;amp;D&amp;amp;amp;E]]
!! html/php
<h2><span class="mw-headline" id="A.26B.26C.26amp.3BD.26amp.3Bamp.3BE">A&amp;B&amp;C&amp;amp;D&amp;amp;amp;E</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: A&amp;B&amp;C&amp;amp;D&amp;amp;amp;E">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
@@ -29335,32 +29890,61 @@ Decoding of HTML entities in headings and links for IDs and link fragments (T103
!! end
!! test
+Decoding of HTML entities in embedded HTML tags
+!! wikitext
+<table class="1&2&amp;3&amp;amp;4&amp;amp;amp;5"><tr><td>x</td></tr></table>
+!! html/php
+<table class="1&amp;2&amp;3&amp;amp;4&amp;amp;amp;5"><tr><td>x</td></tr></table>
+
+!! html/parsoid
+<table class="1&amp;2&amp;3&amp;amp;4&amp;amp;amp;5" data-parsoid='{"stx":"html","a":{"class":"1&amp;2&amp;3&amp;amp;4&amp;amp;amp;5"},"sa":{"class":"1&amp;2&amp;amp;3&amp;amp;amp;4&amp;amp;amp;amp;5"}}'><tbody><tr data-parsoid='{"stx":"html"}'><td data-parsoid='{"stx":"html"}'>x</td></tr></tbody></table>
+!! end
+
+!! test
Decoding of HTML entities in indicator names for IDs (T104196)
!! options
+parsoid=wt2html,html2html
showindicators
!! wikitext
<indicator name="1&2&amp;3&amp;amp;4&amp;amp;amp;5">Indicator</indicator>
!! html/php
1&2&3&amp;4&amp;amp;5=Indicator
+!! html/parsoid
+<p><span typeof="mw:Extension/indicator" about="#mwt3" data-mw='{"name":"indicator","attrs":{"name":"1&amp;2&amp;3&amp;amp;4&amp;amp;amp;5"},"body":{"extsrc":"Indicator"}}'></span></p>
+!! end
+
+# this version of the test strips out the ambiguity so Parsoid rts cleanly
+!! test
+Decoding of HTML entities in indicator names for IDs (unambiguous) (T104196)
+!! options
+showindicators
+!! wikitext
+<indicator name="1&2&3&amp;amp;4&amp;amp;amp;5">Indicator</indicator>
+!! html/php
+1&2&3&amp;4&amp;amp;5=Indicator
+
+!! html/parsoid
+<p><span typeof="mw:Extension/indicator" about="#mwt3" data-mw='{"name":"indicator","attrs":{"name":"1&amp;2&amp;3&amp;amp;4&amp;amp;amp;5"},"body":{"extsrc":"Indicator"}}'></span></p>
!! end
+# This fragment mode is what Parsoid supports.
!! test
HTML5 ids: fallback to legacy
!! config
wgFragmentMode=[ 'html5', 'legacy' ]
!! wikitext
-== Foo bar ==
+==Foo bar==
-== foo Bar ==
+==foo Bar==
-== Тест ==
+==Тест==
-== Тест ==
+==Тест==
-== тест ==
+==тест==
-== Hey < # " > % : ' ==
+==Hey < # " > % : '==
[[#Foo bar]] [[#foo Bar]] [[#Тест]] [[#тест]] [[#Hey < # " > % : ']]
{{anchorencode:💩}} <span id="{{anchorencode:💩}}"></span>
@@ -29369,7 +29953,7 @@ wgFragmentMode=[ 'html5', 'legacy' ]
[[#啤酒]] [[#%E5%95%A4%E9%85%92]]
!! html/php
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#Foo_bar"><span class="tocnumber">1</span> <span class="toctext">Foo bar</span></a></li>
<li class="toclevel-1 tocsection-2"><a href="#foo_Bar_2"><span class="tocnumber">2</span> <span class="toctext">foo Bar</span></a></li>
@@ -29390,24 +29974,43 @@ wgFragmentMode=[ 'html5', 'legacy' ]
</p><p>💩 <span id="💩"></span>
</p><p><a href="#啤酒">#啤酒</a> <a href="#啤酒">#啤酒</a>
</p>
+!! html/parsoid
+<h2 id="Foo_bar">Foo bar</h2>
+
+<h2 id="foo_Bar_2">foo Bar</h2>
+
+<h2 id="Тест"><span id=".D0.A2.D0.B5.D1.81.D1.82" typeof="mw:FallbackId"></span>Тест</h2>
+
+<h2 id="Тест_2"><span id=".D0.A2.D0.B5.D1.81.D1.82_2" typeof="mw:FallbackId"></span>Тест</h2>
+
+<h2 id="тест"><span id=".D1.82.D0.B5.D1.81.D1.82" typeof="mw:FallbackId"></span>тест</h2>
+
+<h2 id="Hey_&lt;_#_&quot;_>_%_:_'"><span id="Hey_.3C_.23_.22_.3E_.25_:_.27" typeof="mw:FallbackId"></span>Hey &lt; # " > %<span typeof="mw:DisplaySpace mw:Placeholder" data-parsoid='{"src":" ","isDisplayHack":true}'> </span>: '</h2>
+<p><a rel="mw:WikiLink" href="./Main_Page#Foo_bar">#Foo bar</a> <a rel="mw:WikiLink" href="./Main_Page#foo_Bar">#foo Bar</a> <a rel="mw:WikiLink" href="./Main_Page#Тест">#Тест</a> <a rel="mw:WikiLink" href="./Main_Page#тест">#тест</a> <a rel="mw:WikiLink" href="./Main_Page#Hey_&lt;_#_&quot;_>_%_:_'" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#Hey_&lt;_#_\"_>_%_:_&#39;"},"sa":{"href":"#Hey &lt; # \" > % : &#39;"}}'>#Hey &lt; # " > % : '</a></p>
+
+<p><span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"anchorencode:💩","function":"anchorencode"},"params":{},"i":0}}]}'>💩</span> <span id="💩" about="#mwt3" typeof="mw:ExpandedAttrs" data-mw='{"attribs":[[{"txt":"id"},{"html":"&lt;span about=\"#mwt2\" typeof=\"mw:Transclusion\" data-parsoid=&#39;{\"pi\":[[]],\"dsr\":[178,197,null,null]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"anchorencode:💩\",\"function\":\"anchorencode\"},\"params\":{},\"i\":0}}]}&#39;>💩&lt;/span>"}]]}'></span></p>
+
+<!-- These two links should produce identical HTML -->
+<p><a rel="mw:WikiLink" href="./Main_Page#啤酒">#啤酒</a> <a rel="mw:WikiLink" href="./Main_Page#啤酒" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#啤酒"},"sa":{"href":"#%E5%95%A4%E9%85%92"}}'>#啤酒</a></p>
!! end
+# Parsoid doesn't support this mode
!! test
HTML5 ids: legacy with a fallback to modern
!! config
wgFragmentMode=[ 'legacy', 'html5' ]
!! wikitext
-== Foo bar ==
+==Foo bar==
-== foo Bar ==
+==foo Bar==
-== Тест ==
+==Тест==
-== Тест ==
+==Тест==
-== тест ==
+==тест==
-== Hey < # " > % : ' ==
+==Hey < # " > % : '==
[[#Foo bar]] [[#foo Bar]] [[#Тест]] [[#тест]] [[#Hey < # " > % : ']]
{{anchorencode:💩}} <span id="{{anchorencode:💩}}"></span>
@@ -29416,7 +30019,7 @@ wgFragmentMode=[ 'legacy', 'html5' ]
[[#啤酒]] [[#%E5%95%A4%E9%85%92]]
!! html/php
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#Foo_bar"><span class="tocnumber">1</span> <span class="toctext">Foo bar</span></a></li>
<li class="toclevel-1 tocsection-2"><a href="#foo_Bar_2"><span class="tocnumber">2</span> <span class="toctext">foo Bar</span></a></li>
@@ -29439,22 +30042,23 @@ wgFragmentMode=[ 'legacy', 'html5' ]
</p>
!! end
+# Parsoid doesn't support this mode.
!! test
HTML5 ids: no legacy
!! config
wgFragmentMode=[ 'html5' ]
!! wikitext
-== Foo bar ==
+==Foo bar==
-== foo Bar ==
+==foo Bar==
-== Тест ==
+==Тест==
-== Тест ==
+==Тест==
-== тест ==
+==тест==
-== Hey < # " > % : ' ==
+==Hey < # " > % : '==
[[#Foo bar]] [[#foo Bar]] [[#Тест]] [[#тест]] [[#Hey < # " > % : ']]
{{anchorencode:💩}} <span id="{{anchorencode:💩}}"></span>
@@ -29463,7 +30067,7 @@ wgFragmentMode=[ 'html5' ]
[[#啤酒]] [[#%E5%95%A4%E9%85%92]]
!! html/php
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<div id="toc" class="toc"><div class="toctitle" lang="en" dir="ltr"><h2>Contents</h2></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#Foo_bar"><span class="tocnumber">1</span> <span class="toctext">Foo bar</span></a></li>
<li class="toclevel-1 tocsection-2"><a href="#foo_Bar_2"><span class="tocnumber">2</span> <span class="toctext">foo Bar</span></a></li>
@@ -29485,3 +30089,741 @@ wgFragmentMode=[ 'html5' ]
</p><p><a href="#啤酒">#啤酒</a> <a href="#啤酒">#啤酒</a>
</p>
!! end
+
+!! test
+T90902: Normalize weird characters in section IDs
+!! config
+wgFragmentMode=[ 'html5', 'legacy' ]
+!! wikitext
+==Foo&nbsp;bar==
+[[#Foo&nbsp;bar]]
+
+!! html/php
+<h2><span class="mw-headline" id="Foo_bar">Foo&#160;bar</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Foo bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p><a href="#Foo_bar">#Foo&#160;bar</a>
+</p>
+!! html/parsoid
+<h2 id="Foo_bar"> Foo<span typeof="mw:Entity" data-parsoid='{"src":"&amp;nbsp;","srcContent":" "}'> </span>bar </h2>
+<p><a rel="mw:WikiLink" href="./Main_Page#Foo_bar" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#Foo_bar"},"sa":{"href":"#Foo&amp;nbsp;bar"}}'>#Foo bar</a></p>
+!! end
+
+!! test
+T51672: Test for brackets in attributes of elements in external link texts
+!! wikitext
+[http://example.com/ link <span title="title with [brackets]">span</span>]
+[http://example.com/ link <span title="title with &#91;brackets&#93;">span</span>]
+
+!! html/php
+<p><a rel="nofollow" class="external text" href="http://example.com/">link <span title="title with &#91;brackets&#93;">span</span></a>
+<a rel="nofollow" class="external text" href="http://example.com/">link <span title="title with &#91;brackets&#93;">span</span></a>
+</p>
+!! html/parsoid
+<p><a rel="mw:ExtLink" class="external text" href="http://example.com/">link <span title="title with [brackets]">span</span></a>
+<a rel="mw:ExtLink" class="external text" href="http://example.com/">link <span title="title with [brackets]" data-parsoid='{"stx":"html","a":{"title":"title with [brackets]"},"sa":{"title":"title with &amp;#91;brackets&amp;#93;"}}'>span</span></a></p>
+!! end
+
+!! test
+T72875: Test for brackets in attributes of elements in internal link texts
+!! wikitext
+[[Foo|link <span title="title with [[double brackets]]">span</span>]]
+[[Foo|link <span title="title with &#91;&#91;double brackets&#93;&#93;">span</span>]]
+
+!! html/php
+<p><a href="/wiki/Foo" title="Foo">link <span title="title with &#91;&#91;double brackets&#93;&#93;">span</span></a>
+<a href="/wiki/Foo" title="Foo">link <span title="title with &#91;&#91;double brackets&#93;&#93;">span</span></a>
+</p>
+!! html/parsoid
+<p><a rel="mw:WikiLink" href="./Foo" title="Foo">link <span title="title with [[double brackets]]">span</span></a>
+<a rel="mw:WikiLink" href="./Foo" title="Foo">link <span title="title with [[double brackets]]" data-parsoid='{"stx":"html","a":{"title":"title with [[double brackets]]"},"sa":{"title":"title with &amp;#91;&amp;#91;double brackets&amp;#93;&amp;#93;"}}'>span</span></a></p>
+!! end
+
+!! test
+T179544: {{anchorencode:}} output should be always usable in links
+!! config
+wgFragmentMode=[ 'html5' ]
+!! wikitext
+<span id="{{anchorencode:[foo]}}"></span>[[#{{anchorencode:[foo]}}]]
+!! html/php
+<p><span id="&#91;foo&#93;"></span><a href="#[foo]">#&#91;foo&#93;</a>
+</p>
+!! html/parsoid
+<p><span id="[foo]" about="#mwt3" typeof="mw:ExpandedAttrs" data-parsoid='{"stx":"html","a":{"id":"[foo]"},"sa":{"id":"{{anchorencode:[foo]}}"}}' data-mw='{"attribs":[[{"txt":"id"},{"html":"&lt;span typeof=\"mw:Transclusion mw:Entity\" about=\"#mwt1\" data-parsoid=&apos;{\"srcContent\":\"[\",\"dsr\":[10,32,null,null],\"pi\":[[]]}&apos; data-mw=&apos;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"anchorencode:[foo]\",\"function\":\"anchorencode\"},\"params\":{},\"i\":0}}]}&apos;>[&lt;/span>&lt;span about=\"#mwt1\" data-parsoid=\"{}\">foo&lt;/span>&lt;span typeof=\"mw:Entity\" about=\"#mwt1\" data-parsoid=&apos;{\"src\":\"&amp;amp;#x5D;\",\"srcContent\":\"]\"}&apos;>]&lt;/span>"}]]}'></span><a typeof="mw:ExpandedAttrs" about="#mwt4" rel="mw:WikiLink" href="./Main_Page#[foo]" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#[foo]"},"sa":{"href":"#{{anchorencode:[foo]}}"}}' data-mw='{"attribs":[[{"txt":"href"},{"html":"#&lt;span typeof=\"mw:Transclusion mw:Entity\" about=\"#mwt2\" data-parsoid=&apos;{\"srcContent\":\"[\",\"dsr\":[44,66,null,null],\"pi\":[[]]}&apos; data-mw=&apos;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"anchorencode:[foo]\",\"function\":\"anchorencode\"},\"params\":{},\"i\":0}}]}&apos;>[&lt;/span>&lt;span about=\"#mwt2\" data-parsoid=\"{}\">foo&lt;/span>&lt;span typeof=\"mw:Entity\" about=\"#mwt2\" data-parsoid=&apos;{\"src\":\"&amp;amp;#x5D;\",\"srcContent\":\"]\"}&apos;>]&lt;/span>"}]]}'>#[foo]</a></p>
+!! end
+
+## ------------------------------
+## Parsoid section-wrapping tests
+## ------------------------------
+!! test
+Section wrapping for well-nested sections (no leading content)
+!! options
+parsoid={
+ "wrapSections": true
+}
+!! wikitext
+=1=
+a
+
+=2=
+b
+
+==2.1==
+c
+
+==2.2==
+d
+
+===2.2.1===
+e
+
+=3=
+f
+!! html/parsoid
+<section data-mw-section-id="0"></section><section data-mw-section-id="1"><h1 id="1">1</h1>
+<p>a</p>
+
+</section><section data-mw-section-id="2"><h1 id="2">2</h1>
+<p>b</p>
+
+<section data-mw-section-id="3"><h2 id="2.1">2.1</h2>
+<p>c</p>
+
+</section><section data-mw-section-id="4"><h2 id="2.2">2.2</h2>
+<p>d</p>
+
+<section data-mw-section-id="5"><h3 id="2.2.1">2.2.1</h3>
+<p>e</p>
+
+</section></section></section><section data-mw-section-id="6"><h1 id="3">3</h1>
+<p>f</p>
+
+</section>
+!! end
+
+!! test
+Section wrapping for well-nested sections (with leading content)
+!! options
+parsoid={
+ "wrapSections": true
+}
+!! wikitext
+Para 1.
+
+Para 2 with a <div>nested in it</div>
+
+Para 3.
+
+=1=
+a
+
+=2=
+b
+
+==2.1==
+c
+!! html/parsoid
+<section data-mw-section-id="0"><p>Para 1.</p>
+
+<p>Para 2 with a </p><div>nested in it</div>
+
+<p>Para 3.</p>
+
+</section><section data-mw-section-id="1"><h1 id="1">1</h1>
+<p>a</p>
+
+</section><section data-mw-section-id="2"><h1 id="2">2</h1>
+<p>b</p>
+
+<section data-mw-section-id="3"><h2 id="2.1">2.1</h2>
+<p>c</p>
+
+</section></section>
+!! end
+
+!! test
+Section wrapping with template-generated sections (good nesting 1)
+!! options
+parsoid={
+ "wrapSections": true
+}
+!! wikitext
+=1=
+a
+
+{{echo|1=
+==1.1==
+b
+}}
+
+==1.2==
+c
+
+=2=
+d
+!! html/parsoid
+<section data-mw-section-id="0"></section><section data-mw-section-id="1"><h1 id="1">1</h1>
+<p>a</p>
+
+<section data-mw-section-id="-1"><h2 about="#mwt1" typeof="mw:Transclusion" id="1.1" data-parsoid='{"dsr":[9,33,null,null],"pi":[[{"k":"1","named":true,"spc":["","","\n","\n"]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"==1.1==\nb"}},"i":0}}]}'>1.1</h2><span about="#mwt1">
+</span><p about="#mwt1">b</p>
+</section><section data-mw-section-id="3"><h2 id="1.2">1.2</h2>
+<p>c</p>
+
+</section></section><section data-mw-section-id="4"><h1 id="2">2</h1>
+<p>d</p></section>
+!! end
+
+# In this example, the template scope is mildly expanded to incorporate the
+# trailing newline after the transclusion since that is part of section 1.1.1
+!! test
+Section wrapping with template-generated sections (good nesting 2)
+!! options
+parsoid={
+ "wrapSections": true,
+ "modes": ["wt2html", "wt2wt"]
+}
+!! wikitext
+=1=
+a
+
+{{echo|1=
+==1.1==
+b
+===1.1.1===
+d
+}}
+=2=
+e
+!! html/parsoid
+<section data-mw-section-id="0"></section><section data-mw-section-id="1"><h1 id="1">1</h1>
+<p>a</p>
+
+<section data-mw-section-id="-1"><h2 about="#mwt1" typeof="mw:Transclusion" id="1.1" data-parsoid='{"dsr":[9,50,null,null],"pi":[[{"k":"1","named":true,"spc":["","","\n","\n"]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"==1.1==\nb\n===1.1.1===\nd"}},"i":0}},"\n"]}'>1.1</h2><span about="#mwt1">
+</span><p about="#mwt1">b</p><span about="#mwt1">
+</span><section data-mw-section-id="-1" about="#mwt1"><h3 about="#mwt1" id="1.1.1">1.1.1</h3><span about="#mwt1">
+</span><p about="#mwt1">d</p><span about="#mwt1">
+</span></section></section></section><section data-mw-section-id="4" data-parsoid="{}"><h1 id="2">2</h1>
+<p>e</p></section>
+!! end
+
+# In this example, the template scope is mildly expanded to incorporate the
+# trailing newline after the transclusion since that is part of section 1.2.1
+!! test
+Section wrapping with template-generated sections (good nesting 3)
+!! options
+parsoid={
+ "wrapSections": true,
+ "modes": ["wt2html", "wt2wt"]
+}
+!! wikitext
+=1=
+a
+
+{{echo|1=
+x
+==1.1==
+b
+==1.2==
+c
+===1.2.1===
+d
+}}
+=2=
+e
+!! html/parsoid
+<section data-mw-section-id="0"></section><section data-mw-section-id="1" data-parsoid="{}"><h1 id="1"> 1 </h1>
+<p>a</p>
+
+<p about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"dsr":[9,60,0,0],"pi":[[{"k":"1","named":true,"spc":["","","\n","\n"]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"x\n==1.1==\nb\n==1.2==\nc\n===1.2.1===\nd"}},"i":0}},"\n"]}'>x</p><span about="#mwt1">
+</span><section data-mw-section-id="-1" about="#mwt1"><h2 about="#mwt1" id="1.1">1.1</h2><span about="#mwt1">
+</span><p about="#mwt1">b</p><span about="#mwt1">
+</span></section><section data-mw-section-id="-1" about="#mwt1"><h2 about="#mwt1" id="1.2">1.2</h2><span about="#mwt1">
+</span><p about="#mwt1">c</p><span about="#mwt1">
+</span><section data-mw-section-id="-1" about="#mwt1"><h3 about="#mwt1" id="1.2.1">1.2.1</h3><span about="#mwt1">
+</span><p about="#mwt1">d</p><span about="#mwt1">
+</span></section></section></section><section data-mw-section-id="5"><h1 id="2">2</h1>
+<p>e</p></section>
+!! end
+
+# Because of section-wrapping and template-wrapping interactions,
+# the scope of the template is expanded so that the template markup
+# is valid in the presence of <section> tags.
+# This exercises the s1 is null scenario in the wrapSections code
+!! test
+Section wrapping with template-generated sections (bad nesting 1)
+!! options
+parsoid={
+ "wrapSections": true
+}
+!! wikitext
+<div>
+a
+
+{{echo|
+=1=
+b
+}}
+
+c
+</div>
+!! html/parsoid
+<section data-mw-section-id="-1"></section><section data-mw-section-id="-2"><div data-parsoid='{"stx":"html"}'>
+<p>a</p>
+
+<span about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[{"k":"1"}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"\n=1=\nb\n"}},"i":0}},"\n\nc\n"]}'>
+</span><section data-mw-section-id="-1" about="#mwt1"><h1 about="#mwt1" id="1">1</h1><span about="#mwt1">
+</span><p about="#mwt1">b
+</p><span about="#mwt1">
+
+</span><p about="#mwt1">c</p><span about="#mwt1">
+</span></section></div></section>
+!! end
+
+# Because of section-wrapping and template-wrapping interactions,
+# the scope of the template is expanded so that the template markup
+# is valid in the presence of <section> tags.
+# This exercises the s1 is ancestor of s2 scenario in the wrapSections code
+!! test
+Section wrapping with template-generated sections (bad nesting 2)
+!! options
+parsoid={
+ "wrapSections": true
+}
+!! wikitext
+=1=
+a
+
+{{echo|1=
+=2=
+b
+==2.1==
+c
+}}
+
+d
+
+=3=
+e
+!! html/parsoid
+<section data-mw-section-id="0"></section><section data-mw-section-id="1"><h1 id="1">1</h1>
+<p>a</p>
+
+</section><section data-mw-section-id="-1"><h1 about="#mwt1" typeof="mw:Transclusion" id="2" data-parsoid='{"dsr":[9,45,null,null],"pi":[[{"k":"1","named":true,"spc":["","","\n","\n"]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"=2=\nb\n==2.1==\nc"}},"i":0}},"\n\nd\n\n"]}'>2</h1><span about="#mwt1">
+</span><p about="#mwt1">b</p><span about="#mwt1">
+</span><section data-mw-section-id="-1" about="#mwt1"><h2 about="#mwt1" id="2.1">2.1</h2><span about="#mwt1">
+</span><p about="#mwt1">c</p><span about="#mwt1">
+
+</span><p about="#mwt1">d</p><span about="#mwt1">
+
+</span></section></section><section data-mw-section-id="4"><h1 id="3">3</h1>
+<p>e</p></section>
+!! end
+
+# Because of section-wrapping and template-wrapping interactions,
+# additional template wrappers are added to <section> tags
+# so that template wrapping semantics are valid whether section
+# tags are retained or stripped. But, the template scope can expand
+# greatly when accounting for section tags.
+# This exercises the s1 and s2 are in different subtrees scenario
+!! test
+Section wrapping with template-generated sections (bad nesting 3)
+!! options
+parsoid={
+ "wrapSections": true,
+ "modes": ["wt2html", "wt2wt"]
+}
+!! wikitext
+=1=
+a
+
+{{echo|1=
+==1.2==
+b
+=2=
+c
+}}
+
+d
+
+=3=
+e
+!! html/parsoid
+<section data-mw-section-id="0"></section><section data-mw-section-id="1" about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":["=1=\na\n\n",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"==1.2==\nb\n=2=\nc"}},"i":0}},"\n\nd\n\n"]}'><h1 id="1">1</h1>
+<p>a</p>
+
+<section data-mw-section-id="-1"><h2 about="#mwt1" typeof="mw:Transclusion" id="1.2" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"==1.2==\nb\n=2=\nc"}},"i":0}}]}'>1.2</h2><span about="#mwt1">
+</span><p about="#mwt1">b</p><span about="#mwt1">
+</span></section></section><section data-mw-section-id="-1" about="#mwt1"><h1 about="#mwt1" id="2">2</h1><span about="#mwt1">
+</span><p about="#mwt1">c</p>
+
+<p>d</p>
+</section><section data-mw-section-id="4" data-parsoid="{}"><h1 id="3">3</h1>
+<p>e</p></section>
+!! end
+
+!! test
+Section wrapping with uneditable lead section + div wrapping multiple sections
+!! options
+parsoid={
+ "wrapSections": true
+}
+!! wikitext
+foo
+
+<div style="border:1px solid red;">
+=1=
+a
+
+==1.1==
+b
+
+=2=
+c
+</div>
+
+=3=
+d
+
+==3.1==
+e
+!! html/parsoid
+<section data-mw-section-id="-1"><p>foo</p>
+
+</section><section data-mw-section-id="-2"><div style="border:1px solid red;">
+<section data-mw-section-id="1"><h1 id="1">1</h1>
+<p>a</p>
+
+<section data-mw-section-id="2"><h2 id="1.1">1.1</h2>
+<p>b</p>
+
+</section></section><section data-mw-section-id="-1"><h1 id="2">2</h1>
+<p>c</p>
+</section></div>
+
+</section><section data-mw-section-id="4"><h1 id="3">3</h1>
+<p>d</p>
+
+<section data-mw-section-id="5"><h2 id="3.1">3.1</h2>
+<p>e</p>
+</section></section>
+!! end
+
+!! test
+Section wrapping with editable lead section + div overlapping multiple sections
+!! options
+parsoid={
+ "wrapSections": true
+}
+!! wikitext
+foo
+
+=1=
+a
+<div style="border:1px solid red;">
+b
+
+==1.1==
+c
+
+=2=
+d
+</div>
+e
+
+=3=
+f
+
+==3.1==
+g
+!! html/parsoid
+<section data-mw-section-id="0"><p>foo</p>
+
+</section><section data-mw-section-id="-1"><h1 id="1">1</h1>
+<p>a</p>
+</section><section data-mw-section-id="-2"><div style="border:1px solid red;">
+<p>b</p>
+
+<section data-mw-section-id="2"><h2 id="1.1">1.1</h2>
+<p>c</p>
+
+</section><section data-mw-section-id="-1"><h1 id="2">2</h1>
+<p>d</p>
+</section></div>
+<p>e</p>
+
+</section><section data-mw-section-id="4"><h1 id="3">3</h1>
+<p>f</p>
+
+<section data-mw-section-id="5"><h2 id="3.1">3.1</h2>
+<p>g</p>
+</section></section>
+!! end
+
+!! test
+HTML header tags should not be wrapped in section tags
+!! options
+parsoid={
+ "wrapSections": true
+}
+!! wikitext
+foo
+
+<h1>a</h1>
+
+=b=
+
+<h1>c</h1>
+
+=d=
+!! html/parsoid
+<section data-mw-section-id="0"><p>foo</p>
+
+<h1 id="a" data-parsoid='{"stx":"html"}'>a</h1>
+
+</section><section data-mw-section-id="1"><h1 id="b">b</h1>
+
+<h1 id="c" data-parsoid='{"stx":"html"}'>c</h1>
+
+</section><section data-mw-section-id="2"><h1 id="d">d</h1></section>
+!! end
+
+!! test
+Lead section containing only whitespace and comments.
+!! options
+parsoid={
+ "wrapSections": true
+}
+!! wikitext
+
+<!-- this is a comment, presumably significant to editors -->
+=1=
+a
+
+=2=
+b
+!! html/parsoid
+<section data-mw-section-id="0" data-parsoid="{}">
+<!-- this is a comment, presumably significant to editors -->
+</section><section data-mw-section-id="1"><h1 id="1">1</h1>
+<p>a</p>
+
+</section><section data-mw-section-id="2"><h1 id="2">2</h1>
+<p>b</p></section>
+!! end
+
+!! test
+Pseudo-sections emitted by templates should have id -2
+!! options
+parsoid={
+ "wrapSections": true
+}
+!! wikitext
+foo
+{{echo|<div>
+==a==
+==b==
+</div>
+}}
+!! html/parsoid
+<section data-mw-section-id="-1"><p>foo</p>
+</section><section data-mw-section-id="-2"><div about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"stx":"html","pi":[[{"k":"1"}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;div>\n==a==\n==b==\n&lt;/div>\n"}},"i":0}}]}'>
+<section data-mw-section-id="-1"><h2 id="a">a</h2>
+</section><section data-mw-section-id="-1"><h2 id="b">b</h2>
+</section></div><span about="#mwt1">
+</span></section>
+!! end
+
+##########################################################################
+Tests demonstrating white-space insensitivity in input wikitext
+for wikitext headings, wikitext list items, and wikitext table captions,
+headings, and cells. HTML versions of the same should preserve whitespace.
+##########################################################################
+!! test
+Trim whitespace in wikitext headings, list items, table captions, headings, and cells
+!! wikitext
+__NOTOC__
+== <!--c1--> <!--c2--> Spaces <!--c3--> <!--c4--> ==
+== <!--c2--> <!--c2--> Tabs <!--c3--><!--c4--> ==
+* <!--c1--> <!--c2--> List item <!--c3--> <!--c4-->
+; <!--term to define--> term : <!--term's definition--> definition
+{|
+|+ <!--c1--> <!--c2--> Table Caption <!--c3--> <!--c4-->
+|-
+! <!--c1--> <!--c2--> Table Heading 1 <!--c3--> <!--c4--> !! Table Heading 2 <!--c5-->
+|-
+| <!--c1--> <!--c2--> Table Cell 1 <!--c3--> <!--c4--> || Table Cell 2 <!--c5-->
+|-
+| class="foo" || <!--c1--> <!--c2--> Table Cell 3 <!--c3--> <!--c4-->
+|-
+| <!--c1--> testing [[one|two]] <!--c2--> | <!--c3--> some content
+|}
+: {|
+ | <!--c1--> <!--c2--> Table Cell 1 <!--c3--> <!--c4--> || Table Cell 2 <!--c5-->
+ |} foo <!--c1-->
+!! html/php+tidy
+<h2><span class="mw-headline" id="Spaces">Spaces</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Spaces">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<h2><span class="mw-headline" id="Tabs">Tabs</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Tabs">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<ul><li>List item</li></ul>
+<dl><dt>term&#160;</dt>
+<dd>definition</dd></dl>
+<table>
+<caption>Table Caption
+</caption>
+<tbody><tr>
+<th>Table Heading 1</th>
+<th>Table Heading 2
+</th></tr>
+<tr>
+<td>Table Cell 1</td>
+<td>Table Cell 2
+</td></tr>
+<tr>
+<td>class="foo"</td>
+<td>Table Cell 3
+</td></tr>
+<tr>
+<td>testing <a href="/index.php?title=One&amp;action=edit&amp;redlink=1" class="new" title="One (page does not exist)">two</a> | some content
+</td></tr></tbody></table>
+<dl><dd><table>
+<tbody><tr>
+<td>Table Cell 1</td>
+<td>Table Cell 2
+</td></tr></tbody></table> foo</dd></dl>
+!! end
+
+# Looks like <caption> is not accepted in HTML
+!! test
+Do not trim whitespace in HTML headings, list items, table captions, headings, and cells
+!! wikitext
+__NOTOC__
+<h2> <!--c1--> <!--c2--> Heading <!--c3--> <!--c4--> </h2>
+<ul><li> <!--c1--> <!--c2--> List item <!--c3--> <!--c4--> </li></ul>
+<table>
+<tr><th> <!--c1--> <!--c2--> Table Heading <!--c3--> <!--c4--> <th></tr>
+<tr><td> <!--c1--> <!--c2--> Table Cell <!--c3--> <!--c4--> <th></tr>
+</table>
+!! html/php+tidy
+<h2><span class="mw-headline" id="Heading"> Heading </span></h2>
+<ul><li> List item </li></ul>
+<table>
+<tbody><tr><th> Table Heading </th><th></th></tr>
+<tr><td> Table Cell </td><th></th></tr>
+</tbody></table>
+!! end
+
+!! test
+Do not trim whitespace in links and quotes
+!! wikitext
+foo '' <!--c1--> italic <!--c2--> '' and ''' <!--c3--> bold <!--c4--> '''
+[[Foo| some text ]]
+!! html/php+tidy
+<p>foo <i> italic </i> and <b> bold </b>
+<a href="/wiki/Foo" title="Foo"> some text </a>
+</p>
+!! end
+
+!! test
+Remove p tags surrounding a single element in a figcaption
+!! options
+parsoid=html2wt
+!! wikitext
+[[File:Foobar.jpg|right|200x200px|Caption]]
+!! html/parsoid
+<figure class="mw-halign-right" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="23" width="200"/></a><figcaption><p>Caption</p></figcaption></figure>
+!! end
+
+!! test
+Selser preserves lack of newline before list and allows newline after the list
+!! options
+parsoid={
+ "modes": ["selser"],
+ "scrubWikitext": true,
+ "changes": [
+ [ "ul", "after", "<p>footer</p>" ]
+ ]
+}
+!! wikitext
+header
+*foo
+*bar
+!! wikitext/edited
+header
+*foo
+*bar
+
+footer
+!! end
+
+
+!! test
+Selser does not introduce newlines between unedited paragraph preceding the list
+!! options
+parsoid={
+ "modes": ["selser"],
+ "changes": [
+ [ "table tbody tr td p:last-child", "empty" ]
+ ]
+}
+!! wikitext
+{|
+|
+header
+*foo
+*bar
+footer
+|}
+!! wikitext/edited
+{|
+|
+header
+*foo
+*bar
+
+|}
+!! end
+
+!! test
+Selser does not introduce newlines between unedited paragraph following the list
+!! options
+parsoid={
+ "modes": ["selser"],
+ "changes": [
+ [ "table tbody tr td p:first-child", "empty" ]
+ ]
+}
+!! wikitext
+{|
+|
+header
+*foo
+*bar
+footer
+|}
+!! wikitext/edited
+{|
+|
+
+*foo
+*bar
+footer
+|}
+!! end
+
+!! test
+Remove a list item but do not insert newline above list
+!! options
+parsoid={
+ "modes": ["selser"],
+ "changes": [
+ [ "ul li:last-child", "remove" ]
+ ]
+}
+!! wikitext
+header
+*foo
+*bar
+footer
+!! wikitext/edited
+header
+*foo
+footer
+!! end
diff --git a/www/wiki/tests/parser/parserTestsParserHook.php b/www/wiki/tests/parser/parserTestsParserHook.php
deleted file mode 100644
index 5bf50ead..00000000
--- a/www/wiki/tests/parser/parserTestsParserHook.php
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-/**
- * A basic extension that's used by the parser tests to test whether input and
- * arguments are passed to extensions properly.
- *
- * Copyright © 2005, 2006 Ævar Arnfjörð Bjarmason
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Testing
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- */
-
-class ParserTestParserHook {
-
- static function setup( &$parser ) {
- $parser->setHook( 'tag', [ __CLASS__, 'dumpHook' ] );
- $parser->setHook( 'tåg', [ __CLASS__, 'dumpHook' ] );
- $parser->setHook( 'statictag', [ __CLASS__, 'staticTagHook' ] );
- return true;
- }
-
- static function dumpHook( $in, $argv ) {
- return "<pre>\n" .
- var_export( $in, true ) . "\n" .
- var_export( $argv, true ) . "\n" .
- "</pre>";
- }
-
- static function staticTagHook( $in, $argv, $parser ) {
- if ( !count( $argv ) ) {
- $parser->static_tag_buf = $in;
- return '';
- } elseif ( count( $argv ) === 1 && isset( $argv['action'] )
- && $argv['action'] === 'flush' && $in === null
- ) {
- // Clear the buffer, we probably don't need to
- if ( isset( $parser->static_tag_buf ) ) {
- $tmp = $parser->static_tag_buf;
- } else {
- $tmp = '';
- }
- $parser->static_tag_buf = null;
- return $tmp;
- } else { // wtf?
- return
- "\nCall this extension as <statictag>string</statictag> or as" .
- " <statictag action=flush/>, not in any other way.\n" .
- "text: " . var_export( $in, true ) . "\n" .
- "argv: " . var_export( $argv, true ) . "\n";
- }
- }
-}
diff --git a/www/wiki/tests/parserTests.php b/www/wiki/tests/parserTests.php
deleted file mode 100644
index b3cb89ae..00000000
--- a/www/wiki/tests/parserTests.php
+++ /dev/null
@@ -1,93 +0,0 @@
-<?php
-/**
- * MediaWiki parser test suite
- *
- * Copyright © 2004 Brion Vibber <brion@pobox.com>
- * https://www.mediawiki.org/
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Testing
- */
-
-define( 'MW_PARSER_TEST', true );
-
-$options = [ 'quick', 'color', 'quiet', 'help', 'show-output',
- 'record', 'run-disabled', 'run-parsoid' ];
-$optionsWithArgs = [ 'regex', 'filter', 'seed', 'setversion', 'file' ];
-
-require_once __DIR__ . '/../maintenance/commandLine.inc';
-require_once __DIR__ . '/TestsAutoLoader.php';
-
-if ( isset( $options['help'] ) ) {
- echo <<<ENDS
-MediaWiki $wgVersion parser test suite
-Usage: php parserTests.php [options...]
-
-Options:
- --quick Suppress diff output of failed tests
- --quiet Suppress notification of passed tests (shows only failed tests)
- --show-output Show expected and actual output
- --color[=yes|no] Override terminal detection and force color output on or off
- use wgCommandLineDarkBg = true; if your term is dark
- --regex Only run tests whose descriptions which match given regex
- --filter Alias for --regex
- --file=<testfile> Run test cases from a custom file instead of parserTests.txt
- --record Record tests in database
- --compare Compare with recorded results, without updating the database.
- --setversion When using --record, set the version string to use (useful
- with git-svn so that you can get the exact revision)
- --keep-uploads Re-use the same upload directory for each test, don't delete it
- --fuzz Do a fuzz test instead of a normal test
- --seed <n> Start the fuzz test from the specified seed
- --help Show this help message
- --run-disabled run disabled tests
- --run-parsoid run parsoid tests (normally disabled)
-
-ENDS;
- exit( 0 );
-}
-
-# Cases of weird db corruption were encountered when running tests on earlyish
-# versions of SQLite
-if ( $wgDBtype == 'sqlite' ) {
- $db = wfGetDB( DB_MASTER );
- $version = $db->getServerVersion();
- if ( version_compare( $version, '3.6' ) < 0 ) {
- die( "Parser tests require SQLite version 3.6 or later, you have $version\n" );
- }
-}
-
-$tester = new ParserTest( $options );
-
-if ( isset( $options['file'] ) ) {
- $files = [ $options['file'] ];
-} else {
- // Default parser tests and any set from extensions or local config
- $files = $wgParserTestFiles;
-}
-
-# Print out software version to assist with locating regressions
-$version = SpecialVersion::getVersion( 'nodb' );
-echo "This is MediaWiki version {$version}.\n\n";
-
-if ( isset( $options['fuzz'] ) ) {
- $tester->fuzzTest( $files );
-} else {
- $ok = $tester->runTestsFromFiles( $files );
- exit( $ok ? 0 : 1 );
-}
diff --git a/www/wiki/tests/phan/config.php b/www/wiki/tests/phan/config.php
index 8a82d74c..71ebd6f4 100644
--- a/www/wiki/tests/phan/config.php
+++ b/www/wiki/tests/phan/config.php
@@ -1,7 +1,5 @@
<?php
-use \Phan\Config;
-
// If xdebug is enabled, we need to increase the nesting level for phan
ini_set( 'xdebug.max_nesting_level', 1000 );
@@ -40,10 +38,13 @@ return [
function_exists( 'tideways_enable' ) ? [] : [ 'tests/phan/stubs/tideways.php' ],
class_exists( PEAR::class ) ? [] : [ 'tests/phan/stubs/mail.php' ],
class_exists( Memcached::class ) ? [] : [ 'tests/phan/stubs/memcached.php' ],
+ // Per composer.json, PHPUnit 6 is used for PHP 7.0+, PHPUnit 4 otherwise.
+ // Load the interface for the version of PHPUnit that isn't installed.
+ // Phan only supports PHP 7.0+ (and not HHVM), so we only need to stub PHPUnit 4.
+ class_exists( PHPUnit_TextUI_Command::class ) ? [] : [ 'tests/phan/stubs/phpunit4.php' ],
[
'maintenance/7zip.inc',
'maintenance/backup.inc',
- 'maintenance/backupPrefetch.inc',
'maintenance/cleanupTable.inc',
'maintenance/CodeCleanerGlobalsPass.inc',
'maintenance/commandLine.inc',
@@ -297,20 +298,30 @@ return [
* to this black-list to inhibit them from being reported.
*/
'suppress_issue_types' => [
+ // approximate error count: 29
+ "PhanCommentParamOnEmptyParamList",
+ // approximate error count: 33
+ "PhanCommentParamWithoutRealParam",
// approximate error count: 8
"PhanDeprecatedClass",
// approximate error count: 415
"PhanDeprecatedFunction",
// approximate error count: 25
"PhanDeprecatedProperty",
+ // approximate error count: 17
+ "PhanNonClassMethodCall",
// approximate error count: 11
"PhanParamReqAfterOpt",
// approximate error count: 888
"PhanParamSignatureMismatch",
// approximate error count: 7
"PhanParamSignatureMismatchInternal",
+ // approximate error count: 1
+ "PhanParamSignatureRealMismatchTooFewParameters",
// approximate error count: 125
"PhanParamTooMany",
+ // approximate error count: 1
+ "PhanParamTooManyCallable",
// approximate error count: 3
"PhanParamTooManyInternal",
// approximate error count: 1
@@ -319,12 +330,28 @@ return [
"PhanTraitParentReference",
// approximate error count: 3
"PhanTypeComparisonFromArray",
+ // approximate error count: 2
+ "PhanTypeComparisonToArray",
// approximate error count: 3
"PhanTypeInvalidRightOperand",
+ // approximate error count: 1
+ "PhanTypeMagicVoidWithReturn",
// approximate error count: 218
"PhanTypeMismatchArgument",
// approximate error count: 13
"PhanTypeMismatchArgumentInternal",
+ // approximate error count: 6
+ "PhanTypeMismatchDeclaredParam",
+ // approximate error count: 111
+ "PhanTypeMismatchDeclaredParamNullable",
+ // approximate error count: 1
+ "PhanTypeMismatchDefault",
+ // approximate error count: 5
+ "PhanTypeMismatchDimAssignment",
+ // approximate error count: 2
+ "PhanTypeMismatchDimEmpty",
+ // approximate error count: 1
+ "PhanTypeMismatchDimFetch",
// approximate error count: 14
"PhanTypeMismatchForeach",
// approximate error count: 56
@@ -335,6 +362,8 @@ return [
"PhanTypeMissingReturn",
// approximate error count: 5
"PhanTypeNonVarPassByRef",
+ // approximate error count: 1
+ "PhanUndeclaredClassInCallable",
// approximate error count: 32
"PhanUndeclaredConstant",
// approximate error count: 233
@@ -343,6 +372,12 @@ return [
"PhanUndeclaredProperty",
// approximate error count: 3
"PhanUndeclaredStaticMethod",
+ // approximate error count: 11
+ "PhanUndeclaredTypeReturnType",
+ // approximate error count: 27
+ "PhanUndeclaredVariable",
+ // approximate error count: 58
+ "PhanUndeclaredVariableDim",
],
/**
diff --git a/www/wiki/tests/phan/stubs/hhvm.php b/www/wiki/tests/phan/stubs/hhvm.php
index 79feaa00..364ebdaa 100644
--- a/www/wiki/tests/phan/stubs/hhvm.php
+++ b/www/wiki/tests/phan/stubs/hhvm.php
@@ -16,7 +16,7 @@
* http://www.gnu.org/copyleft/gpl.html
*/
-// @codingStandardsIgnoreFile
+// phpcs:ignoreFile
/**
* @param callable $callback
diff --git a/www/wiki/tests/phan/stubs/mail.php b/www/wiki/tests/phan/stubs/mail.php
index 7cd90167..ba1efb96 100644
--- a/www/wiki/tests/phan/stubs/mail.php
+++ b/www/wiki/tests/phan/stubs/mail.php
@@ -3,7 +3,7 @@
/**
* Minimal set of classes necessary for UserMailer to be happy. Types
* taken from documentation at pear.php.net.
- * @codingStandardsIgnoreFile
+ * phpcs:ignoreFile
*/
class PEAR {
diff --git a/www/wiki/tests/phan/stubs/memcached.php b/www/wiki/tests/phan/stubs/memcached.php
index ee47937a..0f8859d2 100644
--- a/www/wiki/tests/phan/stubs/memcached.php
+++ b/www/wiki/tests/phan/stubs/memcached.php
@@ -5,7 +5,7 @@
* that they are optional. Phan can not detect this and thus throws an error for a usage with
* no params. So we have this small stub just for the constructor to allow no params.
* @see https://secure.php.net/manual/en/memcached.construct.php
- * @codingStandardsIgnoreFile
+ * phpcs:ignoreFile
*/
class Memcached {
diff --git a/www/wiki/tests/phan/stubs/phpunit4.php b/www/wiki/tests/phan/stubs/phpunit4.php
new file mode 100644
index 00000000..e5e88e6b
--- /dev/null
+++ b/www/wiki/tests/phan/stubs/phpunit4.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * Some old classes from PHPUnit 4 that MediaWiki (conditionally) references.
+ *
+ * phpcs:ignoreFile
+ */
+
+class PHPUnit_TextUI_Command {
+
+}
diff --git a/www/wiki/tests/phan/stubs/tideways.php b/www/wiki/tests/phan/stubs/tideways.php
index 1372219b..34ac735c 100644
--- a/www/wiki/tests/phan/stubs/tideways.php
+++ b/www/wiki/tests/phan/stubs/tideways.php
@@ -2,7 +2,7 @@
/**
* Minimal set of classes necessary for Xhprof using tideways
- * @codingStandardsIgnoreFile
+ * phpcs:ignoreFile
*/
function tideways_enable(){
diff --git a/www/wiki/tests/phan/stubs/wikidiff.php b/www/wiki/tests/phan/stubs/wikidiff.php
index 1015b0b9..02bcd1fb 100644
--- a/www/wiki/tests/phan/stubs/wikidiff.php
+++ b/www/wiki/tests/phan/stubs/wikidiff.php
@@ -16,7 +16,7 @@
* http://www.gnu.org/copyleft/gpl.html
*/
-// @codingStandardsIgnoreFile
+// phpcs:ignoreFile
/**
* @param string $text1
@@ -27,3 +27,13 @@
*/
function wikidiff2_do_diff( $text1, $text2, $numContextLines, $movedParagraphDetectionCutoff = 0 ) {
}
+
+/**
+ * @param string $text1
+ * @param string $text2
+ * @param int $numContextLines
+ * @param int $maxMovedLines
+ * @return string
+ */
+function wikidiff2_inline_diff( $text1, $text2, $numContextLines, $maxMovedLines = 25 ) {
+}
diff --git a/www/wiki/tests/phpunit/HamcrestPHPUnitIntegration.php b/www/wiki/tests/phpunit/HamcrestPHPUnitIntegration.php
new file mode 100644
index 00000000..def08ff3
--- /dev/null
+++ b/www/wiki/tests/phpunit/HamcrestPHPUnitIntegration.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Copyright (C) 2018 Kunal Mehta <legoktm@member.fsf.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/**
+ * @since 1.31
+ */
+trait HamcrestPHPUnitIntegration {
+
+ /**
+ * Wrapper around Hamcrest's assertThat, which marks the assertion
+ * for PHPUnit so the test is not marked as risky
+ */
+ public function assertThatHamcrest( /* ... */ ) {
+ call_user_func_array( 'assertThat', func_get_args() );
+ $this->addToAssertionCount( 1 );
+ }
+}
diff --git a/www/wiki/tests/phpunit/MediaWikiCoversValidator.php b/www/wiki/tests/phpunit/MediaWikiCoversValidator.php
new file mode 100644
index 00000000..a79a139c
--- /dev/null
+++ b/www/wiki/tests/phpunit/MediaWikiCoversValidator.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Copyright (C) 2017 Kunal Mehta <legoktm@member.fsf.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/**
+ * Trait that checks that covers tags are valid, since PHPUnit
+ * won't do it unless you run it with coverage, which is super
+ * slow.
+ *
+ * @since 1.31
+ */
+trait MediaWikiCoversValidator {
+
+ /**
+ * Test that all methods in this class that begin
+ * with "test" have valid covers tags.
+ */
+ public function testValidCovers() {
+ $methods = get_class_methods( $this );
+ $class = get_class( $this );
+ $bad = '';
+ foreach ( $methods as $method ) {
+ if ( strpos( $method, 'test' ) === 0 ) {
+ try {
+ PHPUnit_Util_Test::getLinesToBeCovered( $class, $method );
+ } catch ( PHPUnit_Framework_CodeCoverageException $e ) {
+ $bad .= "$class::$method: {$e->getMessage()}\n";
+ }
+ }
+ }
+
+ $this->assertEquals( '', $bad );
+ }
+}
diff --git a/www/wiki/tests/phpunit/MediaWikiPHPUnitTestListener.php b/www/wiki/tests/phpunit/MediaWikiPHPUnitTestListener.php
index dd606d8a..0a162a28 100644
--- a/www/wiki/tests/phpunit/MediaWikiPHPUnitTestListener.php
+++ b/www/wiki/tests/phpunit/MediaWikiPHPUnitTestListener.php
@@ -11,7 +11,7 @@ class MediaWikiPHPUnitTestListener
protected function getTestName( PHPUnit_Framework_Test $test ) {
$name = get_class( $test );
- if ( $test instanceof PHPUnit_Framework_TestCase ) {
+ if ( $test instanceof PHPUnit\Framework\TestCase ) {
$name .= '::' . $test->getName( true );
}
diff --git a/www/wiki/tests/phpunit/MediaWikiTestCase.php b/www/wiki/tests/phpunit/MediaWikiTestCase.php
index f04eec73..87ca9181 100644
--- a/www/wiki/tests/phpunit/MediaWikiTestCase.php
+++ b/www/wiki/tests/phpunit/MediaWikiTestCase.php
@@ -5,14 +5,19 @@ use MediaWiki\Logger\LoggerFactory;
use MediaWiki\Logger\MonologSpi;
use MediaWiki\MediaWikiServices;
use Psr\Log\LoggerInterface;
+use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\IMaintainableDatabase;
use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\LBFactory;
use Wikimedia\TestingAccessWrapper;
/**
* @since 1.18
*/
-abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
+abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+ use PHPUnit4And6Compat;
/**
* The service locator created by prepareServices(). This service locator will
@@ -256,20 +261,19 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
* which we can't allow, as that would open a new connection for mysql.
* Replace with a HashBag. They would not be going to persist anyway.
*/
- $hashCache = [ 'class' => 'HashBagOStuff', 'reportDupes' => false ];
+ $hashCache = [ 'class' => HashBagOStuff::class, 'reportDupes' => false ];
$objectCaches = [
CACHE_DB => $hashCache,
CACHE_ACCEL => $hashCache,
CACHE_MEMCACHED => $hashCache,
'apc' => $hashCache,
'apcu' => $hashCache,
- 'xcache' => $hashCache,
'wincache' => $hashCache,
] + $baseConfig->get( 'ObjectCaches' );
$defaultOverrides->set( 'ObjectCaches', $objectCaches );
$defaultOverrides->set( 'MainCacheType', CACHE_NONE );
- $defaultOverrides->set( 'JobTypeConf', [ 'default' => [ 'class' => 'JobQueueMemory' ] ] );
+ $defaultOverrides->set( 'JobTypeConf', [ 'default' => [ 'class' => JobQueueMemory::class ] ] );
// Use a fast hash algorithm to hash passwords.
$defaultOverrides->set( 'PasswordDefault', 'A' );
@@ -406,6 +410,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
// is available in subclass's setUpBeforeClass() and setUp() methods.
// This would also remove the need for the HACK that is oncePerClass().
if ( $this->oncePerClass() ) {
+ $this->setUpSchema( $this->db );
$this->addDBDataOnce();
}
@@ -515,8 +520,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
// XXX: reset maintenance triggers
// Hook into period lag checks which often happen in long-running scripts
- $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
- Maintenance::setLBFactoryTriggers( $lbFactory );
+ $services = MediaWikiServices::getInstance();
+ $lbFactory = $services->getDBLoadBalancerFactory();
+ Maintenance::setLBFactoryTriggers( $lbFactory, $services->getMainConfig() );
ob_start( 'MediaWikiTestCase::wfResetOutputBuffersBarrier' );
}
@@ -898,6 +904,36 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
}
/**
+ * Alters $wgGroupPermissions for the duration of the test. Can be called
+ * with an array, like
+ * [ '*' => [ 'read' => false ], 'user' => [ 'read' => false ] ]
+ * or three values to set a single permission, like
+ * $this->setGroupPermissions( '*', 'read', false );
+ *
+ * @since 1.31
+ * @param array|string $newPerms Either an array of permissions to change,
+ * in which case the next two parameters are ignored; or a single string
+ * identifying a group, to use with the next two parameters.
+ * @param string|null $newKey
+ * @param mixed $newValue
+ */
+ public function setGroupPermissions( $newPerms, $newKey = null, $newValue = null ) {
+ global $wgGroupPermissions;
+
+ $this->stashMwGlobals( 'wgGroupPermissions' );
+
+ if ( is_string( $newPerms ) ) {
+ $newPerms = [ $newPerms => [ $newKey => $newValue ] ];
+ }
+
+ foreach ( $newPerms as $group => $permissions ) {
+ foreach ( $permissions as $key => $value ) {
+ $wgGroupPermissions[$group][$key] = $value;
+ }
+ }
+ }
+
+ /**
* Sets the logger for a specified channel, for the duration of the test.
* @since 1.27
* @param string $channel
@@ -968,12 +1004,13 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
* @since 1.18
*/
public function needsDB() {
- # if the test says it uses database tables, it needs the database
+ // If the test says it uses database tables, it needs the database
if ( $this->tablesUsed ) {
return true;
}
- # if the test says it belongs to the Database group, it needs the database
+ // If the test class says it belongs to the Database group, it needs the database.
+ // NOTE: This ONLY checks for the group in the class level doc comment.
$rc = new ReflectionClass( $this );
if ( preg_match( '/@group +Database/im', $rc->getDocComment() ) ) {
return true;
@@ -1007,10 +1044,6 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
$user = static::getTestSysop()->getUser();
$comment = __METHOD__ . ': Sample page for unit test.';
- // Avoid memory leak...?
- // LinkCache::singleton()->clear();
- // Maybe. But doing this absolutely breaks $title->isRedirect() when called during unit tests....
-
$page = WikiPage::factory( $title );
$page->doEditContent( ContentHandler::makeContent( $text, $title ), $comment, 0, false, $user );
@@ -1077,6 +1110,8 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
}
}
+ SiteStatsInit::doPlaceholderInit();
+
User::resetIdByNameCache();
// Make sysop user
@@ -1152,6 +1187,8 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
$dbClone = new CloneDatabase( $db, $tablesCloned, $prefix );
$dbClone->useTemporaryTables( self::$useTemporaryTables );
+ $db->_originalTablePrefix = $db->tablePrefix();
+
if ( ( $db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) {
CloneDatabase::changePrefix( $prefix );
@@ -1296,6 +1333,205 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
}
/**
+ * @throws LogicException if the given database connection is not a set up to use
+ * mock tables.
+ */
+ private function ensureMockDatabaseConnection( IDatabase $db ) {
+ if ( $db->tablePrefix() !== $this->dbPrefix() ) {
+ throw new LogicException(
+ 'Trying to delete mock tables, but table prefix does not indicate a mock database.'
+ );
+ }
+ }
+
+ private static $schemaOverrideDefaults = [
+ 'scripts' => [],
+ 'create' => [],
+ 'drop' => [],
+ 'alter' => [],
+ ];
+
+ /**
+ * Stub. If a test suite needs to test against a specific database schema, it should
+ * override this method and return the appropriate information from it.
+ *
+ * @param IMaintainableDatabase $db The DB connection to use for the mock schema.
+ * May be used to check the current state of the schema, to determine what
+ * overrides are needed.
+ *
+ * @return array An associative array with the following fields:
+ * - 'scripts': any SQL scripts to run. If empty or not present, schema overrides are skipped.
+ * - 'create': A list of tables created (may or may not exist in the original schema).
+ * - 'drop': A list of tables dropped (expected to be present in the original schema).
+ * - 'alter': A list of tables altered (expected to be present in the original schema).
+ */
+ protected function getSchemaOverrides( IMaintainableDatabase $db ) {
+ return [];
+ }
+
+ /**
+ * Undoes the dpecified schema overrides..
+ * Called once per test class, just before addDataOnce().
+ *
+ * @param IMaintainableDatabase $db
+ * @param array $oldOverrides
+ */
+ private function undoSchemaOverrides( IMaintainableDatabase $db, $oldOverrides ) {
+ $this->ensureMockDatabaseConnection( $db );
+
+ $oldOverrides = $oldOverrides + self::$schemaOverrideDefaults;
+ $originalTables = $this->listOriginalTables( $db );
+
+ // Drop tables that need to be restored or removed.
+ $tablesToDrop = array_merge( $oldOverrides['create'], $oldOverrides['alter'] );
+
+ // Restore tables that have been dropped or created or altered,
+ // if they exist in the original schema.
+ $tablesToRestore = array_merge( $tablesToDrop, $oldOverrides['drop'] );
+ $tablesToRestore = array_intersect( $originalTables, $tablesToRestore );
+
+ if ( $tablesToDrop ) {
+ $this->dropMockTables( $db, $tablesToDrop );
+ }
+
+ if ( $tablesToRestore ) {
+ $this->recloneMockTables( $db, $tablesToRestore );
+ }
+ }
+
+ /**
+ * Applies the schema overrides returned by getSchemaOverrides(),
+ * after undoing any previously applied schema overrides.
+ * Called once per test class, just before addDataOnce().
+ */
+ private function setUpSchema( IMaintainableDatabase $db ) {
+ // Undo any active overrides.
+ $oldOverrides = isset( $db->_schemaOverrides ) ? $db->_schemaOverrides
+ : self::$schemaOverrideDefaults;
+
+ if ( $oldOverrides['alter'] || $oldOverrides['create'] || $oldOverrides['drop'] ) {
+ $this->undoSchemaOverrides( $db, $oldOverrides );
+ }
+
+ // Determine new overrides.
+ $overrides = $this->getSchemaOverrides( $db ) + self::$schemaOverrideDefaults;
+
+ $extraKeys = array_diff(
+ array_keys( $overrides ),
+ array_keys( self::$schemaOverrideDefaults )
+ );
+
+ if ( $extraKeys ) {
+ throw new InvalidArgumentException(
+ 'Schema override contains extra keys: ' . var_export( $extraKeys, true )
+ );
+ }
+
+ if ( !$overrides['scripts'] ) {
+ // no scripts to run
+ return;
+ }
+
+ if ( !$overrides['create'] && !$overrides['drop'] && !$overrides['alter'] ) {
+ throw new InvalidArgumentException(
+ 'Schema override scripts given, but no tables are declared to be '
+ . 'created, dropped or altered.'
+ );
+ }
+
+ $this->ensureMockDatabaseConnection( $db );
+
+ // Drop the tables that will be created by the schema scripts.
+ $originalTables = $this->listOriginalTables( $db );
+ $tablesToDrop = array_intersect( $originalTables, $overrides['create'] );
+
+ if ( $tablesToDrop ) {
+ $this->dropMockTables( $db, $tablesToDrop );
+ }
+
+ // Run schema override scripts.
+ foreach ( $overrides['scripts'] as $script ) {
+ $db->sourceFile(
+ $script,
+ null,
+ null,
+ __METHOD__,
+ function ( $cmd ) {
+ return $this->mungeSchemaUpdateQuery( $cmd );
+ }
+ );
+ }
+
+ $db->_schemaOverrides = $overrides;
+ }
+
+ private function mungeSchemaUpdateQuery( $cmd ) {
+ return self::$useTemporaryTables
+ ? preg_replace( '/\bCREATE\s+TABLE\b/i', 'CREATE TEMPORARY TABLE', $cmd )
+ : $cmd;
+ }
+
+ /**
+ * Drops the given mock tables.
+ *
+ * @param IMaintainableDatabase $db
+ * @param array $tables
+ */
+ private function dropMockTables( IMaintainableDatabase $db, array $tables ) {
+ $this->ensureMockDatabaseConnection( $db );
+
+ foreach ( $tables as $tbl ) {
+ $tbl = $db->tableName( $tbl );
+ $db->query( "DROP TABLE IF EXISTS $tbl", __METHOD__ );
+
+ if ( $tbl === 'page' ) {
+ // Forget about the pages since they don't
+ // exist in the DB.
+ LinkCache::singleton()->clear();
+ }
+ }
+ }
+
+ /**
+ * Lists all tables in the live database schema.
+ *
+ * @param IMaintainableDatabase $db
+ * @return array
+ */
+ private function listOriginalTables( IMaintainableDatabase $db ) {
+ if ( !isset( $db->_originalTablePrefix ) ) {
+ throw new LogicException( 'No original table prefix know, cannot list tables!' );
+ }
+
+ $originalTables = $db->listTables( $db->_originalTablePrefix, __METHOD__ );
+ return $originalTables;
+ }
+
+ /**
+ * Re-clones the given mock tables to restore them based on the live database schema.
+ * The tables listed in $tables are expected to currently not exist, so dropMockTables()
+ * should be called first.
+ *
+ * @param IMaintainableDatabase $db
+ * @param array $tables
+ */
+ private function recloneMockTables( IMaintainableDatabase $db, array $tables ) {
+ $this->ensureMockDatabaseConnection( $db );
+
+ if ( !isset( $db->_originalTablePrefix ) ) {
+ throw new LogicException( 'No original table prefix know, cannot restore tables!' );
+ }
+
+ $originalTables = $this->listOriginalTables( $db );
+ $tables = array_intersect( $tables, $originalTables );
+
+ $dbClone = new CloneDatabase( $db, $tables, $db->tablePrefix(), $db->_originalTablePrefix );
+ $dbClone->useTemporaryTables( self::$useTemporaryTables );
+
+ $dbClone->cloneTableStructure();
+ }
+
+ /**
* Empty all tables so they can be repopulated for tests
*
* @param Database $db|null Database to reset
@@ -1303,8 +1539,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
*/
private function resetDB( $db, $tablesUsed ) {
if ( $db ) {
- $userTables = [ 'user', 'user_groups', 'user_properties' ];
- $pageTables = [ 'page', 'revision', 'ip_changes', 'revision_comment_temp', 'comment' ];
+ $userTables = [ 'user', 'user_groups', 'user_properties', 'actor' ];
+ $pageTables = [ 'page', 'revision', 'ip_changes', 'revision_comment_temp',
+ 'revision_actor_temp', 'comment' ];
$coreDBDataTables = array_merge( $userTables, $pageTables );
// If any of the user or page tables were marked as used, we should clear all of them.
@@ -1323,12 +1560,21 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
continue;
}
+ if ( !$db->tableExists( $tbl ) ) {
+ continue;
+ }
+
if ( $truncate ) {
$db->query( 'TRUNCATE TABLE ' . $db->tableName( $tbl ), __METHOD__ );
} else {
$db->delete( $tbl, '*', __METHOD__ );
}
+ if ( in_array( $db->getType(), [ 'postgres', 'sqlite' ], true ) ) {
+ // Reset the table's sequence too.
+ $db->resetSequenceForTable( $tbl, __METHOD__ );
+ }
+
if ( $tbl === 'page' ) {
// Forget about the pages since they don't
// exist in the DB.
@@ -1343,50 +1589,12 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
}
}
- /**
- * @since 1.18
- *
- * @param string $func
- * @param array $args
- *
- * @return mixed
- * @throws MWException
- */
- public function __call( $func, $args ) {
- static $compatibility = [
- 'createMock' => 'createMock2',
- ];
-
- if ( isset( $compatibility[$func] ) ) {
- return call_user_func_array( [ $this, $compatibility[$func] ], $args );
- } else {
- throw new MWException( "Called non-existent $func method on " . static::class );
- }
- }
-
- /**
- * Return a test double for the specified class.
- *
- * @param string $originalClassName
- * @return PHPUnit_Framework_MockObject_MockObject
- * @throws Exception
- */
- private function createMock2( $originalClassName ) {
- return $this->getMockBuilder( $originalClassName )
- ->disableOriginalConstructor()
- ->disableOriginalClone()
- ->disableArgumentCloning()
- // New in phpunit-mock-objects 3.2 (phpunit 5.4.0)
- // ->disallowMockingUnknownTypes()
- ->getMock();
- }
-
private static function unprefixTable( &$tableName, $ind, $prefix ) {
$tableName = substr( $tableName, strlen( $prefix ) );
}
private static function isNotUnittest( $table ) {
- return strpos( $table, 'unittest_' ) !== 0;
+ return strpos( $table, self::DB_PREFIX ) !== 0;
}
/**
@@ -1465,9 +1673,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
* @param string $function
*/
public function hideDeprecated( $function ) {
- MediaWiki\suppressWarnings();
+ Wikimedia\suppressWarnings();
wfDeprecated( $function );
- MediaWiki\restoreWarnings();
+ Wikimedia\restoreWarnings();
}
/**
@@ -1482,13 +1690,17 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
* @param string|array $fields The columns to include in the result (and to sort by)
* @param string|array $condition "where" condition(s)
* @param array $expectedRows An array of arrays giving the expected rows.
+ * @param array $options Options for the query
+ * @param array $join_conds Join conditions for the query
*
* @throws MWException If this test cases's needsDB() method doesn't return true.
* Test cases can use "@group Database" to enable database test support,
* or list the tables under testing in $this->tablesUsed, or override the
* needsDB() method.
*/
- protected function assertSelect( $table, $fields, $condition, array $expectedRows ) {
+ protected function assertSelect(
+ $table, $fields, $condition, array $expectedRows, array $options = [], array $join_conds = []
+ ) {
if ( !$this->needsDB() ) {
throw new MWException( 'When testing database state, the test cases\'s needDB()' .
' method should return true. Use @group Database or $this->tablesUsed.' );
@@ -1496,7 +1708,14 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
$db = wfGetDB( DB_REPLICA );
- $res = $db->select( $table, $fields, $condition, wfGetCaller(), [ 'ORDER BY' => $fields ] );
+ $res = $db->select(
+ $table,
+ $fields,
+ $condition,
+ wfGetCaller(),
+ $options + [ 'ORDER BY' => $fields ],
+ $join_conds
+ );
$this->assertNotEmpty( $res, "query failed: " . $db->lastError() );
$i = 0;
@@ -1750,9 +1969,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
# This check may also protect against code injection in
# case of broken installations.
- MediaWiki\suppressWarnings();
+ Wikimedia\suppressWarnings();
$haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
- MediaWiki\restoreWarnings();
+ Wikimedia\restoreWarnings();
if ( !$haveDiff3 ) {
$this->markTestSkipped( "Skip test, since diff3 is not configured" );
@@ -1777,61 +1996,6 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
}
/**
- * Asserts that the given string is a valid HTML snippet.
- * Wraps the given string in the required top level tags and
- * then calls assertValidHtmlDocument().
- * The snippet is expected to be HTML 5.
- *
- * @since 1.23
- *
- * @note Will mark the test as skipped if the "tidy" module is not installed.
- * @note This ignores $wgUseTidy, so we can check for valid HTML even (and especially)
- * when automatic tidying is disabled.
- *
- * @param string $html An HTML snippet (treated as the contents of the body tag).
- */
- protected function assertValidHtmlSnippet( $html ) {
- $html = '<!DOCTYPE html><html><head><title>test</title></head><body>' . $html . '</body></html>';
- $this->assertValidHtmlDocument( $html );
- }
-
- /**
- * Asserts that the given string is valid HTML document.
- *
- * @since 1.23
- *
- * @note Will mark the test as skipped if the "tidy" module is not installed.
- * @note This ignores $wgUseTidy, so we can check for valid HTML even (and especially)
- * when automatic tidying is disabled.
- *
- * @param string $html A complete HTML document
- */
- protected function assertValidHtmlDocument( $html ) {
- // Note: we only validate if the tidy PHP extension is available.
- // In case wgTidyInternal is false, MWTidy would fall back to the command line version
- // of tidy. In that case however, we can not reliably detect whether a failing validation
- // is due to malformed HTML, or caused by tidy not being installed as a command line tool.
- // That would cause all HTML assertions to fail on a system that has no tidy installed.
- if ( !$GLOBALS['wgTidyInternal'] || !MWTidy::isEnabled() ) {
- $this->markTestSkipped( 'Tidy extension not installed' );
- }
-
- $errorBuffer = '';
- MWTidy::checkErrors( $html, $errorBuffer );
- $allErrors = preg_split( '/[\r\n]+/', $errorBuffer );
-
- // Filter Tidy warnings which aren't useful for us.
- // Tidy eg. often cries about parameters missing which have actually
- // been deprecated since HTML4, thus we should not care about them.
- $errors = preg_grep(
- '/^(.*Warning: (trimming empty|.* lacks ".*?" attribute).*|\s*)$/m',
- $allErrors, PREG_GREP_INVERT
- );
-
- $this->assertEmpty( $errors, implode( "\n", $errors ) );
- }
-
- /**
* Used as a marker to prevent wfResetOutputBuffers from breaking PHPUnit.
* @param string $buffer
* @return string
diff --git a/www/wiki/tests/phpunit/PHPUnit4And6Compat.php b/www/wiki/tests/phpunit/PHPUnit4And6Compat.php
new file mode 100644
index 00000000..672ab4a4
--- /dev/null
+++ b/www/wiki/tests/phpunit/PHPUnit4And6Compat.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * Copyright (C) 2018 Kunal Mehta <legoktm@member.fsf.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/**
+ * @since 1.31
+ */
+trait PHPUnit4And6Compat {
+ /**
+ * @see PHPUnit_Framework_TestCase::setExpectedException
+ *
+ * This function was renamed to expectException() in PHPUnit 6, so this
+ * is a temporary backwards-compatibility layer while we transition.
+ */
+ public function setExpectedException( $name, $message = '', $code = null ) {
+ if ( is_callable( [ $this, 'expectException' ] ) ) {
+ if ( $name !== null ) {
+ $this->expectException( $name );
+ }
+ if ( $message !== '' ) {
+ $this->expectExceptionMessage( $message );
+ }
+ if ( $code !== null ) {
+ $this->expectExceptionCode( $code );
+ }
+ } else {
+ parent::setExpectedException( $name, $message, $code );
+ }
+ }
+
+ /**
+ * @see PHPUnit_Framework_TestCase::getMock
+ *
+ * @return PHPUnit_Framework_MockObject_MockObject
+ */
+ public function getMock( $originalClassName, $methods = [], array $arguments = [],
+ $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true,
+ $callAutoload = true, $cloneArguments = false, $callOriginalMethods = false,
+ $proxyTarget = null
+ ) {
+ if ( is_callable( 'parent::getMock' ) ) {
+ return parent::getMock(
+ $originalClassName, $methods, $arguments, $mockClassName,
+ $callOriginalConstructor, $callOriginalClone, $callAutoload,
+ $cloneArguments, $callOriginalMethods, $proxyTarget
+ );
+ } else {
+ $builder = $this->getMockBuilder( $originalClassName )
+ ->setMethods( $methods )
+ ->setConstructorArgs( $arguments )
+ ->setMockClassName( $mockClassName )
+ ->setProxyTarget( $proxyTarget );
+ if ( $callOriginalConstructor ) {
+ $builder->enableOriginalConstructor();
+ } else {
+ $builder->disableOriginalConstructor();
+ }
+ if ( $callOriginalClone ) {
+ $builder->enableOriginalClone();
+ } else {
+ $builder->disableOriginalClone();
+ }
+ if ( $callAutoload ) {
+ $builder->enableAutoload();
+ } else {
+ $builder->disableAutoload();
+ }
+ if ( $cloneArguments ) {
+ $builder->enableArgumentCloning();
+ } else {
+ $builder->disableArgumentCloning();
+ }
+ if ( $callOriginalMethods ) {
+ $builder->enableProxyingToOriginalMethods();
+ } else {
+ $builder->disableProxyingToOriginalMethods();
+ }
+
+ return $builder->getMock();
+ }
+ }
+
+ /**
+ * Return a test double for the specified class. This
+ * is a forward port of the createMock function that
+ * was introduced in PHPUnit 5.4.
+ *
+ * @param string $originalClassName
+ * @return PHPUnit_Framework_MockObject_MockObject
+ * @throws Exception
+ */
+ public function createMock( $originalClassName ) {
+ if ( is_callable( 'parent::createMock' ) ) {
+ return parent::createMock( $originalClassName );
+ }
+ // Compat for PHPUnit <= 5.4
+ return $this->getMockBuilder( $originalClassName )
+ ->disableOriginalConstructor()
+ ->disableOriginalClone()
+ ->disableArgumentCloning()
+ // New in phpunit-mock-objects 3.2 (phpunit 5.4.0)
+ // ->disallowMockingUnknownTypes()
+ ->getMock();
+ }
+}
diff --git a/www/wiki/tests/phpunit/ResourceLoaderTestCase.php b/www/wiki/tests/phpunit/ResourceLoaderTestCase.php
index 1024ecd0..d5c14a25 100644
--- a/www/wiki/tests/phpunit/ResourceLoaderTestCase.php
+++ b/www/wiki/tests/phpunit/ResourceLoaderTestCase.php
@@ -40,7 +40,7 @@ abstract class ResourceLoaderTestCase extends MediaWikiTestCase {
'skin' => $options['skin'],
'target' => 'phpunit',
] );
- $ctx = $this->getMockBuilder( 'ResourceLoaderContext' )
+ $ctx = $this->getMockBuilder( ResourceLoaderContext::class )
->setConstructorArgs( [ $resourceLoader, $request ] )
->setMethods( [ 'getDirection' ] )
->getMock();
@@ -61,7 +61,6 @@ abstract class ResourceLoaderTestCase extends MediaWikiTestCase {
// For wfScript()
'ScriptPath' => '/w',
- 'ScriptExtension' => '.php',
'Script' => '/w/index.php',
'LoadScript' => '/w/load.php',
];
@@ -87,7 +86,6 @@ class ResourceLoaderTestModule extends ResourceLoaderModule {
protected $dependencies = [];
protected $group = null;
protected $source = 'local';
- protected $position = 'bottom';
protected $script = '';
protected $styles = '';
protected $skipFunction = null;
@@ -126,9 +124,6 @@ class ResourceLoaderTestModule extends ResourceLoaderModule {
public function getSource() {
return $this->source;
}
- public function getPosition() {
- return $this->position;
- }
public function getType() {
return $this->type;
diff --git a/www/wiki/tests/phpunit/autoload.ide.php b/www/wiki/tests/phpunit/autoload.ide.php
index 106ab683..4b0b1873 100644
--- a/www/wiki/tests/phpunit/autoload.ide.php
+++ b/www/wiki/tests/phpunit/autoload.ide.php
@@ -38,16 +38,13 @@ $maintenance->setup();
// to $maintenance->mSelf. Keep that here for b/c
$self = $maintenance->getName();
global $IP;
-# Start the autoloader, so that extensions can derive classes from core files
-require_once "$IP/includes/AutoLoader.php";
-# Grab profiling functions
-require_once "$IP/includes/profiler/ProfilerFunctions.php";
-
-# Start the profiler
+# Get profiler configuraton
$wgProfiler = [];
if ( file_exists( "$IP/StartProfiler.php" ) ) {
require "$IP/StartProfiler.php";
}
+# Start the autoloader, so that extensions can derive classes from core files
+require_once "$IP/includes/AutoLoader.php";
$requireOnceGlobalsScope = function ( $file ) use ( $self ) {
foreach ( array_keys( $GLOBALS ) as $varName ) {
@@ -93,7 +90,7 @@ if ( $maintenance->getDbType() === Maintenance::DB_NONE ) {
|| ( $wgLocalisationCacheConf['store'] == 'detect' && !$wgCacheDirectory )
)
) {
- $wgLocalisationCacheConf['storeClass'] = 'LCStoreNull';
+ $wgLocalisationCacheConf['storeClass'] = LCStoreNull::class;
}
}
diff --git a/www/wiki/tests/phpunit/data/categoriesrdf/categoriesRdf-out.nt b/www/wiki/tests/phpunit/data/categoriesrdf/categoriesRdf-out.nt
index d2d7ea81..bbb37870 100644
--- a/www/wiki/tests/phpunit/data/categoriesrdf/categoriesRdf-out.nt
+++ b/www/wiki/tests/phpunit/data/categoriesrdf/categoriesRdf-out.nt
@@ -1,16 +1,23 @@
-<http://acme.test/categoriesDump> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Dataset> .
-<http://acme.test/categoriesDump> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2002/07/owl#Ontology> .
-<http://acme.test/categoriesDump> <http://creativecommons.org/ns#license> <https://creativecommons.org/licenses/by-sa/3.0/> .
-<http://acme.test/categoriesDump> <http://schema.org/softwareVersion> "1.0" .
-<http://acme.test/categoriesDump> <http://schema.org/dateModified> "{DATE}"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
-<http://acme.test/categoriesDump> <http://schema.org/isPartOf> <http://acme.test/> .
-<http://acme.test/categoriesDump> <http://www.w3.org/2002/07/owl#imports> <https://www.mediawiki.org/ontology/ontology.owl> .
+<http://acme.test/wiki/Special:CategoryDump> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Dataset> .
+<http://acme.test/wiki/Special:CategoryDump> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2002/07/owl#Ontology> .
+<http://acme.test/wiki/Special:CategoryDump> <http://creativecommons.org/ns#license> <https://creativecommons.org/licenses/by-sa/3.0/> .
+<http://acme.test/wiki/Special:CategoryDump> <http://schema.org/softwareVersion> "1.1" .
+<http://acme.test/wiki/Special:CategoryDump> <http://schema.org/dateModified> "{DATE}"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
+<http://acme.test/wiki/Special:CategoryDump> <http://schema.org/isPartOf> <http://acme.test/> .
+<http://acme.test/wiki/Special:CategoryDump> <http://www.w3.org/2002/07/owl#imports> <https://www.mediawiki.org/ontology/ontology.owl> .
<http://acme.test/wiki/Category:Category_One> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://www.mediawiki.org/ontology#Category> .
<http://acme.test/wiki/Category:Category_One> <http://www.w3.org/2000/01/rdf-schema#label> "Category One" .
+<http://acme.test/wiki/Category:Category_One> <https://www.mediawiki.org/ontology#pages> "7"^^<http://www.w3.org/2001/XMLSchema#integer> .
+<http://acme.test/wiki/Category:Category_One> <https://www.mediawiki.org/ontology#subcategories> "10"^^<http://www.w3.org/2001/XMLSchema#integer> .
<http://acme.test/wiki/Category:2_Category_Two> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://www.mediawiki.org/ontology#Category> .
+<http://acme.test/wiki/Category:2_Category_Two> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://www.mediawiki.org/ontology#HiddenCategory> .
<http://acme.test/wiki/Category:2_Category_Two> <http://www.w3.org/2000/01/rdf-schema#label> "2 Category Two" .
+<http://acme.test/wiki/Category:2_Category_Two> <https://www.mediawiki.org/ontology#pages> "17"^^<http://www.w3.org/2001/XMLSchema#integer> .
+<http://acme.test/wiki/Category:2_Category_Two> <https://www.mediawiki.org/ontology#subcategories> "0"^^<http://www.w3.org/2001/XMLSchema#integer> .
<http://acme.test/wiki/Category:Category_One> <https://www.mediawiki.org/ontology#isInCategory> <http://acme.test/wiki/Category:Parent_of_1> .
<http://acme.test/wiki/Category:2_Category_Two> <https://www.mediawiki.org/ontology#isInCategory> <http://acme.test/wiki/Category:Parent_of_2> .
<http://acme.test/wiki/Category:%D0%A2%D1%80%D0%B5%D1%82%D1%8C%D1%8F_%D0%BA%D0%B0%D1%82%D0%B5%D0%B3%D0%BE%D1%80%D0%B8%D1%8F> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://www.mediawiki.org/ontology#Category> .
<http://acme.test/wiki/Category:%D0%A2%D1%80%D0%B5%D1%82%D1%8C%D1%8F_%D0%BA%D0%B0%D1%82%D0%B5%D0%B3%D0%BE%D1%80%D0%B8%D1%8F> <http://www.w3.org/2000/01/rdf-schema#label> "\u0422\u0440\u0435\u0442\u044C\u044F \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F" .
+<http://acme.test/wiki/Category:%D0%A2%D1%80%D0%B5%D1%82%D1%8C%D1%8F_%D0%BA%D0%B0%D1%82%D0%B5%D0%B3%D0%BE%D1%80%D0%B8%D1%8F> <https://www.mediawiki.org/ontology#pages> "0"^^<http://www.w3.org/2001/XMLSchema#integer> .
+<http://acme.test/wiki/Category:%D0%A2%D1%80%D0%B5%D1%82%D1%8C%D1%8F_%D0%BA%D0%B0%D1%82%D0%B5%D0%B3%D0%BE%D1%80%D0%B8%D1%8F> <https://www.mediawiki.org/ontology#subcategories> "0"^^<http://www.w3.org/2001/XMLSchema#integer> .
<http://acme.test/wiki/Category:%D0%A2%D1%80%D0%B5%D1%82%D1%8C%D1%8F_%D0%BA%D0%B0%D1%82%D0%B5%D0%B3%D0%BE%D1%80%D0%B8%D1%8F> <https://www.mediawiki.org/ontology#isInCategory> <http://acme.test/wiki/Category:Parent_of_3> .
diff --git a/www/wiki/tests/phpunit/data/composer/composer.json b/www/wiki/tests/phpunit/data/composer/composer.json
index bcd196f4..9b902ae8 100644
--- a/www/wiki/tests/phpunit/data/composer/composer.json
+++ b/www/wiki/tests/phpunit/data/composer/composer.json
@@ -9,7 +9,7 @@
"homepage": "https://www.mediawiki.org/wiki/Special:Version/Credits"
}
],
- "license": "GPL-2.0",
+ "license": "GPL-2.0-only",
"support": {
"issues": "https://bugzilla.wikimedia.org/",
"irc": "irc://irc.freenode.net/mediawiki",
diff --git a/www/wiki/tests/phpunit/data/composer/composer.lock b/www/wiki/tests/phpunit/data/composer/composer.lock
index cae6a478..5c030db8 100644
--- a/www/wiki/tests/phpunit/data/composer/composer.lock
+++ b/www/wiki/tests/phpunit/data/composer/composer.lock
@@ -162,7 +162,7 @@
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT",
- "GPL-3.0"
+ "GPL-3.0-only"
],
"authors": [
{
@@ -207,7 +207,7 @@
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "GPL-2.0+"
+ "GPL-2.0-or-later"
],
"authors": [
{
@@ -265,7 +265,7 @@
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "GPL-2.0+",
+ "GPL-2.0-or-later",
"MIT"
],
"description": "The primary aim is to allow users to select a language and configure its support in an easy way. Main features are language selection, input methods and web fonts.",
@@ -374,7 +374,7 @@
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "GPL-2.0"
+ "GPL-2.0-only"
],
"authors": [
{
diff --git a/www/wiki/tests/phpunit/data/composer/installed.json b/www/wiki/tests/phpunit/data/composer/installed.json
new file mode 100644
index 00000000..88a6bae2
--- /dev/null
+++ b/www/wiki/tests/phpunit/data/composer/installed.json
@@ -0,0 +1,1682 @@
+[
+ {
+ "name": "leafo/lessphp",
+ "version": "v0.5.0",
+ "version_normalized": "0.5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/leafo/lessphp.git",
+ "reference": "0f5a7f5545d2bcf4e9fad9a228c8ad89cc9aa283"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/leafo/lessphp/zipball/0f5a7f5545d2bcf4e9fad9a228c8ad89cc9aa283",
+ "reference": "0f5a7f5545d2bcf4e9fad9a228c8ad89cc9aa283",
+ "shasum": ""
+ },
+ "time": "2014-11-24T18:39:20+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.4.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "lessc.inc.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT",
+ "GPL-3.0-only"
+ ],
+ "authors": [
+ {
+ "name": "Leaf Corcoran",
+ "email": "leafot@gmail.com",
+ "homepage": "http://leafo.net"
+ }
+ ],
+ "description": "lessphp is a compiler for LESS written in PHP.",
+ "homepage": "http://leafo.net/lessphp/"
+ },
+ {
+ "name": "psr/log",
+ "version": "1.0.0",
+ "version_normalized": "1.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b",
+ "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b",
+ "shasum": ""
+ },
+ "time": "2012-12-21T11:40:51+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-0": {
+ "Psr\\Log\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ]
+ },
+ {
+ "name": "cssjanus/cssjanus",
+ "version": "v1.1.1",
+ "version_normalized": "1.1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/cssjanus/php-cssjanus.git",
+ "reference": "62a9c32e6e140de09082b40a6e99d868ad14d4e0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/cssjanus/php-cssjanus/zipball/62a9c32e6e140de09082b40a6e99d868ad14d4e0",
+ "reference": "62a9c32e6e140de09082b40a6e99d868ad14d4e0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "jakub-onderka/php-parallel-lint": "0.8.*",
+ "phpunit/phpunit": "3.7.*",
+ "squizlabs/php_codesniffer": "1.*"
+ },
+ "time": "2014-11-14T20:00:50+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-0": {
+ "": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "description": "Convert CSS stylesheets between left-to-right and right-to-left."
+ },
+ {
+ "name": "cdb/cdb",
+ "version": "1.0.0",
+ "version_normalized": "1.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wikimedia/cdb.git",
+ "reference": "918601ea3d31b8c37312e9c0e54446aa8bfb3425"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wikimedia/cdb/zipball/918601ea3d31b8c37312e9c0e54446aa8bfb3425",
+ "reference": "918601ea3d31b8c37312e9c0e54446aa8bfb3425",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "*"
+ },
+ "time": "2014-11-12T19:03:26+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPLv2"
+ ],
+ "authors": [
+ {
+ "name": "Tim Starling",
+ "email": "tstarling@wikimedia.org"
+ },
+ {
+ "name": "Chad Horohoe",
+ "email": "chad@wikimedia.org"
+ }
+ ],
+ "description": "Constant Database (CDB) wrapper library for PHP. Provides pure-PHP fallback when dba_* functions are absent.",
+ "homepage": "https://www.mediawiki.org/wiki/CDB",
+ "abandoned": "wikimedia/cdb"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "2.0.1",
+ "version_normalized": "2.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
+ "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "time": "2016-10-03T07:35:21+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version"
+ },
+ {
+ "name": "sebastian/resource-operations",
+ "version": "1.0.0",
+ "version_normalized": "1.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/resource-operations.git",
+ "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
+ "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6.0"
+ },
+ "time": "2015-07-28T20:34:47+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides a list of PHP built-in functions that operate on resources",
+ "homepage": "https://www.github.com/sebastianbergmann/resource-operations"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "3.0.0",
+ "version_normalized": "3.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
+ "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "time": "2017-03-03T06:23:57+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "1.1.1",
+ "version_normalized": "1.1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "773f97c67f28de00d397be301821b06708fca0be"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be",
+ "reference": "773f97c67f28de00d397be301821b06708fca0be",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "time": "2017-03-29T09:07:27+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "3.0.3",
+ "version_normalized": "3.0.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5",
+ "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "sebastian/object-reflector": "^1.1.1",
+ "sebastian/recursion-context": "^3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "time": "2017-08-03T12:35:26+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "2.0.0",
+ "version_normalized": "2.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
+ "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "suggest": {
+ "ext-uopz": "*"
+ },
+ "time": "2017-04-27T15:39:26+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ]
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "3.1.0",
+ "version_normalized": "3.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "234199f4528de6d12aaa58b612e98f7d36adb937"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937",
+ "reference": "234199f4528de6d12aaa58b612e98f7d36adb937",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "sebastian/recursion-context": "^3.0"
+ },
+ "require-dev": {
+ "ext-mbstring": "*",
+ "phpunit/phpunit": "^6.0"
+ },
+ "time": "2017-04-03T13:19:02+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "http://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ]
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "3.1.0",
+ "version_normalized": "3.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5",
+ "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.1"
+ },
+ "time": "2017-07-01T08:51:00+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ]
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "2.0.1",
+ "version_normalized": "2.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd",
+ "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.2"
+ },
+ "time": "2017-08-03T08:09:46+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff"
+ ]
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "2.1.1",
+ "version_normalized": "2.1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "b11c729f95109b56a0fe9650c6a63a0fcd8c439f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/b11c729f95109b56a0fe9650c6a63a0fcd8c439f",
+ "reference": "b11c729f95109b56a0fe9650c6a63a0fcd8c439f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "sebastian/diff": "^2.0",
+ "sebastian/exporter": "^3.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4"
+ },
+ "time": "2017-12-22T14:50:35+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ]
+ },
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.1.0",
+ "version_normalized": "1.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda",
+ "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "athletic/athletic": "~0.1.8",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "phpunit/phpunit": "^6.2.3",
+ "squizlabs/php_codesniffer": "^3.0.2"
+ },
+ "time": "2017-07-22T11:58:36+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "http://ocramius.github.com/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://github.com/doctrine/instantiator",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ]
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "1.2.1",
+ "version_normalized": "1.2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "time": "2015-06-21T13:50:34+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ]
+ },
+ {
+ "name": "phpunit/phpunit-mock-objects",
+ "version": "5.0.6",
+ "version_normalized": "5.0.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
+ "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/33fd41a76e746b8fa96d00b49a23dadfa8334cdf",
+ "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.0.5",
+ "php": "^7.0",
+ "phpunit/php-text-template": "^1.2.1",
+ "sebastian/exporter": "^3.1"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.5"
+ },
+ "suggest": {
+ "ext-soap": "*"
+ },
+ "time": "2018-01-06T05:45:45+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Mock Object library for PHPUnit",
+ "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
+ "keywords": [
+ "mock",
+ "xunit"
+ ]
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "1.0.9",
+ "version_normalized": "1.0.9.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
+ "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.3 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
+ },
+ "time": "2017-02-26T11:10:40+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ]
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "1.4.5",
+ "version_normalized": "1.4.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
+ "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "time": "2017-11-27T13:52:08+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ]
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.1.0",
+ "version_normalized": "1.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b",
+ "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.0"
+ },
+ "time": "2017-04-07T12:08:54+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "1.0.1",
+ "version_normalized": "1.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
+ "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.7 || ^6.0"
+ },
+ "time": "2017-03-04T06:30:41+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/"
+ },
+ {
+ "name": "phpunit/php-token-stream",
+ "version": "2.0.2",
+ "version_normalized": "2.0.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+ "reference": "791198a2c6254db10131eecfe8c06670700904db"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db",
+ "reference": "791198a2c6254db10131eecfe8c06670700904db",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.2.4"
+ },
+ "time": "2017-11-27T05:48:46+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Wrapper around PHP's tokenizer extension.",
+ "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+ "keywords": [
+ "tokenizer"
+ ]
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "5.3.0",
+ "version_normalized": "5.3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/661f34d0bd3f1a7225ef491a70a020ad23a057a1",
+ "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.0",
+ "phpunit/php-file-iterator": "^1.4.2",
+ "phpunit/php-text-template": "^1.2.1",
+ "phpunit/php-token-stream": "^2.0.1",
+ "sebastian/code-unit-reverse-lookup": "^1.0.1",
+ "sebastian/environment": "^3.0",
+ "sebastian/version": "^2.0.1",
+ "theseer/tokenizer": "^1.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "suggest": {
+ "ext-xdebug": "^2.5.5"
+ },
+ "time": "2017-12-06T09:29:45+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.3.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ]
+ },
+ {
+ "name": "webmozart/assert",
+ "version": "1.2.0",
+ "version_normalized": "1.2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webmozart/assert.git",
+ "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f",
+ "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.3 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.6",
+ "sebastian/version": "^1.0.1"
+ },
+ "time": "2016-11-23T20:04:58+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Webmozart\\Assert\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Assertions to validate method input/output with nice error messages.",
+ "keywords": [
+ "assert",
+ "check",
+ "validate"
+ ]
+ },
+ {
+ "name": "phpdocumentor/reflection-common",
+ "version": "1.0.1",
+ "version_normalized": "1.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
+ "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.6"
+ },
+ "time": "2017-09-11T18:02:19+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": [
+ "src"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "opensource@ijaap.nl"
+ }
+ ],
+ "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+ "homepage": "http://www.phpdoc.org",
+ "keywords": [
+ "FQSEN",
+ "phpDocumentor",
+ "phpdoc",
+ "reflection",
+ "static analysis"
+ ]
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "0.4.0",
+ "version_normalized": "0.4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
+ "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5 || ^7.0",
+ "phpdocumentor/reflection-common": "^1.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^0.9.4",
+ "phpunit/phpunit": "^5.2||^4.8.24"
+ },
+ "time": "2017-07-14T14:27:02+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ]
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "4.2.0",
+ "version_normalized": "4.2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "66465776cfc249844bde6d117abff1d22e06c2da"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/66465776cfc249844bde6d117abff1d22e06c2da",
+ "reference": "66465776cfc249844bde6d117abff1d22e06c2da",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "phpdocumentor/reflection-common": "^1.0.0",
+ "phpdocumentor/type-resolver": "^0.4.0",
+ "webmozart/assert": "^1.0"
+ },
+ "require-dev": {
+ "doctrine/instantiator": "~1.0.5",
+ "mockery/mockery": "^1.0",
+ "phpunit/phpunit": "^6.4"
+ },
+ "time": "2017-11-27T17:38:31+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock."
+ },
+ {
+ "name": "phpspec/prophecy",
+ "version": "1.7.3",
+ "version_normalized": "1.7.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpspec/prophecy.git",
+ "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf",
+ "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.0.2",
+ "php": "^5.3|^7.0",
+ "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
+ "sebastian/comparator": "^1.1|^2.0",
+ "sebastian/recursion-context": "^1.0|^2.0|^3.0"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "^2.5|^3.2",
+ "phpunit/phpunit": "^4.8.35 || ^5.7"
+ },
+ "time": "2017-11-24T13:59:53+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.7.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-0": {
+ "Prophecy\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Konstantin Kudryashov",
+ "email": "ever.zet@gmail.com",
+ "homepage": "http://everzet.com"
+ },
+ {
+ "name": "Marcello Duarte",
+ "email": "marcello.duarte@gmail.com"
+ }
+ ],
+ "description": "Highly opinionated mocking framework for PHP 5.3+",
+ "homepage": "https://github.com/phpspec/prophecy",
+ "keywords": [
+ "Double",
+ "Dummy",
+ "fake",
+ "mock",
+ "spy",
+ "stub"
+ ]
+ },
+ {
+ "name": "phar-io/version",
+ "version": "1.0.1",
+ "version_normalized": "1.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df",
+ "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0"
+ },
+ "time": "2017-03-05T17:38:23+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "1.0.1",
+ "version_normalized": "1.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0",
+ "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-phar": "*",
+ "phar-io/version": "^1.0.1",
+ "php": "^5.6 || ^7.0"
+ },
+ "time": "2017-03-05T18:14:27+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.7.0",
+ "version_normalized": "1.7.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
+ "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.0",
+ "doctrine/common": "^2.6",
+ "phpunit/phpunit": "^4.1"
+ },
+ "time": "2017-10-19T19:58:43+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ },
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ]
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "6.5.5",
+ "version_normalized": "6.5.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "83d27937a310f2984fd575686138597147bdc7df"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/83d27937a310f2984fd575686138597147bdc7df",
+ "reference": "83d27937a310f2984fd575686138597147bdc7df",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "myclabs/deep-copy": "^1.6.1",
+ "phar-io/manifest": "^1.0.1",
+ "phar-io/version": "^1.0",
+ "php": "^7.0",
+ "phpspec/prophecy": "^1.7",
+ "phpunit/php-code-coverage": "^5.3",
+ "phpunit/php-file-iterator": "^1.4.3",
+ "phpunit/php-text-template": "^1.2.1",
+ "phpunit/php-timer": "^1.0.9",
+ "phpunit/phpunit-mock-objects": "^5.0.5",
+ "sebastian/comparator": "^2.1",
+ "sebastian/diff": "^2.0",
+ "sebastian/environment": "^3.1",
+ "sebastian/exporter": "^3.1",
+ "sebastian/global-state": "^2.0",
+ "sebastian/object-enumerator": "^3.0.3",
+ "sebastian/resource-operations": "^1.0",
+ "sebastian/version": "^2.0.1"
+ },
+ "conflict": {
+ "phpdocumentor/reflection-docblock": "3.0.2",
+ "phpunit/dbunit": "<3.0"
+ },
+ "require-dev": {
+ "ext-pdo": "*"
+ },
+ "suggest": {
+ "ext-xdebug": "*",
+ "phpunit/php-invoker": "^1.1"
+ },
+ "time": "2017-12-17T06:31:19+00:00",
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.5.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ]
+ }
+]
diff --git a/www/wiki/tests/phpunit/data/composer/new-composer.json b/www/wiki/tests/phpunit/data/composer/new-composer.json
index 0634c2dd..3a886769 100644
--- a/www/wiki/tests/phpunit/data/composer/new-composer.json
+++ b/www/wiki/tests/phpunit/data/composer/new-composer.json
@@ -9,7 +9,7 @@
"homepage": "https://www.mediawiki.org/wiki/Special:Version/Credits"
}
],
- "license": "GPL-2.0",
+ "license": "GPL-2.0-only",
"support": {
"issues": "https://bugzilla.wikimedia.org/",
"irc": "irc://irc.freenode.net/mediawiki",
diff --git a/www/wiki/tests/phpunit/data/cssmin/circle.svg b/www/wiki/tests/phpunit/data/cssmin/circle.svg
index 4f7af217..415d9920 100644
--- a/www/wiki/tests/phpunit/data/cssmin/circle.svg
+++ b/www/wiki/tests/phpunit/data/cssmin/circle.svg
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8">
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
<circle cx="4" cy="4" r="2"/>
-</svg>
+ <a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:title="?>">test</a>
+</svg> \ No newline at end of file
diff --git a/www/wiki/tests/phpunit/data/db/sqlite/tables-1.19.sql b/www/wiki/tests/phpunit/data/db/sqlite/tables-1.19.sql
new file mode 100644
index 00000000..db853fcb
--- /dev/null
+++ b/www/wiki/tests/phpunit/data/db/sqlite/tables-1.19.sql
@@ -0,0 +1,531 @@
+-- This is a copy of MediaWiki 1.19 schema shared by MySQL and SQLite.
+-- It is used for updater testing. Comments are stripped to decrease
+-- file size, as we don't need to maintain it.
+
+CREATE TABLE /*_*/user (
+ user_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ user_name varchar(255) binary NOT NULL default '',
+ user_real_name varchar(255) binary NOT NULL default '',
+ user_password tinyblob NOT NULL,
+ user_newpassword tinyblob NOT NULL,
+ user_newpass_time binary(14),
+ user_email tinytext NOT NULL,
+ user_touched binary(14) NOT NULL default '',
+ user_token binary(32) NOT NULL default '',
+ user_email_authenticated binary(14),
+ user_email_token binary(32),
+ user_email_token_expires binary(14),
+ user_registration binary(14),
+ user_editcount int
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_name ON /*_*/user (user_name);
+CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token);
+CREATE INDEX /*i*/user_email ON /*_*/user (user_email(50));
+CREATE TABLE /*_*/user_groups (
+ ug_user int unsigned NOT NULL default 0,
+ ug_group varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user,ug_group);
+CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
+CREATE TABLE /*_*/user_former_groups (
+ ufg_user int unsigned NOT NULL default 0,
+ ufg_group varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ufg_user_group ON /*_*/user_former_groups (ufg_user,ufg_group);
+CREATE TABLE /*_*/user_newtalk (
+ user_id int NOT NULL default 0,
+ user_ip varbinary(40) NOT NULL default '',
+ user_last_timestamp varbinary(14) NULL default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/un_user_id ON /*_*/user_newtalk (user_id);
+CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip);
+CREATE TABLE /*_*/user_properties (
+ up_user int NOT NULL,
+ up_property varbinary(255) NOT NULL,
+ up_value blob
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_properties_user_property ON /*_*/user_properties (up_user,up_property);
+CREATE INDEX /*i*/user_properties_property ON /*_*/user_properties (up_property);
+CREATE TABLE /*_*/page (
+ page_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ page_namespace int NOT NULL,
+ page_title varchar(255) binary NOT NULL,
+ page_restrictions tinyblob NOT NULL,
+ page_counter bigint unsigned NOT NULL default 0,
+ page_is_redirect tinyint unsigned NOT NULL default 0,
+ page_is_new tinyint unsigned NOT NULL default 0,
+ page_random real unsigned NOT NULL,
+ page_touched binary(14) NOT NULL default '',
+ page_latest int unsigned NOT NULL,
+ page_len int unsigned NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
+CREATE INDEX /*i*/page_random ON /*_*/page (page_random);
+CREATE INDEX /*i*/page_len ON /*_*/page (page_len);
+CREATE INDEX /*i*/page_redirect_namespace_len ON /*_*/page (page_is_redirect, page_namespace, page_len);
+CREATE TABLE /*_*/revision (
+ rev_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ rev_page int unsigned NOT NULL,
+ rev_text_id int unsigned NOT NULL,
+ rev_comment tinyblob NOT NULL,
+ rev_user int unsigned NOT NULL default 0,
+ rev_user_text varchar(255) binary NOT NULL default '',
+ rev_timestamp binary(14) NOT NULL default '',
+ rev_minor_edit tinyint unsigned NOT NULL default 0,
+ rev_deleted tinyint unsigned NOT NULL default 0,
+ rev_len int unsigned,
+ rev_parent_id int unsigned default NULL,
+ rev_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
+CREATE UNIQUE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
+CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp);
+CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
+CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
+CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
+CREATE TABLE /*_*/text (
+ old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ old_text mediumblob NOT NULL,
+ old_flags tinyblob NOT NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=10240;
+CREATE TABLE /*_*/archive (
+ ar_namespace int NOT NULL default 0,
+ ar_title varchar(255) binary NOT NULL default '',
+ ar_text mediumblob NOT NULL,
+ ar_comment tinyblob NOT NULL,
+ ar_user int unsigned NOT NULL default 0,
+ ar_user_text varchar(255) binary NOT NULL,
+ ar_timestamp binary(14) NOT NULL default '',
+ ar_minor_edit tinyint NOT NULL default 0,
+ ar_flags tinyblob NOT NULL,
+ ar_rev_id int unsigned,
+ ar_text_id int unsigned,
+ ar_deleted tinyint unsigned NOT NULL default 0,
+ ar_len int unsigned,
+ ar_page_id int unsigned,
+ ar_parent_id int unsigned default NULL,
+ ar_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
+CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id);
+CREATE TABLE /*_*/pagelinks (
+ pl_from int unsigned NOT NULL default 0,
+ pl_namespace int NOT NULL default 0,
+ pl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title);
+CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from);
+CREATE TABLE /*_*/templatelinks (
+ tl_from int unsigned NOT NULL default 0,
+ tl_namespace int NOT NULL default 0,
+ tl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title);
+CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from);
+CREATE TABLE /*_*/imagelinks (
+ il_from int unsigned NOT NULL default 0,
+ il_to varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to);
+CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from);
+CREATE TABLE /*_*/categorylinks (
+ cl_from int unsigned NOT NULL default 0,
+ cl_to varchar(255) binary NOT NULL default '',
+ cl_sortkey varbinary(230) NOT NULL default '',
+ cl_sortkey_prefix varchar(255) binary NOT NULL default '',
+ cl_timestamp timestamp NOT NULL,
+ cl_collation varbinary(32) NOT NULL default '',
+ cl_type ENUM('page', 'subcat', 'file') NOT NULL default 'page'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to);
+CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_type,cl_sortkey,cl_from);
+CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp);
+CREATE INDEX /*i*/cl_collation ON /*_*/categorylinks (cl_collation);
+CREATE TABLE /*_*/category (
+ cat_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ cat_title varchar(255) binary NOT NULL,
+ cat_pages int signed NOT NULL default 0,
+ cat_subcats int signed NOT NULL default 0,
+ cat_files int signed NOT NULL default 0,
+ cat_hidden tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cat_title ON /*_*/category (cat_title);
+CREATE INDEX /*i*/cat_pages ON /*_*/category (cat_pages);
+CREATE TABLE /*_*/externallinks (
+ el_from int unsigned NOT NULL default 0,
+ el_to blob NOT NULL,
+ el_index blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from, el_to(40));
+CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from);
+CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60));
+CREATE TABLE /*_*/external_user (
+ eu_local_id int unsigned NOT NULL PRIMARY KEY,
+ eu_external_id varchar(255) binary NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/eu_external_id ON /*_*/external_user (eu_external_id);
+CREATE TABLE /*_*/langlinks (
+ ll_from int unsigned NOT NULL default 0,
+ ll_lang varbinary(20) NOT NULL default '',
+ ll_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ll_from ON /*_*/langlinks (ll_from, ll_lang);
+CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title);
+CREATE TABLE /*_*/iwlinks (
+ iwl_from int unsigned NOT NULL default 0,
+ iwl_prefix varbinary(20) NOT NULL default '',
+ iwl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title);
+CREATE UNIQUE INDEX /*i*/iwl_prefix_title_from ON /*_*/iwlinks (iwl_prefix, iwl_title, iwl_from);
+CREATE TABLE /*_*/site_stats (
+ ss_row_id int unsigned NOT NULL,
+ ss_total_views bigint unsigned default 0,
+ ss_total_edits bigint unsigned default 0,
+ ss_good_articles bigint unsigned default 0,
+ ss_total_pages bigint default '-1',
+ ss_users bigint default '-1',
+ ss_active_users bigint default '-1',
+ ss_admins int default '-1',
+ ss_images int default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id);
+CREATE TABLE /*_*/hitcounter (
+ hc_id int unsigned NOT NULL
+) ENGINE=HEAP MAX_ROWS=25000;
+CREATE TABLE /*_*/ipblocks (
+ ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ ipb_address tinyblob NOT NULL,
+ ipb_user int unsigned NOT NULL default 0,
+ ipb_by int unsigned NOT NULL default 0,
+ ipb_by_text varchar(255) binary NOT NULL default '',
+ ipb_reason tinyblob NOT NULL,
+ ipb_timestamp binary(14) NOT NULL default '',
+ ipb_auto bool NOT NULL default 0,
+ ipb_anon_only bool NOT NULL default 0,
+ ipb_create_account bool NOT NULL default 1,
+ ipb_enable_autoblock bool NOT NULL default '1',
+ ipb_expiry varbinary(14) NOT NULL default '',
+ ipb_range_start tinyblob NOT NULL,
+ ipb_range_end tinyblob NOT NULL,
+ ipb_deleted bool NOT NULL default 0,
+ ipb_block_email bool NOT NULL default 0,
+ ipb_allow_usertalk bool NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
+CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
+CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
+CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
+CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
+CREATE TABLE /*_*/image (
+ img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
+ img_size int unsigned NOT NULL default 0,
+ img_width int NOT NULL default 0,
+ img_height int NOT NULL default 0,
+ img_metadata mediumblob NOT NULL,
+ img_bits int NOT NULL default 0,
+ img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+ img_minor_mime varbinary(100) NOT NULL default "unknown",
+ img_description tinyblob NOT NULL,
+ img_user int unsigned NOT NULL default 0,
+ img_user_text varchar(255) binary NOT NULL,
+ img_timestamp varbinary(14) NOT NULL default '',
+ img_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
+CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
+CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
+CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1);
+CREATE TABLE /*_*/oldimage (
+ oi_name varchar(255) binary NOT NULL default '',
+ oi_archive_name varchar(255) binary NOT NULL default '',
+ oi_size int unsigned NOT NULL default 0,
+ oi_width int NOT NULL default 0,
+ oi_height int NOT NULL default 0,
+ oi_bits int NOT NULL default 0,
+ oi_description tinyblob NOT NULL,
+ oi_user int unsigned NOT NULL default 0,
+ oi_user_text varchar(255) binary NOT NULL,
+ oi_timestamp binary(14) NOT NULL default '',
+ oi_metadata mediumblob NOT NULL,
+ oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+ oi_minor_mime varbinary(100) NOT NULL default "unknown",
+ oi_deleted tinyint unsigned NOT NULL default 0,
+ oi_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
+CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
+CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
+CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1);
+CREATE TABLE /*_*/filearchive (
+ fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ fa_name varchar(255) binary NOT NULL default '',
+ fa_archive_name varchar(255) binary default '',
+ fa_storage_group varbinary(16),
+ fa_storage_key varbinary(64) default '',
+ fa_deleted_user int,
+ fa_deleted_timestamp binary(14) default '',
+ fa_deleted_reason text,
+ fa_size int unsigned default 0,
+ fa_width int default 0,
+ fa_height int default 0,
+ fa_metadata mediumblob,
+ fa_bits int default 0,
+ fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") default "unknown",
+ fa_minor_mime varbinary(100) default "unknown",
+ fa_description tinyblob,
+ fa_user int unsigned default 0,
+ fa_user_text varchar(255) binary,
+ fa_timestamp binary(14) default '',
+ fa_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
+CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
+CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
+CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
+CREATE TABLE /*_*/uploadstash (
+ us_id int unsigned NOT NULL PRIMARY KEY auto_increment,
+ us_user int unsigned NOT NULL,
+ us_key varchar(255) NOT NULL,
+ us_orig_path varchar(255) NOT NULL,
+ us_path varchar(255) NOT NULL,
+ us_source_type varchar(50),
+ us_timestamp varbinary(14) not null,
+ us_status varchar(50) not null,
+ us_chunk_inx int unsigned NULL,
+ us_size int unsigned NOT NULL,
+ us_sha1 varchar(31) NOT NULL,
+ us_mime varchar(255),
+ us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ us_image_width int unsigned,
+ us_image_height int unsigned,
+ us_image_bits smallint unsigned
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/us_user ON /*_*/uploadstash (us_user);
+CREATE UNIQUE INDEX /*i*/us_key ON /*_*/uploadstash (us_key);
+CREATE INDEX /*i*/us_timestamp ON /*_*/uploadstash (us_timestamp);
+CREATE TABLE /*_*/recentchanges (
+ rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ rc_timestamp varbinary(14) NOT NULL default '',
+ rc_cur_time varbinary(14) NOT NULL default '',
+ rc_user int unsigned NOT NULL default 0,
+ rc_user_text varchar(255) binary NOT NULL,
+ rc_namespace int NOT NULL default 0,
+ rc_title varchar(255) binary NOT NULL default '',
+ rc_comment varchar(255) binary NOT NULL default '',
+ rc_minor tinyint unsigned NOT NULL default 0,
+ rc_bot tinyint unsigned NOT NULL default 0,
+ rc_new tinyint unsigned NOT NULL default 0,
+ rc_cur_id int unsigned NOT NULL default 0,
+ rc_this_oldid int unsigned NOT NULL default 0,
+ rc_last_oldid int unsigned NOT NULL default 0,
+ rc_type tinyint unsigned NOT NULL default 0,
+ rc_moved_to_ns tinyint unsigned NOT NULL default 0,
+ rc_moved_to_title varchar(255) binary NOT NULL default '',
+ rc_patrolled tinyint unsigned NOT NULL default 0,
+ rc_ip varbinary(40) NOT NULL default '',
+ rc_old_len int,
+ rc_new_len int,
+ rc_deleted tinyint unsigned NOT NULL default 0,
+ rc_logid int unsigned NOT NULL default 0,
+ rc_log_type varbinary(255) NULL default NULL,
+ rc_log_action varbinary(255) NULL default NULL,
+ rc_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp);
+CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title);
+CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id);
+CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp);
+CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
+CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
+CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
+CREATE TABLE /*_*/watchlist (
+ wl_user int unsigned NOT NULL,
+ wl_namespace int NOT NULL default 0,
+ wl_title varchar(255) binary NOT NULL default '',
+ wl_notificationtimestamp varbinary(14)
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title);
+CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title);
+CREATE TABLE /*_*/searchindex (
+ si_page int unsigned NOT NULL,
+ si_title varchar(255) NOT NULL default '',
+ si_text mediumtext NOT NULL
+) ENGINE=MyISAM;
+CREATE UNIQUE INDEX /*i*/si_page ON /*_*/searchindex (si_page);
+CREATE FULLTEXT INDEX /*i*/si_title ON /*_*/searchindex (si_title);
+CREATE FULLTEXT INDEX /*i*/si_text ON /*_*/searchindex (si_text);
+CREATE TABLE /*_*/interwiki (
+ iw_prefix varchar(32) NOT NULL,
+ iw_url blob NOT NULL,
+ iw_api blob NOT NULL,
+ iw_wikiid varchar(64) NOT NULL,
+ iw_local bool NOT NULL,
+ iw_trans tinyint NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix);
+CREATE TABLE /*_*/querycache (
+ qc_type varbinary(32) NOT NULL,
+ qc_value int unsigned NOT NULL default 0,
+ qc_namespace int NOT NULL default 0,
+ qc_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qc_type ON /*_*/querycache (qc_type,qc_value);
+CREATE TABLE /*_*/objectcache (
+ keyname varbinary(255) NOT NULL default '' PRIMARY KEY,
+ value mediumblob,
+ exptime datetime
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime);
+CREATE TABLE /*_*/transcache (
+ tc_url varbinary(255) NOT NULL,
+ tc_contents text,
+ tc_time binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url);
+CREATE TABLE /*_*/logging (
+ log_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ log_type varbinary(32) NOT NULL default '',
+ log_action varbinary(32) NOT NULL default '',
+ log_timestamp binary(14) NOT NULL default '19700101000000',
+ log_user int unsigned NOT NULL default 0,
+ log_user_text varchar(255) binary NOT NULL default '',
+ log_namespace int NOT NULL default 0,
+ log_title varchar(255) binary NOT NULL default '',
+ log_page int unsigned NULL,
+ log_comment varchar(255) NOT NULL default '',
+ log_params blob NOT NULL,
+ log_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
+CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
+CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
+CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
+CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
+CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
+CREATE INDEX /*i*/type_action ON /*_*/logging(log_type, log_action, log_timestamp);
+CREATE TABLE /*_*/log_search (
+ ls_field varbinary(32) NOT NULL,
+ ls_value varchar(255) NOT NULL,
+ ls_log_id int unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ls_field_val ON /*_*/log_search (ls_field,ls_value,ls_log_id);
+CREATE INDEX /*i*/ls_log_id ON /*_*/log_search (ls_log_id);
+CREATE TABLE /*_*/job (
+ job_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ job_cmd varbinary(60) NOT NULL default '',
+ job_namespace int NOT NULL,
+ job_title varchar(255) binary NOT NULL,
+ job_timestamp varbinary(14) NULL default NULL,
+ job_params blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
+CREATE INDEX /*i*/job_timestamp ON /*_*/job(job_timestamp);
+CREATE TABLE /*_*/querycache_info (
+ qci_type varbinary(32) NOT NULL default '',
+ qci_timestamp binary(14) NOT NULL default '19700101000000'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/qci_type ON /*_*/querycache_info (qci_type);
+CREATE TABLE /*_*/redirect (
+ rd_from int unsigned NOT NULL default 0 PRIMARY KEY,
+ rd_namespace int NOT NULL default 0,
+ rd_title varchar(255) binary NOT NULL default '',
+ rd_interwiki varchar(32) default NULL,
+ rd_fragment varchar(255) binary default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from);
+CREATE TABLE /*_*/querycachetwo (
+ qcc_type varbinary(32) NOT NULL,
+ qcc_value int unsigned NOT NULL default 0,
+ qcc_namespace int NOT NULL default 0,
+ qcc_title varchar(255) binary NOT NULL default '',
+ qcc_namespacetwo int NOT NULL default 0,
+ qcc_titletwo varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qcc_type ON /*_*/querycachetwo (qcc_type,qcc_value);
+CREATE INDEX /*i*/qcc_title ON /*_*/querycachetwo (qcc_type,qcc_namespace,qcc_title);
+CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
+CREATE TABLE /*_*/page_restrictions (
+ pr_page int NOT NULL,
+ pr_type varbinary(60) NOT NULL,
+ pr_level varbinary(60) NOT NULL,
+ pr_cascade tinyint NOT NULL,
+ pr_user int NULL,
+ pr_expiry varbinary(14) NULL,
+ pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions (pr_page,pr_type);
+CREATE INDEX /*i*/pr_typelevel ON /*_*/page_restrictions (pr_type,pr_level);
+CREATE INDEX /*i*/pr_level ON /*_*/page_restrictions (pr_level);
+CREATE INDEX /*i*/pr_cascade ON /*_*/page_restrictions (pr_cascade);
+CREATE TABLE /*_*/protected_titles (
+ pt_namespace int NOT NULL,
+ pt_title varchar(255) binary NOT NULL,
+ pt_user int unsigned NOT NULL,
+ pt_reason tinyblob,
+ pt_timestamp binary(14) NOT NULL,
+ pt_expiry varbinary(14) NOT NULL default '',
+ pt_create_perm varbinary(60) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pt_namespace_title ON /*_*/protected_titles (pt_namespace,pt_title);
+CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp);
+CREATE TABLE /*_*/page_props (
+ pp_page int NOT NULL,
+ pp_propname varbinary(60) NOT NULL,
+ pp_value blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname);
+CREATE TABLE /*_*/updatelog (
+ ul_key varchar(255) NOT NULL PRIMARY KEY,
+ ul_value blob
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/change_tag (
+ ct_rc_id int NULL,
+ ct_log_id int NULL,
+ ct_rev_id int NULL,
+ ct_tag varchar(255) NOT NULL,
+ ct_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/change_tag_rc_tag ON /*_*/change_tag (ct_rc_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_log_tag ON /*_*/change_tag (ct_log_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag);
+CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
+CREATE TABLE /*_*/tag_summary (
+ ts_rc_id int NULL,
+ ts_log_id int NULL,
+ ts_rev_id int NULL,
+ ts_tags blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id);
+CREATE TABLE /*_*/valid_tag (
+ vt_tag varchar(255) NOT NULL PRIMARY KEY
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/l10n_cache (
+ lc_lang varbinary(32) NOT NULL,
+ lc_key varchar(255) NOT NULL,
+ lc_value mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key);
+CREATE TABLE /*_*/msg_resource (
+ mr_resource varbinary(255) NOT NULL,
+ mr_lang varbinary(32) NOT NULL,
+ mr_blob mediumblob NOT NULL,
+ mr_timestamp binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mr_resource_lang ON /*_*/msg_resource (mr_resource, mr_lang);
+CREATE TABLE /*_*/msg_resource_links (
+ mrl_resource varbinary(255) NOT NULL,
+ mrl_message varbinary(255) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mrl_message_resource ON /*_*/msg_resource_links (mrl_message, mrl_resource);
+CREATE TABLE /*_*/module_deps (
+ md_module varbinary(255) NOT NULL,
+ md_skin varbinary(32) NOT NULL,
+ md_deps mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/md_module_skin ON /*_*/module_deps (md_module, md_skin);
diff --git a/www/wiki/tests/phpunit/data/db/sqlite/tables-1.20.sql b/www/wiki/tests/phpunit/data/db/sqlite/tables-1.20.sql
new file mode 100644
index 00000000..d6c4f5bc
--- /dev/null
+++ b/www/wiki/tests/phpunit/data/db/sqlite/tables-1.20.sql
@@ -0,0 +1,534 @@
+-- This is a copy of MediaWiki 1.20 schema shared by MySQL and SQLite.
+-- It is used for updater testing. Comments are stripped to decrease
+-- file size, as we don't need to maintain it.
+
+CREATE TABLE /*_*/user (
+ user_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ user_name varchar(255) binary NOT NULL default '',
+ user_real_name varchar(255) binary NOT NULL default '',
+ user_password tinyblob NOT NULL,
+ user_newpassword tinyblob NOT NULL,
+ user_newpass_time binary(14),
+ user_email tinytext NOT NULL,
+ user_touched binary(14) NOT NULL default '',
+ user_token binary(32) NOT NULL default '',
+ user_email_authenticated binary(14),
+ user_email_token binary(32),
+ user_email_token_expires binary(14),
+ user_registration binary(14),
+ user_editcount int
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_name ON /*_*/user (user_name);
+CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token);
+CREATE INDEX /*i*/user_email ON /*_*/user (user_email(50));
+CREATE TABLE /*_*/user_groups (
+ ug_user int unsigned NOT NULL default 0,
+ ug_group varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user,ug_group);
+CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
+CREATE TABLE /*_*/user_former_groups (
+ ufg_user int unsigned NOT NULL default 0,
+ ufg_group varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ufg_user_group ON /*_*/user_former_groups (ufg_user,ufg_group);
+CREATE TABLE /*_*/user_newtalk (
+ user_id int NOT NULL default 0,
+ user_ip varbinary(40) NOT NULL default '',
+ user_last_timestamp varbinary(14) NULL default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/un_user_id ON /*_*/user_newtalk (user_id);
+CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip);
+CREATE TABLE /*_*/user_properties (
+ up_user int NOT NULL,
+ up_property varbinary(255) NOT NULL,
+ up_value blob
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_properties_user_property ON /*_*/user_properties (up_user,up_property);
+CREATE INDEX /*i*/user_properties_property ON /*_*/user_properties (up_property);
+CREATE TABLE /*_*/page (
+ page_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ page_namespace int NOT NULL,
+ page_title varchar(255) binary NOT NULL,
+ page_restrictions tinyblob NOT NULL,
+ page_counter bigint unsigned NOT NULL default 0,
+ page_is_redirect tinyint unsigned NOT NULL default 0,
+ page_is_new tinyint unsigned NOT NULL default 0,
+ page_random real unsigned NOT NULL,
+ page_touched binary(14) NOT NULL default '',
+ page_latest int unsigned NOT NULL,
+ page_len int unsigned NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
+CREATE INDEX /*i*/page_random ON /*_*/page (page_random);
+CREATE INDEX /*i*/page_len ON /*_*/page (page_len);
+CREATE INDEX /*i*/page_redirect_namespace_len ON /*_*/page (page_is_redirect, page_namespace, page_len);
+CREATE TABLE /*_*/revision (
+ rev_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ rev_page int unsigned NOT NULL,
+ rev_text_id int unsigned NOT NULL,
+ rev_comment tinyblob NOT NULL,
+ rev_user int unsigned NOT NULL default 0,
+ rev_user_text varchar(255) binary NOT NULL default '',
+ rev_timestamp binary(14) NOT NULL default '',
+ rev_minor_edit tinyint unsigned NOT NULL default 0,
+ rev_deleted tinyint unsigned NOT NULL default 0,
+ rev_len int unsigned,
+ rev_parent_id int unsigned default NULL,
+ rev_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
+CREATE UNIQUE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
+CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp);
+CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
+CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
+CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
+CREATE INDEX /*i*/page_user_timestamp ON /*_*/revision (rev_page,rev_user,rev_timestamp);
+CREATE TABLE /*_*/text (
+ old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ old_text mediumblob NOT NULL,
+ old_flags tinyblob NOT NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=10240;
+CREATE TABLE /*_*/archive (
+ ar_namespace int NOT NULL default 0,
+ ar_title varchar(255) binary NOT NULL default '',
+ ar_text mediumblob NOT NULL,
+ ar_comment tinyblob NOT NULL,
+ ar_user int unsigned NOT NULL default 0,
+ ar_user_text varchar(255) binary NOT NULL,
+ ar_timestamp binary(14) NOT NULL default '',
+ ar_minor_edit tinyint NOT NULL default 0,
+ ar_flags tinyblob NOT NULL,
+ ar_rev_id int unsigned,
+ ar_text_id int unsigned,
+ ar_deleted tinyint unsigned NOT NULL default 0,
+ ar_len int unsigned,
+ ar_page_id int unsigned,
+ ar_parent_id int unsigned default NULL,
+ ar_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
+CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id);
+CREATE TABLE /*_*/pagelinks (
+ pl_from int unsigned NOT NULL default 0,
+ pl_namespace int NOT NULL default 0,
+ pl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title);
+CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from);
+CREATE TABLE /*_*/templatelinks (
+ tl_from int unsigned NOT NULL default 0,
+ tl_namespace int NOT NULL default 0,
+ tl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title);
+CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from);
+CREATE TABLE /*_*/imagelinks (
+ il_from int unsigned NOT NULL default 0,
+ il_to varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to);
+CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from);
+CREATE TABLE /*_*/categorylinks (
+ cl_from int unsigned NOT NULL default 0,
+ cl_to varchar(255) binary NOT NULL default '',
+ cl_sortkey varbinary(230) NOT NULL default '',
+ cl_sortkey_prefix varchar(255) binary NOT NULL default '',
+ cl_timestamp timestamp NOT NULL,
+ cl_collation varbinary(32) NOT NULL default '',
+ cl_type ENUM('page', 'subcat', 'file') NOT NULL default 'page'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to);
+CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_type,cl_sortkey,cl_from);
+CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp);
+CREATE INDEX /*i*/cl_collation ON /*_*/categorylinks (cl_collation);
+CREATE TABLE /*_*/category (
+ cat_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ cat_title varchar(255) binary NOT NULL,
+ cat_pages int signed NOT NULL default 0,
+ cat_subcats int signed NOT NULL default 0,
+ cat_files int signed NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cat_title ON /*_*/category (cat_title);
+CREATE INDEX /*i*/cat_pages ON /*_*/category (cat_pages);
+CREATE TABLE /*_*/externallinks (
+ el_from int unsigned NOT NULL default 0,
+ el_to blob NOT NULL,
+ el_index blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from, el_to(40));
+CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from);
+CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60));
+CREATE TABLE /*_*/external_user (
+ eu_local_id int unsigned NOT NULL PRIMARY KEY,
+ eu_external_id varchar(255) binary NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/eu_external_id ON /*_*/external_user (eu_external_id);
+CREATE TABLE /*_*/langlinks (
+ ll_from int unsigned NOT NULL default 0,
+ ll_lang varbinary(20) NOT NULL default '',
+ ll_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ll_from ON /*_*/langlinks (ll_from, ll_lang);
+CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title);
+CREATE TABLE /*_*/iwlinks (
+ iwl_from int unsigned NOT NULL default 0,
+ iwl_prefix varbinary(20) NOT NULL default '',
+ iwl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title);
+CREATE UNIQUE INDEX /*i*/iwl_prefix_title_from ON /*_*/iwlinks (iwl_prefix, iwl_title, iwl_from);
+CREATE TABLE /*_*/site_stats (
+ ss_row_id int unsigned NOT NULL,
+ ss_total_views bigint unsigned default 0,
+ ss_total_edits bigint unsigned default 0,
+ ss_good_articles bigint unsigned default 0,
+ ss_total_pages bigint default '-1',
+ ss_users bigint default '-1',
+ ss_active_users bigint default '-1',
+ ss_admins int default '-1',
+ ss_images int default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id);
+CREATE TABLE /*_*/hitcounter (
+ hc_id int unsigned NOT NULL
+) ENGINE=HEAP MAX_ROWS=25000;
+CREATE TABLE /*_*/ipblocks (
+ ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ ipb_address tinyblob NOT NULL,
+ ipb_user int unsigned NOT NULL default 0,
+ ipb_by int unsigned NOT NULL default 0,
+ ipb_by_text varchar(255) binary NOT NULL default '',
+ ipb_reason tinyblob NOT NULL,
+ ipb_timestamp binary(14) NOT NULL default '',
+ ipb_auto bool NOT NULL default 0,
+ ipb_anon_only bool NOT NULL default 0,
+ ipb_create_account bool NOT NULL default 1,
+ ipb_enable_autoblock bool NOT NULL default '1',
+ ipb_expiry varbinary(14) NOT NULL default '',
+ ipb_range_start tinyblob NOT NULL,
+ ipb_range_end tinyblob NOT NULL,
+ ipb_deleted bool NOT NULL default 0,
+ ipb_block_email bool NOT NULL default 0,
+ ipb_allow_usertalk bool NOT NULL default 0,
+ ipb_parent_block_id int default NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
+CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
+CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
+CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
+CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
+CREATE INDEX /*i*/ipb_parent_block_id ON /*_*/ipblocks (ipb_parent_block_id);
+CREATE TABLE /*_*/image (
+ img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
+ img_size int unsigned NOT NULL default 0,
+ img_width int NOT NULL default 0,
+ img_height int NOT NULL default 0,
+ img_metadata mediumblob NOT NULL,
+ img_bits int NOT NULL default 0,
+ img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+ img_minor_mime varbinary(100) NOT NULL default "unknown",
+ img_description tinyblob NOT NULL,
+ img_user int unsigned NOT NULL default 0,
+ img_user_text varchar(255) binary NOT NULL,
+ img_timestamp varbinary(14) NOT NULL default '',
+ img_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
+CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
+CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
+CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1);
+CREATE TABLE /*_*/oldimage (
+ oi_name varchar(255) binary NOT NULL default '',
+ oi_archive_name varchar(255) binary NOT NULL default '',
+ oi_size int unsigned NOT NULL default 0,
+ oi_width int NOT NULL default 0,
+ oi_height int NOT NULL default 0,
+ oi_bits int NOT NULL default 0,
+ oi_description tinyblob NOT NULL,
+ oi_user int unsigned NOT NULL default 0,
+ oi_user_text varchar(255) binary NOT NULL,
+ oi_timestamp binary(14) NOT NULL default '',
+ oi_metadata mediumblob NOT NULL,
+ oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+ oi_minor_mime varbinary(100) NOT NULL default "unknown",
+ oi_deleted tinyint unsigned NOT NULL default 0,
+ oi_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
+CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
+CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
+CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1);
+CREATE TABLE /*_*/filearchive (
+ fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ fa_name varchar(255) binary NOT NULL default '',
+ fa_archive_name varchar(255) binary default '',
+ fa_storage_group varbinary(16),
+ fa_storage_key varbinary(64) default '',
+ fa_deleted_user int,
+ fa_deleted_timestamp binary(14) default '',
+ fa_deleted_reason text,
+ fa_size int unsigned default 0,
+ fa_width int default 0,
+ fa_height int default 0,
+ fa_metadata mediumblob,
+ fa_bits int default 0,
+ fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") default "unknown",
+ fa_minor_mime varbinary(100) default "unknown",
+ fa_description tinyblob,
+ fa_user int unsigned default 0,
+ fa_user_text varchar(255) binary,
+ fa_timestamp binary(14) default '',
+ fa_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
+CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
+CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
+CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
+CREATE TABLE /*_*/uploadstash (
+ us_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ us_user int unsigned NOT NULL,
+ us_key varchar(255) NOT NULL,
+ us_orig_path varchar(255) NOT NULL,
+ us_path varchar(255) NOT NULL,
+ us_source_type varchar(50),
+ us_timestamp varbinary(14) NOT NULL,
+ us_status varchar(50) NOT NULL,
+ us_chunk_inx int unsigned NULL,
+ us_size int unsigned NOT NULL,
+ us_sha1 varchar(31) NOT NULL,
+ us_mime varchar(255),
+ us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ us_image_width int unsigned,
+ us_image_height int unsigned,
+ us_image_bits smallint unsigned
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/us_user ON /*_*/uploadstash (us_user);
+CREATE UNIQUE INDEX /*i*/us_key ON /*_*/uploadstash (us_key);
+CREATE INDEX /*i*/us_timestamp ON /*_*/uploadstash (us_timestamp);
+CREATE TABLE /*_*/recentchanges (
+ rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ rc_timestamp varbinary(14) NOT NULL default '',
+ rc_cur_time varbinary(14) NOT NULL default '',
+ rc_user int unsigned NOT NULL default 0,
+ rc_user_text varchar(255) binary NOT NULL,
+ rc_namespace int NOT NULL default 0,
+ rc_title varchar(255) binary NOT NULL default '',
+ rc_comment varchar(255) binary NOT NULL default '',
+ rc_minor tinyint unsigned NOT NULL default 0,
+ rc_bot tinyint unsigned NOT NULL default 0,
+ rc_new tinyint unsigned NOT NULL default 0,
+ rc_cur_id int unsigned NOT NULL default 0,
+ rc_this_oldid int unsigned NOT NULL default 0,
+ rc_last_oldid int unsigned NOT NULL default 0,
+ rc_type tinyint unsigned NOT NULL default 0,
+ rc_moved_to_ns tinyint unsigned NOT NULL default 0,
+ rc_moved_to_title varchar(255) binary NOT NULL default '',
+ rc_patrolled tinyint unsigned NOT NULL default 0,
+ rc_ip varbinary(40) NOT NULL default '',
+ rc_old_len int,
+ rc_new_len int,
+ rc_deleted tinyint unsigned NOT NULL default 0,
+ rc_logid int unsigned NOT NULL default 0,
+ rc_log_type varbinary(255) NULL default NULL,
+ rc_log_action varbinary(255) NULL default NULL,
+ rc_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp);
+CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title);
+CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id);
+CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp);
+CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
+CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
+CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
+CREATE TABLE /*_*/watchlist (
+ wl_user int unsigned NOT NULL,
+ wl_namespace int NOT NULL default 0,
+ wl_title varchar(255) binary NOT NULL default '',
+ wl_notificationtimestamp varbinary(14)
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title);
+CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title);
+CREATE TABLE /*_*/searchindex (
+ si_page int unsigned NOT NULL,
+ si_title varchar(255) NOT NULL default '',
+ si_text mediumtext NOT NULL
+) ENGINE=MyISAM;
+CREATE UNIQUE INDEX /*i*/si_page ON /*_*/searchindex (si_page);
+CREATE FULLTEXT INDEX /*i*/si_title ON /*_*/searchindex (si_title);
+CREATE FULLTEXT INDEX /*i*/si_text ON /*_*/searchindex (si_text);
+CREATE TABLE /*_*/interwiki (
+ iw_prefix varchar(32) NOT NULL,
+ iw_url blob NOT NULL,
+ iw_api blob NOT NULL,
+ iw_wikiid varchar(64) NOT NULL,
+ iw_local bool NOT NULL,
+ iw_trans tinyint NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix);
+CREATE TABLE /*_*/querycache (
+ qc_type varbinary(32) NOT NULL,
+ qc_value int unsigned NOT NULL default 0,
+ qc_namespace int NOT NULL default 0,
+ qc_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qc_type ON /*_*/querycache (qc_type,qc_value);
+CREATE TABLE /*_*/objectcache (
+ keyname varbinary(255) NOT NULL default '' PRIMARY KEY,
+ value mediumblob,
+ exptime datetime
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime);
+CREATE TABLE /*_*/transcache (
+ tc_url varbinary(255) NOT NULL,
+ tc_contents text,
+ tc_time binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url);
+CREATE TABLE /*_*/logging (
+ log_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ log_type varbinary(32) NOT NULL default '',
+ log_action varbinary(32) NOT NULL default '',
+ log_timestamp binary(14) NOT NULL default '19700101000000',
+ log_user int unsigned NOT NULL default 0,
+ log_user_text varchar(255) binary NOT NULL default '',
+ log_namespace int NOT NULL default 0,
+ log_title varchar(255) binary NOT NULL default '',
+ log_page int unsigned NULL,
+ log_comment varchar(255) NOT NULL default '',
+ log_params blob NOT NULL,
+ log_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
+CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
+CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
+CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
+CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
+CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
+CREATE INDEX /*i*/type_action ON /*_*/logging (log_type, log_action, log_timestamp);
+CREATE TABLE /*_*/log_search (
+ ls_field varbinary(32) NOT NULL,
+ ls_value varchar(255) NOT NULL,
+ ls_log_id int unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ls_field_val ON /*_*/log_search (ls_field,ls_value,ls_log_id);
+CREATE INDEX /*i*/ls_log_id ON /*_*/log_search (ls_log_id);
+CREATE TABLE /*_*/job (
+ job_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ job_cmd varbinary(60) NOT NULL default '',
+ job_namespace int NOT NULL,
+ job_title varchar(255) binary NOT NULL,
+ job_timestamp varbinary(14) NULL default NULL,
+ job_params blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
+CREATE INDEX /*i*/job_timestamp ON /*_*/job (job_timestamp);
+CREATE TABLE /*_*/querycache_info (
+ qci_type varbinary(32) NOT NULL default '',
+ qci_timestamp binary(14) NOT NULL default '19700101000000'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/qci_type ON /*_*/querycache_info (qci_type);
+CREATE TABLE /*_*/redirect (
+ rd_from int unsigned NOT NULL default 0 PRIMARY KEY,
+ rd_namespace int NOT NULL default 0,
+ rd_title varchar(255) binary NOT NULL default '',
+ rd_interwiki varchar(32) default NULL,
+ rd_fragment varchar(255) binary default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from);
+CREATE TABLE /*_*/querycachetwo (
+ qcc_type varbinary(32) NOT NULL,
+ qcc_value int unsigned NOT NULL default 0,
+ qcc_namespace int NOT NULL default 0,
+ qcc_title varchar(255) binary NOT NULL default '',
+ qcc_namespacetwo int NOT NULL default 0,
+ qcc_titletwo varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qcc_type ON /*_*/querycachetwo (qcc_type,qcc_value);
+CREATE INDEX /*i*/qcc_title ON /*_*/querycachetwo (qcc_type,qcc_namespace,qcc_title);
+CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
+CREATE TABLE /*_*/page_restrictions (
+ pr_page int NOT NULL,
+ pr_type varbinary(60) NOT NULL,
+ pr_level varbinary(60) NOT NULL,
+ pr_cascade tinyint NOT NULL,
+ pr_user int NULL,
+ pr_expiry varbinary(14) NULL,
+ pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions (pr_page,pr_type);
+CREATE INDEX /*i*/pr_typelevel ON /*_*/page_restrictions (pr_type,pr_level);
+CREATE INDEX /*i*/pr_level ON /*_*/page_restrictions (pr_level);
+CREATE INDEX /*i*/pr_cascade ON /*_*/page_restrictions (pr_cascade);
+CREATE TABLE /*_*/protected_titles (
+ pt_namespace int NOT NULL,
+ pt_title varchar(255) binary NOT NULL,
+ pt_user int unsigned NOT NULL,
+ pt_reason tinyblob,
+ pt_timestamp binary(14) NOT NULL,
+ pt_expiry varbinary(14) NOT NULL default '',
+ pt_create_perm varbinary(60) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pt_namespace_title ON /*_*/protected_titles (pt_namespace,pt_title);
+CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp);
+CREATE TABLE /*_*/page_props (
+ pp_page int NOT NULL,
+ pp_propname varbinary(60) NOT NULL,
+ pp_value blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname);
+CREATE TABLE /*_*/updatelog (
+ ul_key varchar(255) NOT NULL PRIMARY KEY,
+ ul_value blob
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/change_tag (
+ ct_rc_id int NULL,
+ ct_log_id int NULL,
+ ct_rev_id int NULL,
+ ct_tag varchar(255) NOT NULL,
+ ct_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/change_tag_rc_tag ON /*_*/change_tag (ct_rc_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_log_tag ON /*_*/change_tag (ct_log_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag);
+CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
+CREATE TABLE /*_*/tag_summary (
+ ts_rc_id int NULL,
+ ts_log_id int NULL,
+ ts_rev_id int NULL,
+ ts_tags blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id);
+CREATE TABLE /*_*/valid_tag (
+ vt_tag varchar(255) NOT NULL PRIMARY KEY
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/l10n_cache (
+ lc_lang varbinary(32) NOT NULL,
+ lc_key varchar(255) NOT NULL,
+ lc_value mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key);
+CREATE TABLE /*_*/msg_resource (
+ mr_resource varbinary(255) NOT NULL,
+ mr_lang varbinary(32) NOT NULL,
+ mr_blob mediumblob NOT NULL,
+ mr_timestamp binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mr_resource_lang ON /*_*/msg_resource (mr_resource, mr_lang);
+CREATE TABLE /*_*/msg_resource_links (
+ mrl_resource varbinary(255) NOT NULL,
+ mrl_message varbinary(255) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mrl_message_resource ON /*_*/msg_resource_links (mrl_message, mrl_resource);
+CREATE TABLE /*_*/module_deps (
+ md_module varbinary(255) NOT NULL,
+ md_skin varbinary(32) NOT NULL,
+ md_deps mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/md_module_skin ON /*_*/module_deps (md_module, md_skin);
+-- vim: sw=2 sts=2 et \ No newline at end of file
diff --git a/www/wiki/tests/phpunit/data/db/sqlite/tables-1.21.sql b/www/wiki/tests/phpunit/data/db/sqlite/tables-1.21.sql
new file mode 100644
index 00000000..dbc84a60
--- /dev/null
+++ b/www/wiki/tests/phpunit/data/db/sqlite/tables-1.21.sql
@@ -0,0 +1,577 @@
+-- This is a copy of MediaWiki 1.21 schema shared by MySQL and SQLite.
+-- It is used for updater testing. Comments are stripped to decrease
+-- file size, as we don't need to maintain it.
+
+CREATE TABLE /*_*/user (
+ user_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ user_name varchar(255) binary NOT NULL default '',
+ user_real_name varchar(255) binary NOT NULL default '',
+ user_password tinyblob NOT NULL,
+ user_newpassword tinyblob NOT NULL,
+ user_newpass_time binary(14),
+ user_email tinytext NOT NULL,
+ user_touched binary(14) NOT NULL default '',
+ user_token binary(32) NOT NULL default '',
+ user_email_authenticated binary(14),
+ user_email_token binary(32),
+ user_email_token_expires binary(14),
+ user_registration binary(14),
+ user_editcount int
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_name ON /*_*/user (user_name);
+CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token);
+CREATE INDEX /*i*/user_email ON /*_*/user (user_email(50));
+CREATE TABLE /*_*/user_groups (
+ ug_user int unsigned NOT NULL default 0,
+ ug_group varbinary(255) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user,ug_group);
+CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
+CREATE TABLE /*_*/user_former_groups (
+ ufg_user int unsigned NOT NULL default 0,
+ ufg_group varbinary(255) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ufg_user_group ON /*_*/user_former_groups (ufg_user,ufg_group);
+CREATE TABLE /*_*/user_newtalk (
+ user_id int NOT NULL default 0,
+ user_ip varbinary(40) NOT NULL default '',
+ user_last_timestamp varbinary(14) NULL default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/un_user_id ON /*_*/user_newtalk (user_id);
+CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip);
+CREATE TABLE /*_*/user_properties (
+ up_user int NOT NULL,
+ up_property varbinary(255) NOT NULL,
+ up_value blob
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_properties_user_property ON /*_*/user_properties (up_user,up_property);
+CREATE INDEX /*i*/user_properties_property ON /*_*/user_properties (up_property);
+CREATE TABLE /*_*/page (
+ page_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ page_namespace int NOT NULL,
+ page_title varchar(255) binary NOT NULL,
+ page_restrictions tinyblob NOT NULL,
+ page_counter bigint unsigned NOT NULL default 0,
+ page_is_redirect tinyint unsigned NOT NULL default 0,
+ page_is_new tinyint unsigned NOT NULL default 0,
+ page_random real unsigned NOT NULL,
+ page_touched binary(14) NOT NULL default '',
+ page_latest int unsigned NOT NULL,
+ page_len int unsigned NOT NULL,
+ page_content_model varbinary(32) DEFAULT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
+CREATE INDEX /*i*/page_random ON /*_*/page (page_random);
+CREATE INDEX /*i*/page_len ON /*_*/page (page_len);
+CREATE INDEX /*i*/page_redirect_namespace_len ON /*_*/page (page_is_redirect, page_namespace, page_len);
+CREATE TABLE /*_*/revision (
+ rev_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ rev_page int unsigned NOT NULL,
+ rev_text_id int unsigned NOT NULL,
+ rev_comment tinyblob NOT NULL,
+ rev_user int unsigned NOT NULL default 0,
+ rev_user_text varchar(255) binary NOT NULL default '',
+ rev_timestamp binary(14) NOT NULL default '',
+ rev_minor_edit tinyint unsigned NOT NULL default 0,
+ rev_deleted tinyint unsigned NOT NULL default 0,
+ rev_len int unsigned,
+ rev_parent_id int unsigned default NULL,
+ rev_sha1 varbinary(32) NOT NULL default '',
+ rev_content_model varbinary(32) DEFAULT NULL,
+ rev_content_format varbinary(64) DEFAULT NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
+CREATE UNIQUE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
+CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp);
+CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
+CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
+CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
+CREATE INDEX /*i*/page_user_timestamp ON /*_*/revision (rev_page,rev_user,rev_timestamp);
+CREATE TABLE /*_*/text (
+ old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ old_text mediumblob NOT NULL,
+ old_flags tinyblob NOT NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=10240;
+CREATE TABLE /*_*/archive (
+ ar_namespace int NOT NULL default 0,
+ ar_title varchar(255) binary NOT NULL default '',
+ ar_text mediumblob NOT NULL,
+ ar_comment tinyblob NOT NULL,
+ ar_user int unsigned NOT NULL default 0,
+ ar_user_text varchar(255) binary NOT NULL,
+ ar_timestamp binary(14) NOT NULL default '',
+ ar_minor_edit tinyint NOT NULL default 0,
+ ar_flags tinyblob NOT NULL,
+ ar_rev_id int unsigned,
+ ar_text_id int unsigned,
+ ar_deleted tinyint unsigned NOT NULL default 0,
+ ar_len int unsigned,
+ ar_page_id int unsigned,
+ ar_parent_id int unsigned default NULL,
+ ar_sha1 varbinary(32) NOT NULL default '',
+ ar_content_model varbinary(32) DEFAULT NULL,
+ ar_content_format varbinary(64) DEFAULT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
+CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id);
+CREATE TABLE /*_*/pagelinks (
+ pl_from int unsigned NOT NULL default 0,
+ pl_namespace int NOT NULL default 0,
+ pl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title);
+CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from);
+CREATE TABLE /*_*/templatelinks (
+ tl_from int unsigned NOT NULL default 0,
+ tl_namespace int NOT NULL default 0,
+ tl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title);
+CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from);
+CREATE TABLE /*_*/imagelinks (
+ il_from int unsigned NOT NULL default 0,
+ il_to varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to);
+CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from);
+CREATE TABLE /*_*/categorylinks (
+ cl_from int unsigned NOT NULL default 0,
+ cl_to varchar(255) binary NOT NULL default '',
+ cl_sortkey varbinary(230) NOT NULL default '',
+ cl_sortkey_prefix varchar(255) binary NOT NULL default '',
+ cl_timestamp timestamp NOT NULL,
+ cl_collation varbinary(32) NOT NULL default '',
+ cl_type ENUM('page', 'subcat', 'file') NOT NULL default 'page'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to);
+CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_type,cl_sortkey,cl_from);
+CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp);
+CREATE INDEX /*i*/cl_collation ON /*_*/categorylinks (cl_collation);
+CREATE TABLE /*_*/category (
+ cat_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ cat_title varchar(255) binary NOT NULL,
+ cat_pages int signed NOT NULL default 0,
+ cat_subcats int signed NOT NULL default 0,
+ cat_files int signed NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cat_title ON /*_*/category (cat_title);
+CREATE INDEX /*i*/cat_pages ON /*_*/category (cat_pages);
+CREATE TABLE /*_*/externallinks (
+ el_from int unsigned NOT NULL default 0,
+ el_to blob NOT NULL,
+ el_index blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from, el_to(40));
+CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from);
+CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60));
+CREATE TABLE /*_*/external_user (
+ eu_local_id int unsigned NOT NULL PRIMARY KEY,
+ eu_external_id varchar(255) binary NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/eu_external_id ON /*_*/external_user (eu_external_id);
+CREATE TABLE /*_*/langlinks (
+ ll_from int unsigned NOT NULL default 0,
+ ll_lang varbinary(20) NOT NULL default '',
+ ll_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ll_from ON /*_*/langlinks (ll_from, ll_lang);
+CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title);
+CREATE TABLE /*_*/iwlinks (
+ iwl_from int unsigned NOT NULL default 0,
+ iwl_prefix varbinary(20) NOT NULL default '',
+ iwl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title);
+CREATE UNIQUE INDEX /*i*/iwl_prefix_title_from ON /*_*/iwlinks (iwl_prefix, iwl_title, iwl_from);
+CREATE TABLE /*_*/site_stats (
+ ss_row_id int unsigned NOT NULL,
+ ss_total_views bigint unsigned default 0,
+ ss_total_edits bigint unsigned default 0,
+ ss_good_articles bigint unsigned default 0,
+ ss_total_pages bigint default '-1',
+ ss_users bigint default '-1',
+ ss_active_users bigint default '-1',
+ ss_images int default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id);
+CREATE TABLE /*_*/hitcounter (
+ hc_id int unsigned NOT NULL
+) ENGINE=HEAP MAX_ROWS=25000;
+CREATE TABLE /*_*/ipblocks (
+ ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ ipb_address tinyblob NOT NULL,
+ ipb_user int unsigned NOT NULL default 0,
+ ipb_by int unsigned NOT NULL default 0,
+ ipb_by_text varchar(255) binary NOT NULL default '',
+ ipb_reason tinyblob NOT NULL,
+ ipb_timestamp binary(14) NOT NULL default '',
+ ipb_auto bool NOT NULL default 0,
+ ipb_anon_only bool NOT NULL default 0,
+ ipb_create_account bool NOT NULL default 1,
+ ipb_enable_autoblock bool NOT NULL default '1',
+ ipb_expiry varbinary(14) NOT NULL default '',
+ ipb_range_start tinyblob NOT NULL,
+ ipb_range_end tinyblob NOT NULL,
+ ipb_deleted bool NOT NULL default 0,
+ ipb_block_email bool NOT NULL default 0,
+ ipb_allow_usertalk bool NOT NULL default 0,
+ ipb_parent_block_id int default NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
+CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
+CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
+CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
+CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
+CREATE INDEX /*i*/ipb_parent_block_id ON /*_*/ipblocks (ipb_parent_block_id);
+CREATE TABLE /*_*/image (
+ img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
+ img_size int unsigned NOT NULL default 0,
+ img_width int NOT NULL default 0,
+ img_height int NOT NULL default 0,
+ img_metadata mediumblob NOT NULL,
+ img_bits int NOT NULL default 0,
+ img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+ img_minor_mime varbinary(100) NOT NULL default "unknown",
+ img_description tinyblob NOT NULL,
+ img_user int unsigned NOT NULL default 0,
+ img_user_text varchar(255) binary NOT NULL,
+ img_timestamp varbinary(14) NOT NULL default '',
+ img_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
+CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
+CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
+CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1(10));
+CREATE INDEX /*i*/img_media_mime ON /*_*/image (img_media_type,img_major_mime,img_minor_mime);
+CREATE TABLE /*_*/oldimage (
+ oi_name varchar(255) binary NOT NULL default '',
+ oi_archive_name varchar(255) binary NOT NULL default '',
+ oi_size int unsigned NOT NULL default 0,
+ oi_width int NOT NULL default 0,
+ oi_height int NOT NULL default 0,
+ oi_bits int NOT NULL default 0,
+ oi_description tinyblob NOT NULL,
+ oi_user int unsigned NOT NULL default 0,
+ oi_user_text varchar(255) binary NOT NULL,
+ oi_timestamp binary(14) NOT NULL default '',
+ oi_metadata mediumblob NOT NULL,
+ oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+ oi_minor_mime varbinary(100) NOT NULL default "unknown",
+ oi_deleted tinyint unsigned NOT NULL default 0,
+ oi_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
+CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
+CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
+CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1(10));
+CREATE TABLE /*_*/filearchive (
+ fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ fa_name varchar(255) binary NOT NULL default '',
+ fa_archive_name varchar(255) binary default '',
+ fa_storage_group varbinary(16),
+ fa_storage_key varbinary(64) default '',
+ fa_deleted_user int,
+ fa_deleted_timestamp binary(14) default '',
+ fa_deleted_reason text,
+ fa_size int unsigned default 0,
+ fa_width int default 0,
+ fa_height int default 0,
+ fa_metadata mediumblob,
+ fa_bits int default 0,
+ fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") default "unknown",
+ fa_minor_mime varbinary(100) default "unknown",
+ fa_description tinyblob,
+ fa_user int unsigned default 0,
+ fa_user_text varchar(255) binary,
+ fa_timestamp binary(14) default '',
+ fa_deleted tinyint unsigned NOT NULL default 0,
+ fa_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
+CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
+CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
+CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
+CREATE INDEX /*i*/fa_sha1 ON /*_*/filearchive (fa_sha1(10));
+CREATE TABLE /*_*/uploadstash (
+ us_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ us_user int unsigned NOT NULL,
+ us_key varchar(255) NOT NULL,
+ us_orig_path varchar(255) NOT NULL,
+ us_path varchar(255) NOT NULL,
+ us_source_type varchar(50),
+ us_timestamp varbinary(14) NOT NULL,
+ us_status varchar(50) NOT NULL,
+ us_chunk_inx int unsigned NULL,
+ us_props blob,
+ us_size int unsigned NOT NULL,
+ us_sha1 varchar(31) NOT NULL,
+ us_mime varchar(255),
+ us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ us_image_width int unsigned,
+ us_image_height int unsigned,
+ us_image_bits smallint unsigned
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/us_user ON /*_*/uploadstash (us_user);
+CREATE UNIQUE INDEX /*i*/us_key ON /*_*/uploadstash (us_key);
+CREATE INDEX /*i*/us_timestamp ON /*_*/uploadstash (us_timestamp);
+CREATE TABLE /*_*/recentchanges (
+ rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ rc_timestamp varbinary(14) NOT NULL default '',
+ rc_cur_time varbinary(14) NOT NULL default '',
+ rc_user int unsigned NOT NULL default 0,
+ rc_user_text varchar(255) binary NOT NULL,
+ rc_namespace int NOT NULL default 0,
+ rc_title varchar(255) binary NOT NULL default '',
+ rc_comment varchar(255) binary NOT NULL default '',
+ rc_minor tinyint unsigned NOT NULL default 0,
+ rc_bot tinyint unsigned NOT NULL default 0,
+ rc_new tinyint unsigned NOT NULL default 0,
+ rc_cur_id int unsigned NOT NULL default 0,
+ rc_this_oldid int unsigned NOT NULL default 0,
+ rc_last_oldid int unsigned NOT NULL default 0,
+ rc_type tinyint unsigned NOT NULL default 0,
+ rc_patrolled tinyint unsigned NOT NULL default 0,
+ rc_ip varbinary(40) NOT NULL default '',
+ rc_old_len int,
+ rc_new_len int,
+ rc_deleted tinyint unsigned NOT NULL default 0,
+ rc_logid int unsigned NOT NULL default 0,
+ rc_log_type varbinary(255) NULL default NULL,
+ rc_log_action varbinary(255) NULL default NULL,
+ rc_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp);
+CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title);
+CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id);
+CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp);
+CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
+CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
+CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
+CREATE TABLE /*_*/watchlist (
+ wl_user int unsigned NOT NULL,
+ wl_namespace int NOT NULL default 0,
+ wl_title varchar(255) binary NOT NULL default '',
+ wl_notificationtimestamp varbinary(14)
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title);
+CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title);
+CREATE TABLE /*_*/searchindex (
+ si_page int unsigned NOT NULL,
+ si_title varchar(255) NOT NULL default '',
+ si_text mediumtext NOT NULL
+) ENGINE=MyISAM;
+CREATE UNIQUE INDEX /*i*/si_page ON /*_*/searchindex (si_page);
+CREATE FULLTEXT INDEX /*i*/si_title ON /*_*/searchindex (si_title);
+CREATE FULLTEXT INDEX /*i*/si_text ON /*_*/searchindex (si_text);
+CREATE TABLE /*_*/interwiki (
+ iw_prefix varchar(32) NOT NULL,
+ iw_url blob NOT NULL,
+ iw_api blob NOT NULL,
+ iw_wikiid varchar(64) NOT NULL,
+ iw_local bool NOT NULL,
+ iw_trans tinyint NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix);
+CREATE TABLE /*_*/querycache (
+ qc_type varbinary(32) NOT NULL,
+ qc_value int unsigned NOT NULL default 0,
+ qc_namespace int NOT NULL default 0,
+ qc_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qc_type ON /*_*/querycache (qc_type,qc_value);
+CREATE TABLE /*_*/objectcache (
+ keyname varbinary(255) NOT NULL default '' PRIMARY KEY,
+ value mediumblob,
+ exptime datetime
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime);
+CREATE TABLE /*_*/transcache (
+ tc_url varbinary(255) NOT NULL,
+ tc_contents text,
+ tc_time binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url);
+CREATE TABLE /*_*/logging (
+ log_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ log_type varbinary(32) NOT NULL default '',
+ log_action varbinary(32) NOT NULL default '',
+ log_timestamp binary(14) NOT NULL default '19700101000000',
+ log_user int unsigned NOT NULL default 0,
+ log_user_text varchar(255) binary NOT NULL default '',
+ log_namespace int NOT NULL default 0,
+ log_title varchar(255) binary NOT NULL default '',
+ log_page int unsigned NULL,
+ log_comment varchar(255) NOT NULL default '',
+ log_params blob NOT NULL,
+ log_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
+CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
+CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
+CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
+CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
+CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
+CREATE INDEX /*i*/type_action ON /*_*/logging (log_type, log_action, log_timestamp);
+CREATE TABLE /*_*/log_search (
+ ls_field varbinary(32) NOT NULL,
+ ls_value varchar(255) NOT NULL,
+ ls_log_id int unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ls_field_val ON /*_*/log_search (ls_field,ls_value,ls_log_id);
+CREATE INDEX /*i*/ls_log_id ON /*_*/log_search (ls_log_id);
+CREATE TABLE /*_*/job (
+ job_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ job_cmd varbinary(60) NOT NULL default '',
+ job_namespace int NOT NULL,
+ job_title varchar(255) binary NOT NULL,
+ job_timestamp varbinary(14) NULL default NULL,
+ job_params blob NOT NULL,
+ job_random integer unsigned NOT NULL default 0,
+ job_attempts integer unsigned NOT NULL default 0,
+ job_token varbinary(32) NOT NULL default '',
+ job_token_timestamp varbinary(14) NULL default NULL,
+ job_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/job_sha1 ON /*_*/job (job_sha1);
+CREATE INDEX /*i*/job_cmd_token ON /*_*/job (job_cmd,job_token,job_random);
+CREATE INDEX /*i*/job_cmd_token_id ON /*_*/job (job_cmd,job_token,job_id);
+CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
+CREATE INDEX /*i*/job_timestamp ON /*_*/job (job_timestamp);
+CREATE TABLE /*_*/querycache_info (
+ qci_type varbinary(32) NOT NULL default '',
+ qci_timestamp binary(14) NOT NULL default '19700101000000'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/qci_type ON /*_*/querycache_info (qci_type);
+CREATE TABLE /*_*/redirect (
+ rd_from int unsigned NOT NULL default 0 PRIMARY KEY,
+ rd_namespace int NOT NULL default 0,
+ rd_title varchar(255) binary NOT NULL default '',
+ rd_interwiki varchar(32) default NULL,
+ rd_fragment varchar(255) binary default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from);
+CREATE TABLE /*_*/querycachetwo (
+ qcc_type varbinary(32) NOT NULL,
+ qcc_value int unsigned NOT NULL default 0,
+ qcc_namespace int NOT NULL default 0,
+ qcc_title varchar(255) binary NOT NULL default '',
+ qcc_namespacetwo int NOT NULL default 0,
+ qcc_titletwo varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qcc_type ON /*_*/querycachetwo (qcc_type,qcc_value);
+CREATE INDEX /*i*/qcc_title ON /*_*/querycachetwo (qcc_type,qcc_namespace,qcc_title);
+CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
+CREATE TABLE /*_*/page_restrictions (
+ pr_page int NOT NULL,
+ pr_type varbinary(60) NOT NULL,
+ pr_level varbinary(60) NOT NULL,
+ pr_cascade tinyint NOT NULL,
+ pr_user int NULL,
+ pr_expiry varbinary(14) NULL,
+ pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions (pr_page,pr_type);
+CREATE INDEX /*i*/pr_typelevel ON /*_*/page_restrictions (pr_type,pr_level);
+CREATE INDEX /*i*/pr_level ON /*_*/page_restrictions (pr_level);
+CREATE INDEX /*i*/pr_cascade ON /*_*/page_restrictions (pr_cascade);
+CREATE TABLE /*_*/protected_titles (
+ pt_namespace int NOT NULL,
+ pt_title varchar(255) binary NOT NULL,
+ pt_user int unsigned NOT NULL,
+ pt_reason tinyblob,
+ pt_timestamp binary(14) NOT NULL,
+ pt_expiry varbinary(14) NOT NULL default '',
+ pt_create_perm varbinary(60) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pt_namespace_title ON /*_*/protected_titles (pt_namespace,pt_title);
+CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp);
+CREATE TABLE /*_*/page_props (
+ pp_page int NOT NULL,
+ pp_propname varbinary(60) NOT NULL,
+ pp_value blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname);
+CREATE UNIQUE INDEX /*i*/pp_propname_page ON /*_*/page_props (pp_propname,pp_page);
+CREATE TABLE /*_*/updatelog (
+ ul_key varchar(255) NOT NULL PRIMARY KEY,
+ ul_value blob
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/change_tag (
+ ct_rc_id int NULL,
+ ct_log_id int NULL,
+ ct_rev_id int NULL,
+ ct_tag varchar(255) NOT NULL,
+ ct_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/change_tag_rc_tag ON /*_*/change_tag (ct_rc_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_log_tag ON /*_*/change_tag (ct_log_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag);
+CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
+CREATE TABLE /*_*/tag_summary (
+ ts_rc_id int NULL,
+ ts_log_id int NULL,
+ ts_rev_id int NULL,
+ ts_tags blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id);
+CREATE TABLE /*_*/valid_tag (
+ vt_tag varchar(255) NOT NULL PRIMARY KEY
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/l10n_cache (
+ lc_lang varbinary(32) NOT NULL,
+ lc_key varchar(255) NOT NULL,
+ lc_value mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key);
+CREATE TABLE /*_*/msg_resource (
+ mr_resource varbinary(255) NOT NULL,
+ mr_lang varbinary(32) NOT NULL,
+ mr_blob mediumblob NOT NULL,
+ mr_timestamp binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mr_resource_lang ON /*_*/msg_resource (mr_resource, mr_lang);
+CREATE TABLE /*_*/msg_resource_links (
+ mrl_resource varbinary(255) NOT NULL,
+ mrl_message varbinary(255) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mrl_message_resource ON /*_*/msg_resource_links (mrl_message, mrl_resource);
+CREATE TABLE /*_*/module_deps (
+ md_module varbinary(255) NOT NULL,
+ md_skin varbinary(32) NOT NULL,
+ md_deps mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/md_module_skin ON /*_*/module_deps (md_module, md_skin);
+CREATE TABLE /*_*/sites (
+ site_id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ site_global_key varbinary(32) NOT NULL,
+ site_type varbinary(32) NOT NULL,
+ site_group varbinary(32) NOT NULL,
+ site_source varbinary(32) NOT NULL,
+ site_language varbinary(32) NOT NULL,
+ site_protocol varbinary(32) NOT NULL,
+ site_domain VARCHAR(255) NOT NULL,
+ site_data BLOB NOT NULL,
+ site_forward bool NOT NULL,
+ site_config BLOB NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/sites_global_key ON /*_*/sites (site_global_key);
+CREATE INDEX /*i*/sites_type ON /*_*/sites (site_type);
+CREATE INDEX /*i*/sites_group ON /*_*/sites (site_group);
+CREATE INDEX /*i*/sites_source ON /*_*/sites (site_source);
+CREATE INDEX /*i*/sites_language ON /*_*/sites (site_language);
+CREATE INDEX /*i*/sites_protocol ON /*_*/sites (site_protocol);
+CREATE INDEX /*i*/sites_domain ON /*_*/sites (site_domain);
+CREATE INDEX /*i*/sites_forward ON /*_*/sites (site_forward);
+CREATE TABLE /*_*/site_identifiers (
+ si_site INT UNSIGNED NOT NULL,
+ si_type varbinary(32) NOT NULL,
+ si_key varbinary(32) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/site_ids_type ON /*_*/site_identifiers (si_type, si_key);
+CREATE INDEX /*i*/site_ids_site ON /*_*/site_identifiers (si_site);
+CREATE INDEX /*i*/site_ids_key ON /*_*/site_identifiers (si_key);
diff --git a/www/wiki/tests/phpunit/data/db/sqlite/tables-1.22.sql b/www/wiki/tests/phpunit/data/db/sqlite/tables-1.22.sql
new file mode 100644
index 00000000..74c5bd5d
--- /dev/null
+++ b/www/wiki/tests/phpunit/data/db/sqlite/tables-1.22.sql
@@ -0,0 +1,575 @@
+-- This is a copy of MediaWiki 1.22 schema shared by MySQL and SQLite.
+-- It is used for updater testing. Comments are stripped to decrease
+-- file size, as we don't need to maintain it.
+
+CREATE TABLE /*_*/user (
+ user_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ user_name varchar(255) binary NOT NULL default '',
+ user_real_name varchar(255) binary NOT NULL default '',
+ user_password tinyblob NOT NULL,
+ user_newpassword tinyblob NOT NULL,
+ user_newpass_time binary(14),
+ user_email tinytext NOT NULL,
+ user_touched binary(14) NOT NULL default '',
+ user_token binary(32) NOT NULL default '',
+ user_email_authenticated binary(14),
+ user_email_token binary(32),
+ user_email_token_expires binary(14),
+ user_registration binary(14),
+ user_editcount int
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_name ON /*_*/user (user_name);
+CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token);
+CREATE INDEX /*i*/user_email ON /*_*/user (user_email(50));
+CREATE TABLE /*_*/user_groups (
+ ug_user int unsigned NOT NULL default 0,
+ ug_group varbinary(255) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user,ug_group);
+CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
+CREATE TABLE /*_*/user_former_groups (
+ ufg_user int unsigned NOT NULL default 0,
+ ufg_group varbinary(255) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ufg_user_group ON /*_*/user_former_groups (ufg_user,ufg_group);
+CREATE TABLE /*_*/user_newtalk (
+ user_id int NOT NULL default 0,
+ user_ip varbinary(40) NOT NULL default '',
+ user_last_timestamp varbinary(14) NULL default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/un_user_id ON /*_*/user_newtalk (user_id);
+CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip);
+CREATE TABLE /*_*/user_properties (
+ up_user int NOT NULL,
+ up_property varbinary(255) NOT NULL,
+ up_value blob
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_properties_user_property ON /*_*/user_properties (up_user,up_property);
+CREATE INDEX /*i*/user_properties_property ON /*_*/user_properties (up_property);
+CREATE TABLE /*_*/page (
+ page_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ page_namespace int NOT NULL,
+ page_title varchar(255) binary NOT NULL,
+ page_restrictions tinyblob NOT NULL,
+ page_counter bigint unsigned NOT NULL default 0,
+ page_is_redirect tinyint unsigned NOT NULL default 0,
+ page_is_new tinyint unsigned NOT NULL default 0,
+ page_random real unsigned NOT NULL,
+ page_touched binary(14) NOT NULL default '',
+ page_latest int unsigned NOT NULL,
+ page_len int unsigned NOT NULL,
+ page_content_model varbinary(32) DEFAULT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
+CREATE INDEX /*i*/page_random ON /*_*/page (page_random);
+CREATE INDEX /*i*/page_len ON /*_*/page (page_len);
+CREATE INDEX /*i*/page_redirect_namespace_len ON /*_*/page (page_is_redirect, page_namespace, page_len);
+CREATE TABLE /*_*/revision (
+ rev_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ rev_page int unsigned NOT NULL,
+ rev_text_id int unsigned NOT NULL,
+ rev_comment tinyblob NOT NULL,
+ rev_user int unsigned NOT NULL default 0,
+ rev_user_text varchar(255) binary NOT NULL default '',
+ rev_timestamp binary(14) NOT NULL default '',
+ rev_minor_edit tinyint unsigned NOT NULL default 0,
+ rev_deleted tinyint unsigned NOT NULL default 0,
+ rev_len int unsigned,
+ rev_parent_id int unsigned default NULL,
+ rev_sha1 varbinary(32) NOT NULL default '',
+ rev_content_model varbinary(32) DEFAULT NULL,
+ rev_content_format varbinary(64) DEFAULT NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
+CREATE UNIQUE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
+CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp);
+CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
+CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
+CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
+CREATE INDEX /*i*/page_user_timestamp ON /*_*/revision (rev_page,rev_user,rev_timestamp);
+CREATE TABLE /*_*/text (
+ old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ old_text mediumblob NOT NULL,
+ old_flags tinyblob NOT NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=10240;
+CREATE TABLE /*_*/archive (
+ ar_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ ar_namespace int NOT NULL default 0,
+ ar_title varchar(255) binary NOT NULL default '',
+ ar_text mediumblob NOT NULL,
+ ar_comment tinyblob NOT NULL,
+ ar_user int unsigned NOT NULL default 0,
+ ar_user_text varchar(255) binary NOT NULL,
+ ar_timestamp binary(14) NOT NULL default '',
+ ar_minor_edit tinyint NOT NULL default 0,
+ ar_flags tinyblob NOT NULL,
+ ar_rev_id int unsigned,
+ ar_text_id int unsigned,
+ ar_deleted tinyint unsigned NOT NULL default 0,
+ ar_len int unsigned,
+ ar_page_id int unsigned,
+ ar_parent_id int unsigned default NULL,
+ ar_sha1 varbinary(32) NOT NULL default '',
+ ar_content_model varbinary(32) DEFAULT NULL,
+ ar_content_format varbinary(64) DEFAULT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
+CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id);
+CREATE TABLE /*_*/pagelinks (
+ pl_from int unsigned NOT NULL default 0,
+ pl_namespace int NOT NULL default 0,
+ pl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title);
+CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from);
+CREATE TABLE /*_*/templatelinks (
+ tl_from int unsigned NOT NULL default 0,
+ tl_namespace int NOT NULL default 0,
+ tl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title);
+CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from);
+CREATE TABLE /*_*/imagelinks (
+ il_from int unsigned NOT NULL default 0,
+ il_to varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to);
+CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from);
+CREATE TABLE /*_*/categorylinks (
+ cl_from int unsigned NOT NULL default 0,
+ cl_to varchar(255) binary NOT NULL default '',
+ cl_sortkey varbinary(230) NOT NULL default '',
+ cl_sortkey_prefix varchar(255) binary NOT NULL default '',
+ cl_timestamp timestamp NOT NULL,
+ cl_collation varbinary(32) NOT NULL default '',
+ cl_type ENUM('page', 'subcat', 'file') NOT NULL default 'page'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to);
+CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_type,cl_sortkey,cl_from);
+CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp);
+CREATE INDEX /*i*/cl_collation ON /*_*/categorylinks (cl_collation);
+CREATE TABLE /*_*/category (
+ cat_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ cat_title varchar(255) binary NOT NULL,
+ cat_pages int signed NOT NULL default 0,
+ cat_subcats int signed NOT NULL default 0,
+ cat_files int signed NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cat_title ON /*_*/category (cat_title);
+CREATE INDEX /*i*/cat_pages ON /*_*/category (cat_pages);
+CREATE TABLE /*_*/externallinks (
+ el_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ el_from int unsigned NOT NULL default 0,
+ el_to blob NOT NULL,
+ el_index blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from, el_to(40));
+CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from);
+CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60));
+CREATE TABLE /*_*/langlinks (
+ ll_from int unsigned NOT NULL default 0,
+ ll_lang varbinary(20) NOT NULL default '',
+ ll_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ll_from ON /*_*/langlinks (ll_from, ll_lang);
+CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title);
+CREATE TABLE /*_*/iwlinks (
+ iwl_from int unsigned NOT NULL default 0,
+ iwl_prefix varbinary(20) NOT NULL default '',
+ iwl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title);
+CREATE INDEX /*i*/iwl_prefix_title_from ON /*_*/iwlinks (iwl_prefix, iwl_title, iwl_from);
+CREATE INDEX /*i*/iwl_prefix_from_title ON /*_*/iwlinks (iwl_prefix, iwl_from, iwl_title);
+CREATE TABLE /*_*/site_stats (
+ ss_row_id int unsigned NOT NULL,
+ ss_total_views bigint unsigned default 0,
+ ss_total_edits bigint unsigned default 0,
+ ss_good_articles bigint unsigned default 0,
+ ss_total_pages bigint default '-1',
+ ss_users bigint default '-1',
+ ss_active_users bigint default '-1',
+ ss_images int default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id);
+CREATE TABLE /*_*/hitcounter (
+ hc_id int unsigned NOT NULL
+) ENGINE=HEAP MAX_ROWS=25000;
+CREATE TABLE /*_*/ipblocks (
+ ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ ipb_address tinyblob NOT NULL,
+ ipb_user int unsigned NOT NULL default 0,
+ ipb_by int unsigned NOT NULL default 0,
+ ipb_by_text varchar(255) binary NOT NULL default '',
+ ipb_reason tinyblob NOT NULL,
+ ipb_timestamp binary(14) NOT NULL default '',
+ ipb_auto bool NOT NULL default 0,
+ ipb_anon_only bool NOT NULL default 0,
+ ipb_create_account bool NOT NULL default 1,
+ ipb_enable_autoblock bool NOT NULL default '1',
+ ipb_expiry varbinary(14) NOT NULL default '',
+ ipb_range_start tinyblob NOT NULL,
+ ipb_range_end tinyblob NOT NULL,
+ ipb_deleted bool NOT NULL default 0,
+ ipb_block_email bool NOT NULL default 0,
+ ipb_allow_usertalk bool NOT NULL default 0,
+ ipb_parent_block_id int default NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
+CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
+CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
+CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
+CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
+CREATE INDEX /*i*/ipb_parent_block_id ON /*_*/ipblocks (ipb_parent_block_id);
+CREATE TABLE /*_*/image (
+ img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
+ img_size int unsigned NOT NULL default 0,
+ img_width int NOT NULL default 0,
+ img_height int NOT NULL default 0,
+ img_metadata mediumblob NOT NULL,
+ img_bits int NOT NULL default 0,
+ img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+ img_minor_mime varbinary(100) NOT NULL default "unknown",
+ img_description tinyblob NOT NULL,
+ img_user int unsigned NOT NULL default 0,
+ img_user_text varchar(255) binary NOT NULL,
+ img_timestamp varbinary(14) NOT NULL default '',
+ img_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
+CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
+CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
+CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1(10));
+CREATE INDEX /*i*/img_media_mime ON /*_*/image (img_media_type,img_major_mime,img_minor_mime);
+CREATE TABLE /*_*/oldimage (
+ oi_name varchar(255) binary NOT NULL default '',
+ oi_archive_name varchar(255) binary NOT NULL default '',
+ oi_size int unsigned NOT NULL default 0,
+ oi_width int NOT NULL default 0,
+ oi_height int NOT NULL default 0,
+ oi_bits int NOT NULL default 0,
+ oi_description tinyblob NOT NULL,
+ oi_user int unsigned NOT NULL default 0,
+ oi_user_text varchar(255) binary NOT NULL,
+ oi_timestamp binary(14) NOT NULL default '',
+ oi_metadata mediumblob NOT NULL,
+ oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+ oi_minor_mime varbinary(100) NOT NULL default "unknown",
+ oi_deleted tinyint unsigned NOT NULL default 0,
+ oi_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
+CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
+CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
+CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1(10));
+CREATE TABLE /*_*/filearchive (
+ fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ fa_name varchar(255) binary NOT NULL default '',
+ fa_archive_name varchar(255) binary default '',
+ fa_storage_group varbinary(16),
+ fa_storage_key varbinary(64) default '',
+ fa_deleted_user int,
+ fa_deleted_timestamp binary(14) default '',
+ fa_deleted_reason text,
+ fa_size int unsigned default 0,
+ fa_width int default 0,
+ fa_height int default 0,
+ fa_metadata mediumblob,
+ fa_bits int default 0,
+ fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") default "unknown",
+ fa_minor_mime varbinary(100) default "unknown",
+ fa_description tinyblob,
+ fa_user int unsigned default 0,
+ fa_user_text varchar(255) binary,
+ fa_timestamp binary(14) default '',
+ fa_deleted tinyint unsigned NOT NULL default 0,
+ fa_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
+CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
+CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
+CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
+CREATE INDEX /*i*/fa_sha1 ON /*_*/filearchive (fa_sha1(10));
+CREATE TABLE /*_*/uploadstash (
+ us_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ us_user int unsigned NOT NULL,
+ us_key varchar(255) NOT NULL,
+ us_orig_path varchar(255) NOT NULL,
+ us_path varchar(255) NOT NULL,
+ us_source_type varchar(50),
+ us_timestamp varbinary(14) NOT NULL,
+ us_status varchar(50) NOT NULL,
+ us_chunk_inx int unsigned NULL,
+ us_props blob,
+ us_size int unsigned NOT NULL,
+ us_sha1 varchar(31) NOT NULL,
+ us_mime varchar(255),
+ us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ us_image_width int unsigned,
+ us_image_height int unsigned,
+ us_image_bits smallint unsigned
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/us_user ON /*_*/uploadstash (us_user);
+CREATE UNIQUE INDEX /*i*/us_key ON /*_*/uploadstash (us_key);
+CREATE INDEX /*i*/us_timestamp ON /*_*/uploadstash (us_timestamp);
+CREATE TABLE /*_*/recentchanges (
+ rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ rc_timestamp varbinary(14) NOT NULL default '',
+ rc_cur_time varbinary(14) NOT NULL default '',
+ rc_user int unsigned NOT NULL default 0,
+ rc_user_text varchar(255) binary NOT NULL,
+ rc_namespace int NOT NULL default 0,
+ rc_title varchar(255) binary NOT NULL default '',
+ rc_comment varchar(255) binary NOT NULL default '',
+ rc_minor tinyint unsigned NOT NULL default 0,
+ rc_bot tinyint unsigned NOT NULL default 0,
+ rc_new tinyint unsigned NOT NULL default 0,
+ rc_cur_id int unsigned NOT NULL default 0,
+ rc_this_oldid int unsigned NOT NULL default 0,
+ rc_last_oldid int unsigned NOT NULL default 0,
+ rc_type tinyint unsigned NOT NULL default 0,
+ rc_patrolled tinyint unsigned NOT NULL default 0,
+ rc_ip varbinary(40) NOT NULL default '',
+ rc_old_len int,
+ rc_new_len int,
+ rc_deleted tinyint unsigned NOT NULL default 0,
+ rc_logid int unsigned NOT NULL default 0,
+ rc_log_type varbinary(255) NULL default NULL,
+ rc_log_action varbinary(255) NULL default NULL,
+ rc_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp);
+CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title);
+CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id);
+CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp);
+CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
+CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
+CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
+CREATE TABLE /*_*/watchlist (
+ wl_user int unsigned NOT NULL,
+ wl_namespace int NOT NULL default 0,
+ wl_title varchar(255) binary NOT NULL default '',
+ wl_notificationtimestamp varbinary(14)
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title);
+CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title);
+CREATE TABLE /*_*/searchindex (
+ si_page int unsigned NOT NULL,
+ si_title varchar(255) NOT NULL default '',
+ si_text mediumtext NOT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+CREATE UNIQUE INDEX /*i*/si_page ON /*_*/searchindex (si_page);
+CREATE FULLTEXT INDEX /*i*/si_title ON /*_*/searchindex (si_title);
+CREATE FULLTEXT INDEX /*i*/si_text ON /*_*/searchindex (si_text);
+CREATE TABLE /*_*/interwiki (
+ iw_prefix varchar(32) NOT NULL,
+ iw_url blob NOT NULL,
+ iw_api blob NOT NULL,
+ iw_wikiid varchar(64) NOT NULL,
+ iw_local bool NOT NULL,
+ iw_trans tinyint NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix);
+CREATE TABLE /*_*/querycache (
+ qc_type varbinary(32) NOT NULL,
+ qc_value int unsigned NOT NULL default 0,
+ qc_namespace int NOT NULL default 0,
+ qc_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qc_type ON /*_*/querycache (qc_type,qc_value);
+CREATE TABLE /*_*/objectcache (
+ keyname varbinary(255) NOT NULL default '' PRIMARY KEY,
+ value mediumblob,
+ exptime datetime
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime);
+CREATE TABLE /*_*/transcache (
+ tc_url varbinary(255) NOT NULL,
+ tc_contents text,
+ tc_time binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url);
+CREATE TABLE /*_*/logging (
+ log_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ log_type varbinary(32) NOT NULL default '',
+ log_action varbinary(32) NOT NULL default '',
+ log_timestamp binary(14) NOT NULL default '19700101000000',
+ log_user int unsigned NOT NULL default 0,
+ log_user_text varchar(255) binary NOT NULL default '',
+ log_namespace int NOT NULL default 0,
+ log_title varchar(255) binary NOT NULL default '',
+ log_page int unsigned NULL,
+ log_comment varchar(255) NOT NULL default '',
+ log_params blob NOT NULL,
+ log_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
+CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
+CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
+CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
+CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
+CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
+CREATE INDEX /*i*/type_action ON /*_*/logging (log_type, log_action, log_timestamp);
+CREATE TABLE /*_*/log_search (
+ ls_field varbinary(32) NOT NULL,
+ ls_value varchar(255) NOT NULL,
+ ls_log_id int unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ls_field_val ON /*_*/log_search (ls_field,ls_value,ls_log_id);
+CREATE INDEX /*i*/ls_log_id ON /*_*/log_search (ls_log_id);
+CREATE TABLE /*_*/job (
+ job_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ job_cmd varbinary(60) NOT NULL default '',
+ job_namespace int NOT NULL,
+ job_title varchar(255) binary NOT NULL,
+ job_timestamp varbinary(14) NULL default NULL,
+ job_params blob NOT NULL,
+ job_random integer unsigned NOT NULL default 0,
+ job_attempts integer unsigned NOT NULL default 0,
+ job_token varbinary(32) NOT NULL default '',
+ job_token_timestamp varbinary(14) NULL default NULL,
+ job_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/job_sha1 ON /*_*/job (job_sha1);
+CREATE INDEX /*i*/job_cmd_token ON /*_*/job (job_cmd,job_token,job_random);
+CREATE INDEX /*i*/job_cmd_token_id ON /*_*/job (job_cmd,job_token,job_id);
+CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
+CREATE INDEX /*i*/job_timestamp ON /*_*/job (job_timestamp);
+CREATE TABLE /*_*/querycache_info (
+ qci_type varbinary(32) NOT NULL default '',
+ qci_timestamp binary(14) NOT NULL default '19700101000000'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/qci_type ON /*_*/querycache_info (qci_type);
+CREATE TABLE /*_*/redirect (
+ rd_from int unsigned NOT NULL default 0 PRIMARY KEY,
+ rd_namespace int NOT NULL default 0,
+ rd_title varchar(255) binary NOT NULL default '',
+ rd_interwiki varchar(32) default NULL,
+ rd_fragment varchar(255) binary default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from);
+CREATE TABLE /*_*/querycachetwo (
+ qcc_type varbinary(32) NOT NULL,
+ qcc_value int unsigned NOT NULL default 0,
+ qcc_namespace int NOT NULL default 0,
+ qcc_title varchar(255) binary NOT NULL default '',
+ qcc_namespacetwo int NOT NULL default 0,
+ qcc_titletwo varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qcc_type ON /*_*/querycachetwo (qcc_type,qcc_value);
+CREATE INDEX /*i*/qcc_title ON /*_*/querycachetwo (qcc_type,qcc_namespace,qcc_title);
+CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
+CREATE TABLE /*_*/page_restrictions (
+ pr_page int NOT NULL,
+ pr_type varbinary(60) NOT NULL,
+ pr_level varbinary(60) NOT NULL,
+ pr_cascade tinyint NOT NULL,
+ pr_user int NULL,
+ pr_expiry varbinary(14) NULL,
+ pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions (pr_page,pr_type);
+CREATE INDEX /*i*/pr_typelevel ON /*_*/page_restrictions (pr_type,pr_level);
+CREATE INDEX /*i*/pr_level ON /*_*/page_restrictions (pr_level);
+CREATE INDEX /*i*/pr_cascade ON /*_*/page_restrictions (pr_cascade);
+CREATE TABLE /*_*/protected_titles (
+ pt_namespace int NOT NULL,
+ pt_title varchar(255) binary NOT NULL,
+ pt_user int unsigned NOT NULL,
+ pt_reason tinyblob,
+ pt_timestamp binary(14) NOT NULL,
+ pt_expiry varbinary(14) NOT NULL default '',
+ pt_create_perm varbinary(60) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pt_namespace_title ON /*_*/protected_titles (pt_namespace,pt_title);
+CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp);
+CREATE TABLE /*_*/page_props (
+ pp_page int NOT NULL,
+ pp_propname varbinary(60) NOT NULL,
+ pp_value blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname);
+CREATE UNIQUE INDEX /*i*/pp_propname_page ON /*_*/page_props (pp_propname,pp_page);
+CREATE TABLE /*_*/updatelog (
+ ul_key varchar(255) NOT NULL PRIMARY KEY,
+ ul_value blob
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/change_tag (
+ ct_rc_id int NULL,
+ ct_log_id int NULL,
+ ct_rev_id int NULL,
+ ct_tag varchar(255) NOT NULL,
+ ct_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/change_tag_rc_tag ON /*_*/change_tag (ct_rc_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_log_tag ON /*_*/change_tag (ct_log_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag);
+CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
+CREATE TABLE /*_*/tag_summary (
+ ts_rc_id int NULL,
+ ts_log_id int NULL,
+ ts_rev_id int NULL,
+ ts_tags blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id);
+CREATE TABLE /*_*/valid_tag (
+ vt_tag varchar(255) NOT NULL PRIMARY KEY
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/l10n_cache (
+ lc_lang varbinary(32) NOT NULL,
+ lc_key varchar(255) NOT NULL,
+ lc_value mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key);
+CREATE TABLE /*_*/msg_resource (
+ mr_resource varbinary(255) NOT NULL,
+ mr_lang varbinary(32) NOT NULL,
+ mr_blob mediumblob NOT NULL,
+ mr_timestamp binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mr_resource_lang ON /*_*/msg_resource (mr_resource, mr_lang);
+CREATE TABLE /*_*/msg_resource_links (
+ mrl_resource varbinary(255) NOT NULL,
+ mrl_message varbinary(255) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mrl_message_resource ON /*_*/msg_resource_links (mrl_message, mrl_resource);
+CREATE TABLE /*_*/module_deps (
+ md_module varbinary(255) NOT NULL,
+ md_skin varbinary(32) NOT NULL,
+ md_deps mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/md_module_skin ON /*_*/module_deps (md_module, md_skin);
+CREATE TABLE /*_*/sites (
+ site_id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ site_global_key varbinary(32) NOT NULL,
+ site_type varbinary(32) NOT NULL,
+ site_group varbinary(32) NOT NULL,
+ site_source varbinary(32) NOT NULL,
+ site_language varbinary(32) NOT NULL,
+ site_protocol varbinary(32) NOT NULL,
+ site_domain VARCHAR(255) NOT NULL,
+ site_data BLOB NOT NULL,
+ site_forward bool NOT NULL,
+ site_config BLOB NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/sites_global_key ON /*_*/sites (site_global_key);
+CREATE INDEX /*i*/sites_type ON /*_*/sites (site_type);
+CREATE INDEX /*i*/sites_group ON /*_*/sites (site_group);
+CREATE INDEX /*i*/sites_source ON /*_*/sites (site_source);
+CREATE INDEX /*i*/sites_language ON /*_*/sites (site_language);
+CREATE INDEX /*i*/sites_protocol ON /*_*/sites (site_protocol);
+CREATE INDEX /*i*/sites_domain ON /*_*/sites (site_domain);
+CREATE INDEX /*i*/sites_forward ON /*_*/sites (site_forward);
+CREATE TABLE /*_*/site_identifiers (
+ si_site INT UNSIGNED NOT NULL,
+ si_type varbinary(32) NOT NULL,
+ si_key varbinary(32) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/site_ids_type ON /*_*/site_identifiers (si_type, si_key);
+CREATE INDEX /*i*/site_ids_site ON /*_*/site_identifiers (si_site);
+CREATE INDEX /*i*/site_ids_key ON /*_*/site_identifiers (si_key);
diff --git a/www/wiki/tests/phpunit/data/db/sqlite/tables-1.23.sql b/www/wiki/tests/phpunit/data/db/sqlite/tables-1.23.sql
new file mode 100644
index 00000000..1c3a8ae4
--- /dev/null
+++ b/www/wiki/tests/phpunit/data/db/sqlite/tables-1.23.sql
@@ -0,0 +1,580 @@
+-- This is a copy of MediaWiki 1.23 schema shared by MySQL and SQLite.
+-- It is used for updater testing. Comments are stripped to decrease
+-- file size, as we don't need to maintain it.
+
+CREATE TABLE /*_*/user (
+ user_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ user_name varchar(255) binary NOT NULL default '',
+ user_real_name varchar(255) binary NOT NULL default '',
+ user_password tinyblob NOT NULL,
+ user_newpassword tinyblob NOT NULL,
+ user_newpass_time binary(14),
+ user_email tinytext NOT NULL,
+ user_touched binary(14) NOT NULL default '',
+ user_token binary(32) NOT NULL default '',
+ user_email_authenticated binary(14),
+ user_email_token binary(32),
+ user_email_token_expires binary(14),
+ user_registration binary(14),
+ user_editcount int,
+ user_password_expires varbinary(14) DEFAULT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_name ON /*_*/user (user_name);
+CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token);
+CREATE INDEX /*i*/user_email ON /*_*/user (user_email(50));
+CREATE TABLE /*_*/user_groups (
+ ug_user int unsigned NOT NULL default 0,
+ ug_group varbinary(255) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user,ug_group);
+CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
+CREATE TABLE /*_*/user_former_groups (
+ ufg_user int unsigned NOT NULL default 0,
+ ufg_group varbinary(255) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ufg_user_group ON /*_*/user_former_groups (ufg_user,ufg_group);
+CREATE TABLE /*_*/user_newtalk (
+ user_id int NOT NULL default 0,
+ user_ip varbinary(40) NOT NULL default '',
+ user_last_timestamp varbinary(14) NULL default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/un_user_id ON /*_*/user_newtalk (user_id);
+CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip);
+CREATE TABLE /*_*/user_properties (
+ up_user int NOT NULL,
+ up_property varbinary(255) NOT NULL,
+ up_value blob
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_properties_user_property ON /*_*/user_properties (up_user,up_property);
+CREATE INDEX /*i*/user_properties_property ON /*_*/user_properties (up_property);
+CREATE TABLE /*_*/page (
+ page_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ page_namespace int NOT NULL,
+ page_title varchar(255) binary NOT NULL,
+ page_restrictions tinyblob NOT NULL,
+ page_counter bigint unsigned NOT NULL default 0,
+ page_is_redirect tinyint unsigned NOT NULL default 0,
+ page_is_new tinyint unsigned NOT NULL default 0,
+ page_random real unsigned NOT NULL,
+ page_touched binary(14) NOT NULL default '',
+ page_links_updated varbinary(14) NULL default NULL,
+ page_latest int unsigned NOT NULL,
+ page_len int unsigned NOT NULL,
+ page_content_model varbinary(32) DEFAULT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
+CREATE INDEX /*i*/page_random ON /*_*/page (page_random);
+CREATE INDEX /*i*/page_len ON /*_*/page (page_len);
+CREATE INDEX /*i*/page_redirect_namespace_len ON /*_*/page (page_is_redirect, page_namespace, page_len);
+CREATE TABLE /*_*/revision (
+ rev_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ rev_page int unsigned NOT NULL,
+ rev_text_id int unsigned NOT NULL,
+ rev_comment tinyblob NOT NULL,
+ rev_user int unsigned NOT NULL default 0,
+ rev_user_text varchar(255) binary NOT NULL default '',
+ rev_timestamp binary(14) NOT NULL default '',
+ rev_minor_edit tinyint unsigned NOT NULL default 0,
+ rev_deleted tinyint unsigned NOT NULL default 0,
+ rev_len int unsigned,
+ rev_parent_id int unsigned default NULL,
+ rev_sha1 varbinary(32) NOT NULL default '',
+ rev_content_model varbinary(32) DEFAULT NULL,
+ rev_content_format varbinary(64) DEFAULT NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
+CREATE UNIQUE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
+CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp);
+CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
+CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
+CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
+CREATE INDEX /*i*/page_user_timestamp ON /*_*/revision (rev_page,rev_user,rev_timestamp);
+CREATE TABLE /*_*/text (
+ old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ old_text mediumblob NOT NULL,
+ old_flags tinyblob NOT NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=10240;
+CREATE TABLE /*_*/archive (
+ ar_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ ar_namespace int NOT NULL default 0,
+ ar_title varchar(255) binary NOT NULL default '',
+ ar_text mediumblob NOT NULL,
+ ar_comment tinyblob NOT NULL,
+ ar_user int unsigned NOT NULL default 0,
+ ar_user_text varchar(255) binary NOT NULL,
+ ar_timestamp binary(14) NOT NULL default '',
+ ar_minor_edit tinyint NOT NULL default 0,
+ ar_flags tinyblob NOT NULL,
+ ar_rev_id int unsigned,
+ ar_text_id int unsigned,
+ ar_deleted tinyint unsigned NOT NULL default 0,
+ ar_len int unsigned,
+ ar_page_id int unsigned,
+ ar_parent_id int unsigned default NULL,
+ ar_sha1 varbinary(32) NOT NULL default '',
+ ar_content_model varbinary(32) DEFAULT NULL,
+ ar_content_format varbinary(64) DEFAULT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
+CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id);
+CREATE TABLE /*_*/pagelinks (
+ pl_from int unsigned NOT NULL default 0,
+ pl_namespace int NOT NULL default 0,
+ pl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title);
+CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from);
+CREATE TABLE /*_*/templatelinks (
+ tl_from int unsigned NOT NULL default 0,
+ tl_namespace int NOT NULL default 0,
+ tl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title);
+CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from);
+CREATE TABLE /*_*/imagelinks (
+ il_from int unsigned NOT NULL default 0,
+ il_to varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to);
+CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from);
+CREATE TABLE /*_*/categorylinks (
+ cl_from int unsigned NOT NULL default 0,
+ cl_to varchar(255) binary NOT NULL default '',
+ cl_sortkey varbinary(230) NOT NULL default '',
+ cl_sortkey_prefix varchar(255) binary NOT NULL default '',
+ cl_timestamp timestamp NOT NULL,
+ cl_collation varbinary(32) NOT NULL default '',
+ cl_type ENUM('page', 'subcat', 'file') NOT NULL default 'page'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to);
+CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_type,cl_sortkey,cl_from);
+CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp);
+CREATE INDEX /*i*/cl_collation ON /*_*/categorylinks (cl_collation);
+CREATE TABLE /*_*/category (
+ cat_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ cat_title varchar(255) binary NOT NULL,
+ cat_pages int signed NOT NULL default 0,
+ cat_subcats int signed NOT NULL default 0,
+ cat_files int signed NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cat_title ON /*_*/category (cat_title);
+CREATE INDEX /*i*/cat_pages ON /*_*/category (cat_pages);
+CREATE TABLE /*_*/externallinks (
+ el_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ el_from int unsigned NOT NULL default 0,
+ el_to blob NOT NULL,
+ el_index blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from, el_to(40));
+CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from);
+CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60));
+CREATE TABLE /*_*/langlinks (
+ ll_from int unsigned NOT NULL default 0,
+ ll_lang varbinary(20) NOT NULL default '',
+ ll_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ll_from ON /*_*/langlinks (ll_from, ll_lang);
+CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title);
+CREATE TABLE /*_*/iwlinks (
+ iwl_from int unsigned NOT NULL default 0,
+ iwl_prefix varbinary(20) NOT NULL default '',
+ iwl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title);
+CREATE INDEX /*i*/iwl_prefix_title_from ON /*_*/iwlinks (iwl_prefix, iwl_title, iwl_from);
+CREATE INDEX /*i*/iwl_prefix_from_title ON /*_*/iwlinks (iwl_prefix, iwl_from, iwl_title);
+CREATE TABLE /*_*/site_stats (
+ ss_row_id int unsigned NOT NULL,
+ ss_total_views bigint unsigned default 0,
+ ss_total_edits bigint unsigned default 0,
+ ss_good_articles bigint unsigned default 0,
+ ss_total_pages bigint default '-1',
+ ss_users bigint default '-1',
+ ss_active_users bigint default '-1',
+ ss_images int default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id);
+CREATE TABLE /*_*/hitcounter (
+ hc_id int unsigned NOT NULL
+) ENGINE=HEAP MAX_ROWS=25000;
+CREATE TABLE /*_*/ipblocks (
+ ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ ipb_address tinyblob NOT NULL,
+ ipb_user int unsigned NOT NULL default 0,
+ ipb_by int unsigned NOT NULL default 0,
+ ipb_by_text varchar(255) binary NOT NULL default '',
+ ipb_reason tinyblob NOT NULL,
+ ipb_timestamp binary(14) NOT NULL default '',
+ ipb_auto bool NOT NULL default 0,
+ ipb_anon_only bool NOT NULL default 0,
+ ipb_create_account bool NOT NULL default 1,
+ ipb_enable_autoblock bool NOT NULL default '1',
+ ipb_expiry varbinary(14) NOT NULL default '',
+ ipb_range_start tinyblob NOT NULL,
+ ipb_range_end tinyblob NOT NULL,
+ ipb_deleted bool NOT NULL default 0,
+ ipb_block_email bool NOT NULL default 0,
+ ipb_allow_usertalk bool NOT NULL default 0,
+ ipb_parent_block_id int default NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
+CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
+CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
+CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
+CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
+CREATE INDEX /*i*/ipb_parent_block_id ON /*_*/ipblocks (ipb_parent_block_id);
+CREATE TABLE /*_*/image (
+ img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
+ img_size int unsigned NOT NULL default 0,
+ img_width int NOT NULL default 0,
+ img_height int NOT NULL default 0,
+ img_metadata mediumblob NOT NULL,
+ img_bits int NOT NULL default 0,
+ img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+ img_minor_mime varbinary(100) NOT NULL default "unknown",
+ img_description tinyblob NOT NULL,
+ img_user int unsigned NOT NULL default 0,
+ img_user_text varchar(255) binary NOT NULL,
+ img_timestamp varbinary(14) NOT NULL default '',
+ img_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
+CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
+CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
+CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1(10));
+CREATE INDEX /*i*/img_media_mime ON /*_*/image (img_media_type,img_major_mime,img_minor_mime);
+CREATE TABLE /*_*/oldimage (
+ oi_name varchar(255) binary NOT NULL default '',
+ oi_archive_name varchar(255) binary NOT NULL default '',
+ oi_size int unsigned NOT NULL default 0,
+ oi_width int NOT NULL default 0,
+ oi_height int NOT NULL default 0,
+ oi_bits int NOT NULL default 0,
+ oi_description tinyblob NOT NULL,
+ oi_user int unsigned NOT NULL default 0,
+ oi_user_text varchar(255) binary NOT NULL,
+ oi_timestamp binary(14) NOT NULL default '',
+ oi_metadata mediumblob NOT NULL,
+ oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+ oi_minor_mime varbinary(100) NOT NULL default "unknown",
+ oi_deleted tinyint unsigned NOT NULL default 0,
+ oi_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
+CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
+CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
+CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1(10));
+CREATE TABLE /*_*/filearchive (
+ fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ fa_name varchar(255) binary NOT NULL default '',
+ fa_archive_name varchar(255) binary default '',
+ fa_storage_group varbinary(16),
+ fa_storage_key varbinary(64) default '',
+ fa_deleted_user int,
+ fa_deleted_timestamp binary(14) default '',
+ fa_deleted_reason text,
+ fa_size int unsigned default 0,
+ fa_width int default 0,
+ fa_height int default 0,
+ fa_metadata mediumblob,
+ fa_bits int default 0,
+ fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") default "unknown",
+ fa_minor_mime varbinary(100) default "unknown",
+ fa_description tinyblob,
+ fa_user int unsigned default 0,
+ fa_user_text varchar(255) binary,
+ fa_timestamp binary(14) default '',
+ fa_deleted tinyint unsigned NOT NULL default 0,
+ fa_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
+CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
+CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
+CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
+CREATE INDEX /*i*/fa_sha1 ON /*_*/filearchive (fa_sha1(10));
+CREATE TABLE /*_*/uploadstash (
+ us_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ us_user int unsigned NOT NULL,
+ us_key varchar(255) NOT NULL,
+ us_orig_path varchar(255) NOT NULL,
+ us_path varchar(255) NOT NULL,
+ us_source_type varchar(50),
+ us_timestamp varbinary(14) NOT NULL,
+ us_status varchar(50) NOT NULL,
+ us_chunk_inx int unsigned NULL,
+ us_props blob,
+ us_size int unsigned NOT NULL,
+ us_sha1 varchar(31) NOT NULL,
+ us_mime varchar(255),
+ us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ us_image_width int unsigned,
+ us_image_height int unsigned,
+ us_image_bits smallint unsigned
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/us_user ON /*_*/uploadstash (us_user);
+CREATE UNIQUE INDEX /*i*/us_key ON /*_*/uploadstash (us_key);
+CREATE INDEX /*i*/us_timestamp ON /*_*/uploadstash (us_timestamp);
+CREATE TABLE /*_*/recentchanges (
+ rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ rc_timestamp varbinary(14) NOT NULL default '',
+ rc_cur_time varbinary(14) NOT NULL default '',
+ rc_user int unsigned NOT NULL default 0,
+ rc_user_text varchar(255) binary NOT NULL,
+ rc_namespace int NOT NULL default 0,
+ rc_title varchar(255) binary NOT NULL default '',
+ rc_comment varchar(255) binary NOT NULL default '',
+ rc_minor tinyint unsigned NOT NULL default 0,
+ rc_bot tinyint unsigned NOT NULL default 0,
+ rc_new tinyint unsigned NOT NULL default 0,
+ rc_cur_id int unsigned NOT NULL default 0,
+ rc_this_oldid int unsigned NOT NULL default 0,
+ rc_last_oldid int unsigned NOT NULL default 0,
+ rc_type tinyint unsigned NOT NULL default 0,
+ rc_source varchar(16) binary not null default '',
+ rc_patrolled tinyint unsigned NOT NULL default 0,
+ rc_ip varbinary(40) NOT NULL default '',
+ rc_old_len int,
+ rc_new_len int,
+ rc_deleted tinyint unsigned NOT NULL default 0,
+ rc_logid int unsigned NOT NULL default 0,
+ rc_log_type varbinary(255) NULL default NULL,
+ rc_log_action varbinary(255) NULL default NULL,
+ rc_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp);
+CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title);
+CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id);
+CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp);
+CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
+CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
+CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
+CREATE TABLE /*_*/watchlist (
+ wl_user int unsigned NOT NULL,
+ wl_namespace int NOT NULL default 0,
+ wl_title varchar(255) binary NOT NULL default '',
+ wl_notificationtimestamp varbinary(14)
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title);
+CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title);
+CREATE TABLE /*_*/searchindex (
+ si_page int unsigned NOT NULL,
+ si_title varchar(255) NOT NULL default '',
+ si_text mediumtext NOT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+CREATE UNIQUE INDEX /*i*/si_page ON /*_*/searchindex (si_page);
+CREATE FULLTEXT INDEX /*i*/si_title ON /*_*/searchindex (si_title);
+CREATE FULLTEXT INDEX /*i*/si_text ON /*_*/searchindex (si_text);
+CREATE TABLE /*_*/interwiki (
+ iw_prefix varchar(32) NOT NULL,
+ iw_url blob NOT NULL,
+ iw_api blob NOT NULL,
+ iw_wikiid varchar(64) NOT NULL,
+ iw_local bool NOT NULL,
+ iw_trans tinyint NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix);
+CREATE TABLE /*_*/querycache (
+ qc_type varbinary(32) NOT NULL,
+ qc_value int unsigned NOT NULL default 0,
+ qc_namespace int NOT NULL default 0,
+ qc_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qc_type ON /*_*/querycache (qc_type,qc_value);
+CREATE TABLE /*_*/objectcache (
+ keyname varbinary(255) NOT NULL default '' PRIMARY KEY,
+ value mediumblob,
+ exptime datetime
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime);
+CREATE TABLE /*_*/transcache (
+ tc_url varbinary(255) NOT NULL,
+ tc_contents text,
+ tc_time binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url);
+CREATE TABLE /*_*/logging (
+ log_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ log_type varbinary(32) NOT NULL default '',
+ log_action varbinary(32) NOT NULL default '',
+ log_timestamp binary(14) NOT NULL default '19700101000000',
+ log_user int unsigned NOT NULL default 0,
+ log_user_text varchar(255) binary NOT NULL default '',
+ log_namespace int NOT NULL default 0,
+ log_title varchar(255) binary NOT NULL default '',
+ log_page int unsigned NULL,
+ log_comment varchar(255) NOT NULL default '',
+ log_params blob NOT NULL,
+ log_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
+CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
+CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
+CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
+CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
+CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
+CREATE INDEX /*i*/type_action ON /*_*/logging (log_type, log_action, log_timestamp);
+CREATE INDEX /*i*/log_user_text_type_time ON /*_*/logging (log_user_text, log_type, log_timestamp);
+CREATE INDEX /*i*/log_user_text_time ON /*_*/logging (log_user_text, log_timestamp);
+CREATE TABLE /*_*/log_search (
+ ls_field varbinary(32) NOT NULL,
+ ls_value varchar(255) NOT NULL,
+ ls_log_id int unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ls_field_val ON /*_*/log_search (ls_field,ls_value,ls_log_id);
+CREATE INDEX /*i*/ls_log_id ON /*_*/log_search (ls_log_id);
+CREATE TABLE /*_*/job (
+ job_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ job_cmd varbinary(60) NOT NULL default '',
+ job_namespace int NOT NULL,
+ job_title varchar(255) binary NOT NULL,
+ job_timestamp varbinary(14) NULL default NULL,
+ job_params blob NOT NULL,
+ job_random integer unsigned NOT NULL default 0,
+ job_attempts integer unsigned NOT NULL default 0,
+ job_token varbinary(32) NOT NULL default '',
+ job_token_timestamp varbinary(14) NULL default NULL,
+ job_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/job_sha1 ON /*_*/job (job_sha1);
+CREATE INDEX /*i*/job_cmd_token ON /*_*/job (job_cmd,job_token,job_random);
+CREATE INDEX /*i*/job_cmd_token_id ON /*_*/job (job_cmd,job_token,job_id);
+CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
+CREATE INDEX /*i*/job_timestamp ON /*_*/job (job_timestamp);
+CREATE TABLE /*_*/querycache_info (
+ qci_type varbinary(32) NOT NULL default '',
+ qci_timestamp binary(14) NOT NULL default '19700101000000'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/qci_type ON /*_*/querycache_info (qci_type);
+CREATE TABLE /*_*/redirect (
+ rd_from int unsigned NOT NULL default 0 PRIMARY KEY,
+ rd_namespace int NOT NULL default 0,
+ rd_title varchar(255) binary NOT NULL default '',
+ rd_interwiki varchar(32) default NULL,
+ rd_fragment varchar(255) binary default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from);
+CREATE TABLE /*_*/querycachetwo (
+ qcc_type varbinary(32) NOT NULL,
+ qcc_value int unsigned NOT NULL default 0,
+ qcc_namespace int NOT NULL default 0,
+ qcc_title varchar(255) binary NOT NULL default '',
+ qcc_namespacetwo int NOT NULL default 0,
+ qcc_titletwo varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qcc_type ON /*_*/querycachetwo (qcc_type,qcc_value);
+CREATE INDEX /*i*/qcc_title ON /*_*/querycachetwo (qcc_type,qcc_namespace,qcc_title);
+CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
+CREATE TABLE /*_*/page_restrictions (
+ pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ pr_page int NOT NULL,
+ pr_type varbinary(60) NOT NULL,
+ pr_level varbinary(60) NOT NULL,
+ pr_cascade tinyint NOT NULL,
+ pr_user int NULL,
+ pr_expiry varbinary(14) NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions (pr_page,pr_type);
+CREATE INDEX /*i*/pr_typelevel ON /*_*/page_restrictions (pr_type,pr_level);
+CREATE INDEX /*i*/pr_level ON /*_*/page_restrictions (pr_level);
+CREATE INDEX /*i*/pr_cascade ON /*_*/page_restrictions (pr_cascade);
+CREATE TABLE /*_*/protected_titles (
+ pt_namespace int NOT NULL,
+ pt_title varchar(255) binary NOT NULL,
+ pt_user int unsigned NOT NULL,
+ pt_reason tinyblob,
+ pt_timestamp binary(14) NOT NULL,
+ pt_expiry varbinary(14) NOT NULL default '',
+ pt_create_perm varbinary(60) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pt_namespace_title ON /*_*/protected_titles (pt_namespace,pt_title);
+CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp);
+CREATE TABLE /*_*/page_props (
+ pp_page int NOT NULL,
+ pp_propname varbinary(60) NOT NULL,
+ pp_value blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname);
+CREATE UNIQUE INDEX /*i*/pp_propname_page ON /*_*/page_props (pp_propname,pp_page);
+CREATE TABLE /*_*/updatelog (
+ ul_key varchar(255) NOT NULL PRIMARY KEY,
+ ul_value blob
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/change_tag (
+ ct_rc_id int NULL,
+ ct_log_id int NULL,
+ ct_rev_id int NULL,
+ ct_tag varchar(255) NOT NULL,
+ ct_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/change_tag_rc_tag ON /*_*/change_tag (ct_rc_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_log_tag ON /*_*/change_tag (ct_log_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag);
+CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
+CREATE TABLE /*_*/tag_summary (
+ ts_rc_id int NULL,
+ ts_log_id int NULL,
+ ts_rev_id int NULL,
+ ts_tags blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id);
+CREATE TABLE /*_*/valid_tag (
+ vt_tag varchar(255) NOT NULL PRIMARY KEY
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/l10n_cache (
+ lc_lang varbinary(32) NOT NULL,
+ lc_key varchar(255) NOT NULL,
+ lc_value mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key);
+CREATE TABLE /*_*/msg_resource (
+ mr_resource varbinary(255) NOT NULL,
+ mr_lang varbinary(32) NOT NULL,
+ mr_blob mediumblob NOT NULL,
+ mr_timestamp binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mr_resource_lang ON /*_*/msg_resource (mr_resource, mr_lang);
+CREATE TABLE /*_*/msg_resource_links (
+ mrl_resource varbinary(255) NOT NULL,
+ mrl_message varbinary(255) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mrl_message_resource ON /*_*/msg_resource_links (mrl_message, mrl_resource);
+CREATE TABLE /*_*/module_deps (
+ md_module varbinary(255) NOT NULL,
+ md_skin varbinary(32) NOT NULL,
+ md_deps mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/md_module_skin ON /*_*/module_deps (md_module, md_skin);
+CREATE TABLE /*_*/sites (
+ site_id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ site_global_key varbinary(32) NOT NULL,
+ site_type varbinary(32) NOT NULL,
+ site_group varbinary(32) NOT NULL,
+ site_source varbinary(32) NOT NULL,
+ site_language varbinary(32) NOT NULL,
+ site_protocol varbinary(32) NOT NULL,
+ site_domain VARCHAR(255) NOT NULL,
+ site_data BLOB NOT NULL,
+ site_forward bool NOT NULL,
+ site_config BLOB NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/sites_global_key ON /*_*/sites (site_global_key);
+CREATE INDEX /*i*/sites_type ON /*_*/sites (site_type);
+CREATE INDEX /*i*/sites_group ON /*_*/sites (site_group);
+CREATE INDEX /*i*/sites_source ON /*_*/sites (site_source);
+CREATE INDEX /*i*/sites_language ON /*_*/sites (site_language);
+CREATE INDEX /*i*/sites_protocol ON /*_*/sites (site_protocol);
+CREATE INDEX /*i*/sites_domain ON /*_*/sites (site_domain);
+CREATE INDEX /*i*/sites_forward ON /*_*/sites (site_forward);
+CREATE TABLE /*_*/site_identifiers (
+ si_site INT UNSIGNED NOT NULL,
+ si_type varbinary(32) NOT NULL,
+ si_key varbinary(32) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/site_ids_type ON /*_*/site_identifiers (si_type, si_key);
+CREATE INDEX /*i*/site_ids_site ON /*_*/site_identifiers (si_site);
+CREATE INDEX /*i*/site_ids_key ON /*_*/site_identifiers (si_key);
diff --git a/www/wiki/tests/phpunit/data/helpers/WellProtectedClass.php b/www/wiki/tests/phpunit/data/helpers/WellProtectedClass.php
deleted file mode 100644
index f2b5a149..00000000
--- a/www/wiki/tests/phpunit/data/helpers/WellProtectedClass.php
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-
-class WellProtectedParentClass {
- private $privateParentProperty;
-
- public function __construct() {
- $this->privateParentProperty = 9000;
- }
-
- private function incrementPrivateParentPropertyValue() {
- $this->privateParentProperty++;
- }
-
- public function getPrivateParentProperty() {
- return $this->privateParentProperty;
- }
-}
-
-class WellProtectedClass extends WellProtectedParentClass {
- protected static $staticProperty = 'sp';
- private static $staticPrivateProperty = 'spp';
-
- protected $property;
- private $privateProperty;
-
- protected static function staticMethod() {
- return 'sm';
- }
-
- private static function staticPrivateMethod() {
- return 'spm';
- }
-
- public function __construct() {
- parent::__construct();
- $this->property = 1;
- $this->privateProperty = 42;
- }
-
- protected function incrementPropertyValue() {
- $this->property++;
- }
-
- private function incrementPrivatePropertyValue() {
- $this->privateProperty++;
- }
-
- public function getProperty() {
- return $this->property;
- }
-
- public function getPrivateProperty() {
- return $this->privateProperty;
- }
-
- protected function whatSecondArg( $a, $b = false ) {
- return $b;
- }
-}
diff --git a/www/wiki/tests/phpunit/data/localisationcache/uk.json b/www/wiki/tests/phpunit/data/localisationcache/uk.json
deleted file mode 100644
index f63ce5d3..00000000
--- a/www/wiki/tests/phpunit/data/localisationcache/uk.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "present-uk": "uk"
-}
diff --git a/www/wiki/tests/phpunit/data/media/README b/www/wiki/tests/phpunit/data/media/README
index 9913f685..52f19128 100644
--- a/www/wiki/tests/phpunit/data/media/README
+++ b/www/wiki/tests/phpunit/data/media/README
@@ -4,18 +4,18 @@ tests in includes/media directory.
Image credits:
QA_icon.svg:
-http://es.wikipedia.org/wiki/Archivo:QA_icon.svg
+https://es.wikipedia.org/wiki/Archivo:QA_icon.svg
GNU Lesser General Public License
~~helix84 (16.4.2007), Philverney (6.12.2005) David Vignoni
Gtk-media-play-ltr.svg
-http://commons.wikimedia.org/wiki/File:Gtk-media-play-ltr.svg
+https://commons.wikimedia.org/wiki/File:Gtk-media-play-ltr.svg
GNU Lesser General Public License
-http://ftp.gnome.org/pub/GNOME/sources/gnome-themes-extras/0.9/gnome-themes-extras-0.9.0.tar.gz
+https://ftp.gnome.org/pub/GNOME/sources/gnome-themes-extras/0.9/gnome-themes-extras-0.9.0.tar.gz
David Vignoni
US_states_by_total_state_tax_revenue.svg
-http://commons.wikimedia.org/wiki/File:US_states_by_total_state_tax_revenue.svg
+https://commons.wikimedia.org/wiki/File:US_states_by_total_state_tax_revenue.svg
CC BY 3.0
TastyCakes on English Wikipedia
@@ -32,7 +32,7 @@ claim copyright, but on the off chance they do, feel free to use them
however you feel fit, without restriction.
Animated_PNG_example_bouncing_beach_ball.png
-http://commons.wikimedia.org/wiki/File:Animated_PNG_example_bouncing_beach_ball.png (originally http://www.treebuilder.de/default.asp?file=89031.xml )
+https://commons.wikimedia.org/wiki/File:Animated_PNG_example_bouncing_beach_ball.png (originally http://www.treebuilder.de/default.asp?file=89031.xml )
Public Domain
Holger Will
diff --git a/www/wiki/tests/phpunit/data/media/jpeg-segment-loop1.jpg b/www/wiki/tests/phpunit/data/media/jpeg-segment-loop1.jpg
new file mode 100644
index 00000000..962f3fe0
--- /dev/null
+++ b/www/wiki/tests/phpunit/data/media/jpeg-segment-loop1.jpg
Binary files differ
diff --git a/www/wiki/tests/phpunit/data/media/jpeg-segment-loop2.jpg b/www/wiki/tests/phpunit/data/media/jpeg-segment-loop2.jpg
new file mode 100644
index 00000000..e3a7505c
--- /dev/null
+++ b/www/wiki/tests/phpunit/data/media/jpeg-segment-loop2.jpg
Binary files differ
diff --git a/www/wiki/tests/phpunit/data/registration/bad_spdx.json b/www/wiki/tests/phpunit/data/registration/bad_spdx.json
new file mode 100644
index 00000000..383ab047
--- /dev/null
+++ b/www/wiki/tests/phpunit/data/registration/bad_spdx.json
@@ -0,0 +1,6 @@
+{
+ "name": "FooBar",
+ "license-name": "not a license identifier",
+ "manifest_version": 1
+}
+
diff --git a/www/wiki/tests/phpunit/data/registration/good.json b/www/wiki/tests/phpunit/data/registration/good.json
new file mode 100644
index 00000000..ad16c5e4
--- /dev/null
+++ b/www/wiki/tests/phpunit/data/registration/good.json
@@ -0,0 +1,4 @@
+{
+ "name": "FooBar",
+ "manifest_version": 1
+}
diff --git a/www/wiki/tests/phpunit/data/registration/invalid.json b/www/wiki/tests/phpunit/data/registration/invalid.json
new file mode 100644
index 00000000..4d1fa589
--- /dev/null
+++ b/www/wiki/tests/phpunit/data/registration/invalid.json
@@ -0,0 +1,5 @@
+{
+ "name": "FooBar",
+ "license-name": [ "array" ],
+ "manifest_version": 1
+}
diff --git a/www/wiki/tests/phpunit/data/registration/newer_manifest_version.json b/www/wiki/tests/phpunit/data/registration/newer_manifest_version.json
new file mode 100644
index 00000000..29c668ee
--- /dev/null
+++ b/www/wiki/tests/phpunit/data/registration/newer_manifest_version.json
@@ -0,0 +1,4 @@
+{
+ "name": "FooBar",
+ "manifest_version": 999999
+}
diff --git a/www/wiki/tests/phpunit/data/registration/no_manifest_version.json b/www/wiki/tests/phpunit/data/registration/no_manifest_version.json
new file mode 100644
index 00000000..1a6119f7
--- /dev/null
+++ b/www/wiki/tests/phpunit/data/registration/no_manifest_version.json
@@ -0,0 +1,3 @@
+{
+ "name": "FooBar"
+}
diff --git a/www/wiki/tests/phpunit/data/registration/notjson.txt b/www/wiki/tests/phpunit/data/registration/notjson.txt
new file mode 100644
index 00000000..d47b4607
--- /dev/null
+++ b/www/wiki/tests/phpunit/data/registration/notjson.txt
@@ -0,0 +1 @@
+This is definitely not JSON.
diff --git a/www/wiki/tests/phpunit/data/registration/old_manifest_version.json b/www/wiki/tests/phpunit/data/registration/old_manifest_version.json
new file mode 100644
index 00000000..f50faa1b
--- /dev/null
+++ b/www/wiki/tests/phpunit/data/registration/old_manifest_version.json
@@ -0,0 +1,4 @@
+{
+ "name": "FooBar",
+ "manifest_version": -2
+}
diff --git a/www/wiki/tests/phpunit/data/resourceloader/add.gif b/www/wiki/tests/phpunit/data/resourceloader/add.gif
deleted file mode 100644
index 5f454ca1..00000000
--- a/www/wiki/tests/phpunit/data/resourceloader/add.gif
+++ /dev/null
Binary files differ
diff --git a/www/wiki/tests/phpunit/data/resourceloader/bold-a.svg b/www/wiki/tests/phpunit/data/resourceloader/bold-a.svg
deleted file mode 100644
index 4b828779..00000000
--- a/www/wiki/tests/phpunit/data/resourceloader/bold-a.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
- <g id="bold-a">
- <path d="M16 18h3l-5-12h-3l-5 12h3l1.25-3h4.5l1.25 3zm-4.917-5l1.417-3.4 1.417 3.4h-2.834z"/>
- </g>
-</svg>
diff --git a/www/wiki/tests/phpunit/data/resourceloader/bold-b.svg b/www/wiki/tests/phpunit/data/resourceloader/bold-b.svg
deleted file mode 100644
index 4f648203..00000000
--- a/www/wiki/tests/phpunit/data/resourceloader/bold-b.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
- <g id="bold-b">
- <path id="b" d="M7 18h6c2 0 4-1 4-3 0-1.064.011-1.975-1.989-3 2-.975 1.989-1.935 1.989-3 0-2-2-3-4-3h-6v12zm7-8c0 1.001 0 1-2 1h-2v-3h2c2 0 2 0 2 1v1zm-2 6h-2v-3h2c2 0 2 0 2 1v1s0 1-2 1z"/>
- </g>
-</svg>
diff --git a/www/wiki/tests/phpunit/data/resourceloader/bold-f.svg b/www/wiki/tests/phpunit/data/resourceloader/bold-f.svg
deleted file mode 100644
index 357d2e5d..00000000
--- a/www/wiki/tests/phpunit/data/resourceloader/bold-f.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
- <g id="bold-f">
- <path id="f" d="M16 8v-2h-8v12h3v-5h4v-2h-4v-3z"/>
- </g>
-</svg>
diff --git a/www/wiki/tests/phpunit/data/resourceloader/help-ltr.svg b/www/wiki/tests/phpunit/data/resourceloader/help-ltr.svg
deleted file mode 100644
index bb2545c5..00000000
--- a/www/wiki/tests/phpunit/data/resourceloader/help-ltr.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
- <g id="help">
- <path id="circle" d="M12.001 2.085c-5.478 0-9.916 4.438-9.916 9.916 0 5.476 4.438 9.914 9.916 9.914 5.476 0 9.914-4.438 9.914-9.914 0-5.478-4.438-9.916-9.914-9.916zm.001 18c-4.465 0-8.084-3.619-8.084-8.083 0-4.465 3.619-8.084 8.084-8.084 4.464 0 8.083 3.619 8.083 8.084 0 4.464-3.619 8.083-8.083 8.083z"/>
- <g id="question-mark">
- <path id="top" d="M11.766 6.688c-2.5 0-3.219 2.188-3.219 2.188l1.411.854s.298-.791.901-1.229c.516-.375 1.625-.625 2.219.125.701.885-.17 1.587-1.078 2.719-.953 1.186-1 3.655-1 3.655h1.969s.135-2.318 1.041-3.381c.603-.707 1.443-1.338 1.443-2.494s-1.187-2.437-3.687-2.437z"/>
- <path id="bottom" d="M11 16h2v2h-2z"/>
- </g>
- </g>
-</svg>
diff --git a/www/wiki/tests/phpunit/data/resourceloader/help-rtl.svg b/www/wiki/tests/phpunit/data/resourceloader/help-rtl.svg
deleted file mode 100644
index 255ae95b..00000000
--- a/www/wiki/tests/phpunit/data/resourceloader/help-rtl.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
- <g id="help">
- <path id="circle" d="M12.001 2.085c-5.478 0-9.916 4.438-9.916 9.916 0 5.476 4.438 9.914 9.916 9.914 5.476 0 9.914-4.438 9.914-9.914 0-5.478-4.438-9.916-9.914-9.916zm.001 18c-4.465 0-8.084-3.619-8.084-8.083 0-4.465 3.619-8.084 8.084-8.084 4.464 0 8.083 3.619 8.083 8.084 0 4.464-3.619 8.083-8.083 8.083z"/>
- <g id="question-mark" transform="translate(24, 0) scale(-1, 1)">
- <path id="top" d="M11.766 6.688c-2.5 0-3.219 2.188-3.219 2.188l1.411.854s.298-.791.901-1.229c.516-.375 1.625-.625 2.219.125.701.885-.17 1.587-1.078 2.719-.953 1.186-1 3.655-1 3.655h1.969s.135-2.318 1.041-3.381c.603-.707 1.443-1.338 1.443-2.494s-1.187-2.437-3.687-2.437z"/>
- <path id="bottom" d="M11 16h2v2h-2z"/>
- </g>
- </g>
-</svg>
diff --git a/www/wiki/tests/phpunit/data/resourceloader/next.svg b/www/wiki/tests/phpunit/data/resourceloader/next.svg
deleted file mode 100644
index 02b4e387..00000000
--- a/www/wiki/tests/phpunit/data/resourceloader/next.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
- <path d="M16.5 13.1l-8.9 8.9c-.8-.8-.8-2 0-2.8l6.1-6.1-6-6.1c-.8-.8-.8-2 0-2.8l8.8 8.9z" id="path108"/>
-</svg>
diff --git a/www/wiki/tests/phpunit/data/resourceloader/next_massage.svg b/www/wiki/tests/phpunit/data/resourceloader/next_massage.svg
deleted file mode 100644
index bbd1a8d6..00000000
--- a/www/wiki/tests/phpunit/data/resourceloader/next_massage.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
- <path d="M 16.5 13.1 l -8.9 8.9 c -0.8 -0.8 -0.8 -2 0 -2.8 l 6.1 -6.1 -6 -6.1 c -0.8 -0.8 -0.8 -2 0 -2.8 l 8.8 8.9 z" id="path108"/>
-</svg>
diff --git a/www/wiki/tests/phpunit/data/resourceloader/prev.svg b/www/wiki/tests/phpunit/data/resourceloader/prev.svg
deleted file mode 100644
index f31ec095..00000000
--- a/www/wiki/tests/phpunit/data/resourceloader/prev.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
- <path d="M7 13.1l8.9 8.9c.8-.8.8-2 0-2.8l-6.1-6.1 6-6.1c.8-.8.8-2 0-2.8l-8.8 8.9z"/>
-</svg>
diff --git a/www/wiki/tests/phpunit/data/resourceloader/remove.svg b/www/wiki/tests/phpunit/data/resourceloader/remove.svg
deleted file mode 100644
index 6ad79174..00000000
--- a/www/wiki/tests/phpunit/data/resourceloader/remove.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
- <g id="remove">
- <path id="trash-can" d="M12 10h-1v6h1v-6zm-2 0h-1v6h1v-6zm4 0h-1v6h1v-6zm0-4v-1h-5v1h-3v3h1v7.966l1 1.031v-.074.077h6.984l.016-.018v.015l1-1.031v-7.966h1v-3h-3zm1 11h-7v-8h7v8zm1-9h-9v-1h9v1z"/>
- </g>
-</svg>
diff --git a/www/wiki/tests/phpunit/data/resourceloader/remove_variantize.svg b/www/wiki/tests/phpunit/data/resourceloader/remove_variantize.svg
deleted file mode 100644
index bcbe8712..00000000
--- a/www/wiki/tests/phpunit/data/resourceloader/remove_variantize.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="red">
- <g xmlns:default="http://www.w3.org/2000/svg" id="remove">
- <path id="trash-can" d="M12 10h-1v6h1v-6zm-2 0h-1v6h1v-6zm4 0h-1v6h1v-6zm0-4v-1h-5v1h-3v3h1v7.966l1 1.031v-.074.077h6.984l.016-.018v.015l1-1.031v-7.966h1v-3h-3zm1 11h-7v-8h7v8zm1-9h-9v-1h9v1z"/>
- </g>
-</g></svg>
diff --git a/www/wiki/tests/phpunit/data/templates/conds.mustache b/www/wiki/tests/phpunit/data/templates/conds.mustache
deleted file mode 100644
index 5ebd2ea3..00000000
--- a/www/wiki/tests/phpunit/data/templates/conds.mustache
+++ /dev/null
@@ -1 +0,0 @@
-{{#list}}oh no{{/list}}{{#foo}}none of this should render{{/foo}} \ No newline at end of file
diff --git a/www/wiki/tests/phpunit/includes/ActorMigrationTest.php b/www/wiki/tests/phpunit/includes/ActorMigrationTest.php
new file mode 100644
index 00000000..1b0c848b
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/ActorMigrationTest.php
@@ -0,0 +1,695 @@
+<?php
+
+use MediaWiki\User\UserIdentity;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @group Database
+ * @covers ActorMigration
+ */
+class ActorMigrationTest extends MediaWikiLangTestCase {
+
+ protected $tablesUsed = [
+ 'revision',
+ 'revision_actor_temp',
+ 'ipblocks',
+ 'recentchanges',
+ 'actor',
+ ];
+
+ /**
+ * Create an ActorMigration for a particular stage
+ * @param int $stage
+ * @return ActorMigration
+ */
+ protected function makeMigration( $stage ) {
+ return new ActorMigration( $stage );
+ }
+
+ /**
+ * @dataProvider provideGetJoin
+ * @param int $stage
+ * @param string $key
+ * @param array $expect
+ */
+ public function testGetJoin( $stage, $key, $expect ) {
+ $m = $this->makeMigration( $stage );
+ $result = $m->getJoin( $key );
+ $this->assertEquals( $expect, $result );
+ }
+
+ public static function provideGetJoin() {
+ return [
+ 'Simple table, old' => [
+ MIGRATION_OLD, 'rc_user', [
+ 'tables' => [],
+ 'fields' => [
+ 'rc_user' => 'rc_user',
+ 'rc_user_text' => 'rc_user_text',
+ 'rc_actor' => 'NULL',
+ ],
+ 'joins' => [],
+ ],
+ ],
+ 'Simple table, write-both' => [
+ MIGRATION_WRITE_BOTH, 'rc_user', [
+ 'tables' => [ 'actor_rc_user' => 'actor' ],
+ 'fields' => [
+ 'rc_user' => 'COALESCE( actor_rc_user.actor_user, rc_user )',
+ 'rc_user_text' => 'COALESCE( actor_rc_user.actor_name, rc_user_text )',
+ 'rc_actor' => 'rc_actor',
+ ],
+ 'joins' => [
+ 'actor_rc_user' => [ 'LEFT JOIN', 'actor_rc_user.actor_id = rc_actor' ],
+ ],
+ ],
+ ],
+ 'Simple table, write-new' => [
+ MIGRATION_WRITE_NEW, 'rc_user', [
+ 'tables' => [ 'actor_rc_user' => 'actor' ],
+ 'fields' => [
+ 'rc_user' => 'COALESCE( actor_rc_user.actor_user, rc_user )',
+ 'rc_user_text' => 'COALESCE( actor_rc_user.actor_name, rc_user_text )',
+ 'rc_actor' => 'rc_actor',
+ ],
+ 'joins' => [
+ 'actor_rc_user' => [ 'LEFT JOIN', 'actor_rc_user.actor_id = rc_actor' ],
+ ],
+ ],
+ ],
+ 'Simple table, new' => [
+ MIGRATION_NEW, 'rc_user', [
+ 'tables' => [ 'actor_rc_user' => 'actor' ],
+ 'fields' => [
+ 'rc_user' => 'actor_rc_user.actor_user',
+ 'rc_user_text' => 'actor_rc_user.actor_name',
+ 'rc_actor' => 'rc_actor',
+ ],
+ 'joins' => [
+ 'actor_rc_user' => [ 'JOIN', 'actor_rc_user.actor_id = rc_actor' ],
+ ],
+ ],
+ ],
+
+ 'ipblocks, old' => [
+ MIGRATION_OLD, 'ipb_by', [
+ 'tables' => [],
+ 'fields' => [
+ 'ipb_by' => 'ipb_by',
+ 'ipb_by_text' => 'ipb_by_text',
+ 'ipb_by_actor' => 'NULL',
+ ],
+ 'joins' => [],
+ ],
+ ],
+ 'ipblocks, write-both' => [
+ MIGRATION_WRITE_BOTH, 'ipb_by', [
+ 'tables' => [ 'actor_ipb_by' => 'actor' ],
+ 'fields' => [
+ 'ipb_by' => 'COALESCE( actor_ipb_by.actor_user, ipb_by )',
+ 'ipb_by_text' => 'COALESCE( actor_ipb_by.actor_name, ipb_by_text )',
+ 'ipb_by_actor' => 'ipb_by_actor',
+ ],
+ 'joins' => [
+ 'actor_ipb_by' => [ 'LEFT JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
+ ],
+ ],
+ ],
+ 'ipblocks, write-new' => [
+ MIGRATION_WRITE_NEW, 'ipb_by', [
+ 'tables' => [ 'actor_ipb_by' => 'actor' ],
+ 'fields' => [
+ 'ipb_by' => 'COALESCE( actor_ipb_by.actor_user, ipb_by )',
+ 'ipb_by_text' => 'COALESCE( actor_ipb_by.actor_name, ipb_by_text )',
+ 'ipb_by_actor' => 'ipb_by_actor',
+ ],
+ 'joins' => [
+ 'actor_ipb_by' => [ 'LEFT JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
+ ],
+ ],
+ ],
+ 'ipblocks, new' => [
+ MIGRATION_NEW, 'ipb_by', [
+ 'tables' => [ 'actor_ipb_by' => 'actor' ],
+ 'fields' => [
+ 'ipb_by' => 'actor_ipb_by.actor_user',
+ 'ipb_by_text' => 'actor_ipb_by.actor_name',
+ 'ipb_by_actor' => 'ipb_by_actor',
+ ],
+ 'joins' => [
+ 'actor_ipb_by' => [ 'JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
+ ],
+ ],
+ ],
+
+ 'Revision, old' => [
+ MIGRATION_OLD, 'rev_user', [
+ 'tables' => [],
+ 'fields' => [
+ 'rev_user' => 'rev_user',
+ 'rev_user_text' => 'rev_user_text',
+ 'rev_actor' => 'NULL',
+ ],
+ 'joins' => [],
+ ],
+ ],
+ 'Revision, write-both' => [
+ MIGRATION_WRITE_BOTH, 'rev_user', [
+ 'tables' => [
+ 'temp_rev_user' => 'revision_actor_temp',
+ 'actor_rev_user' => 'actor',
+ ],
+ 'fields' => [
+ 'rev_user' => 'COALESCE( actor_rev_user.actor_user, rev_user )',
+ 'rev_user_text' => 'COALESCE( actor_rev_user.actor_name, rev_user_text )',
+ 'rev_actor' => 'temp_rev_user.revactor_actor',
+ ],
+ 'joins' => [
+ 'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'actor_rev_user' => [ 'LEFT JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+ ],
+ ],
+ ],
+ 'Revision, write-new' => [
+ MIGRATION_WRITE_NEW, 'rev_user', [
+ 'tables' => [
+ 'temp_rev_user' => 'revision_actor_temp',
+ 'actor_rev_user' => 'actor',
+ ],
+ 'fields' => [
+ 'rev_user' => 'COALESCE( actor_rev_user.actor_user, rev_user )',
+ 'rev_user_text' => 'COALESCE( actor_rev_user.actor_name, rev_user_text )',
+ 'rev_actor' => 'temp_rev_user.revactor_actor',
+ ],
+ 'joins' => [
+ 'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'actor_rev_user' => [ 'LEFT JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+ ],
+ ],
+ ],
+ 'Revision, new' => [
+ MIGRATION_NEW, 'rev_user', [
+ 'tables' => [
+ 'temp_rev_user' => 'revision_actor_temp',
+ 'actor_rev_user' => 'actor',
+ ],
+ 'fields' => [
+ 'rev_user' => 'actor_rev_user.actor_user',
+ 'rev_user_text' => 'actor_rev_user.actor_name',
+ 'rev_actor' => 'temp_rev_user.revactor_actor',
+ ],
+ 'joins' => [
+ 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+ ],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetWhere
+ * @param int $stage
+ * @param string $key
+ * @param UserIdentity[] $users
+ * @param bool $useId
+ * @param array $expect
+ */
+ public function testGetWhere( $stage, $key, $users, $useId, $expect ) {
+ $expect['conds'] = '(' . implode( ') OR (', $expect['orconds'] ) . ')';
+
+ if ( count( $users ) === 1 ) {
+ $users = reset( $users );
+ }
+
+ $m = $this->makeMigration( $stage );
+ $result = $m->getWhere( $this->db, $key, $users, $useId );
+ $this->assertEquals( $expect, $result );
+ }
+
+ public function provideGetWhere() {
+ $makeUserIdentity = function ( $id, $name, $actor ) {
+ $u = $this->getMock( UserIdentity::class );
+ $u->method( 'getId' )->willReturn( $id );
+ $u->method( 'getName' )->willReturn( $name );
+ $u->method( 'getActorId' )->willReturn( $actor );
+ return $u;
+ };
+
+ $genericUser = [ $makeUserIdentity( 1, 'User1', 11 ) ];
+ $complicatedUsers = [
+ $makeUserIdentity( 1, 'User1', 11 ),
+ $makeUserIdentity( 2, 'User2', 12 ),
+ $makeUserIdentity( 3, 'User3', 0 ),
+ $makeUserIdentity( 0, '192.168.12.34', 34 ),
+ $makeUserIdentity( 0, '192.168.12.35', 0 ),
+ ];
+
+ return [
+ 'Simple table, old' => [
+ MIGRATION_OLD, 'rc_user', $genericUser, true, [
+ 'tables' => [],
+ 'orconds' => [ 'userid' => "rc_user = '1'" ],
+ 'joins' => [],
+ ],
+ ],
+ 'Simple table, write-both' => [
+ MIGRATION_WRITE_BOTH, 'rc_user', $genericUser, true, [
+ 'tables' => [],
+ 'orconds' => [
+ 'actor' => "rc_actor = '11'",
+ 'userid' => "rc_actor = '0' AND rc_user = '1'"
+ ],
+ 'joins' => [],
+ ],
+ ],
+ 'Simple table, write-new' => [
+ MIGRATION_WRITE_NEW, 'rc_user', $genericUser, true, [
+ 'tables' => [],
+ 'orconds' => [
+ 'actor' => "rc_actor = '11'",
+ 'userid' => "rc_actor = '0' AND rc_user = '1'"
+ ],
+ 'joins' => [],
+ ],
+ ],
+ 'Simple table, new' => [
+ MIGRATION_NEW, 'rc_user', $genericUser, true, [
+ 'tables' => [],
+ 'orconds' => [ 'actor' => "rc_actor = '11'" ],
+ 'joins' => [],
+ ],
+ ],
+
+ 'ipblocks, old' => [
+ MIGRATION_OLD, 'ipb_by', $genericUser, true, [
+ 'tables' => [],
+ 'orconds' => [ 'userid' => "ipb_by = '1'" ],
+ 'joins' => [],
+ ],
+ ],
+ 'ipblocks, write-both' => [
+ MIGRATION_WRITE_BOTH, 'ipb_by', $genericUser, true, [
+ 'tables' => [],
+ 'orconds' => [
+ 'actor' => "ipb_by_actor = '11'",
+ 'userid' => "ipb_by_actor = '0' AND ipb_by = '1'"
+ ],
+ 'joins' => [],
+ ],
+ ],
+ 'ipblocks, write-new' => [
+ MIGRATION_WRITE_NEW, 'ipb_by', $genericUser, true, [
+ 'tables' => [],
+ 'orconds' => [
+ 'actor' => "ipb_by_actor = '11'",
+ 'userid' => "ipb_by_actor = '0' AND ipb_by = '1'"
+ ],
+ 'joins' => [],
+ ],
+ ],
+ 'ipblocks, new' => [
+ MIGRATION_NEW, 'ipb_by', $genericUser, true, [
+ 'tables' => [],
+ 'orconds' => [ 'actor' => "ipb_by_actor = '11'" ],
+ 'joins' => [],
+ ],
+ ],
+
+ 'Revision, old' => [
+ MIGRATION_OLD, 'rev_user', $genericUser, true, [
+ 'tables' => [],
+ 'orconds' => [ 'userid' => "rev_user = '1'" ],
+ 'joins' => [],
+ ],
+ ],
+ 'Revision, write-both' => [
+ MIGRATION_WRITE_BOTH, 'rev_user', $genericUser, true, [
+ 'tables' => [
+ 'temp_rev_user' => 'revision_actor_temp',
+ ],
+ 'orconds' => [
+ 'actor' =>
+ "(temp_rev_user.revactor_actor IS NOT NULL) AND temp_rev_user.revactor_actor = '11'",
+ 'userid' => "temp_rev_user.revactor_actor IS NULL AND rev_user = '1'"
+ ],
+ 'joins' => [
+ 'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ ],
+ ],
+ ],
+ 'Revision, write-new' => [
+ MIGRATION_WRITE_NEW, 'rev_user', $genericUser, true, [
+ 'tables' => [
+ 'temp_rev_user' => 'revision_actor_temp',
+ ],
+ 'orconds' => [
+ 'actor' =>
+ "(temp_rev_user.revactor_actor IS NOT NULL) AND temp_rev_user.revactor_actor = '11'",
+ 'userid' => "temp_rev_user.revactor_actor IS NULL AND rev_user = '1'"
+ ],
+ 'joins' => [
+ 'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ ],
+ ],
+ ],
+ 'Revision, new' => [
+ MIGRATION_NEW, 'rev_user', $genericUser, true, [
+ 'tables' => [
+ 'temp_rev_user' => 'revision_actor_temp',
+ ],
+ 'orconds' => [ 'actor' => "temp_rev_user.revactor_actor = '11'" ],
+ 'joins' => [
+ 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ ],
+ ],
+ ],
+
+ 'Multiple users, old' => [
+ MIGRATION_OLD, 'rc_user', $complicatedUsers, true, [
+ 'tables' => [],
+ 'orconds' => [
+ 'userid' => "rc_user IN ('1','2','3') ",
+ 'username' => "rc_user_text IN ('192.168.12.34','192.168.12.35') "
+ ],
+ 'joins' => [],
+ ],
+ ],
+ 'Multiple users, write-both' => [
+ MIGRATION_WRITE_BOTH, 'rc_user', $complicatedUsers, true, [
+ 'tables' => [],
+ 'orconds' => [
+ 'actor' => "rc_actor IN ('11','12','34') ",
+ 'userid' => "rc_actor = '0' AND rc_user IN ('1','2','3') ",
+ 'username' => "rc_actor = '0' AND rc_user_text IN ('192.168.12.34','192.168.12.35') "
+ ],
+ 'joins' => [],
+ ],
+ ],
+ 'Multiple users, write-new' => [
+ MIGRATION_WRITE_NEW, 'rc_user', $complicatedUsers, true, [
+ 'tables' => [],
+ 'orconds' => [
+ 'actor' => "rc_actor IN ('11','12','34') ",
+ 'userid' => "rc_actor = '0' AND rc_user IN ('1','2','3') ",
+ 'username' => "rc_actor = '0' AND rc_user_text IN ('192.168.12.34','192.168.12.35') "
+ ],
+ 'joins' => [],
+ ],
+ ],
+ 'Multiple users, new' => [
+ MIGRATION_NEW, 'rc_user', $complicatedUsers, true, [
+ 'tables' => [],
+ 'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
+ 'joins' => [],
+ ],
+ ],
+
+ 'Multiple users, no use ID, old' => [
+ MIGRATION_OLD, 'rc_user', $complicatedUsers, false, [
+ 'tables' => [],
+ 'orconds' => [
+ 'username' => "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
+ ],
+ 'joins' => [],
+ ],
+ ],
+ 'Multiple users, write-both' => [
+ MIGRATION_WRITE_BOTH, 'rc_user', $complicatedUsers, false, [
+ 'tables' => [],
+ 'orconds' => [
+ 'actor' => "rc_actor IN ('11','12','34') ",
+ 'username' => "rc_actor = '0' AND "
+ . "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
+ ],
+ 'joins' => [],
+ ],
+ ],
+ 'Multiple users, write-new' => [
+ MIGRATION_WRITE_NEW, 'rc_user', $complicatedUsers, false, [
+ 'tables' => [],
+ 'orconds' => [
+ 'actor' => "rc_actor IN ('11','12','34') ",
+ 'username' => "rc_actor = '0' AND "
+ . "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
+ ],
+ 'joins' => [],
+ ],
+ ],
+ 'Multiple users, new' => [
+ MIGRATION_NEW, 'rc_user', $complicatedUsers, false, [
+ 'tables' => [],
+ 'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
+ 'joins' => [],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideInsertRoundTrip
+ * @param string $table
+ * @param string $key
+ * @param string $pk
+ * @param array $extraFields
+ */
+ public function testInsertRoundTrip( $table, $key, $pk, $extraFields ) {
+ $u = $this->getTestUser()->getUser();
+ $user = $this->getMock( UserIdentity::class );
+ $user->method( 'getId' )->willReturn( $u->getId() );
+ $user->method( 'getName' )->willReturn( $u->getName() );
+ if ( $u->getActorId( $this->db ) ) {
+ $user->method( 'getActorId' )->willReturn( $u->getActorId() );
+ } else {
+ $this->db->insert(
+ 'actor',
+ [ 'actor_user' => $u->getId(), 'actor_name' => $u->getName() ],
+ __METHOD__
+ );
+ $user->method( 'getActorId' )->willReturn( $this->db->insertId() );
+ }
+
+ $stages = [
+ MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_NEW ],
+ MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_NEW ],
+ MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
+ MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
+ ];
+
+ $nameKey = $key . '_text';
+ $actorKey = $key === 'ipb_by' ? 'ipb_by_actor' : substr( $key, 0, -5 ) . '_actor';
+
+ foreach ( $stages as $writeStage => $readRange ) {
+ if ( $key === 'ipb_by' ) {
+ $extraFields['ipb_address'] = __CLASS__ . "#$writeStage";
+ }
+
+ $w = $this->makeMigration( $writeStage );
+ $usesTemp = $key === 'rev_user';
+
+ if ( $usesTemp ) {
+ list( $fields, $callback ) = $w->getInsertValuesWithTempTable( $this->db, $key, $user );
+ } else {
+ $fields = $w->getInsertValues( $this->db, $key, $user );
+ }
+
+ if ( $writeStage <= MIGRATION_WRITE_BOTH ) {
+ $this->assertSame( $user->getId(), $fields[$key], "old field, stage=$writeStage" );
+ $this->assertSame( $user->getName(), $fields[$nameKey], "old field, stage=$writeStage" );
+ } else {
+ $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
+ $this->assertArrayNotHasKey( $nameKey, $fields, "old field, stage=$writeStage" );
+ }
+ if ( $writeStage >= MIGRATION_WRITE_BOTH && !$usesTemp ) {
+ $this->assertSame( $user->getActorId(), $fields[$actorKey], "new field, stage=$writeStage" );
+ } else {
+ $this->assertArrayNotHasKey( $actorKey, $fields, "new field, stage=$writeStage" );
+ }
+
+ $this->db->insert( $table, $extraFields + $fields, __METHOD__ );
+ $id = $this->db->insertId();
+ if ( $usesTemp ) {
+ $callback( $id, $extraFields );
+ }
+
+ for ( $readStage = $readRange[0]; $readStage <= $readRange[1]; $readStage++ ) {
+ $r = $this->makeMigration( $readStage );
+
+ $queryInfo = $r->getJoin( $key );
+ $row = $this->db->selectRow(
+ [ $table ] + $queryInfo['tables'],
+ $queryInfo['fields'],
+ [ $pk => $id ],
+ __METHOD__,
+ [],
+ $queryInfo['joins']
+ );
+
+ $this->assertSame( $user->getId(), (int)$row->$key, "w=$writeStage, r=$readStage, id" );
+ $this->assertSame( $user->getName(), $row->$nameKey, "w=$writeStage, r=$readStage, name" );
+ $this->assertSame(
+ $readStage === MIGRATION_OLD || $writeStage === MIGRATION_OLD ? 0 : $user->getActorId(),
+ (int)$row->$actorKey,
+ "w=$writeStage, r=$readStage, actor"
+ );
+ }
+ }
+ }
+
+ public static function provideInsertRoundTrip() {
+ $db = wfGetDB( DB_REPLICA ); // for timestamps
+
+ $ipbfields = [
+ ];
+ $revfields = [
+ ];
+
+ return [
+ 'recentchanges' => [ 'recentchanges', 'rc_user', 'rc_id', [
+ 'rc_timestamp' => $db->timestamp(),
+ 'rc_namespace' => 0,
+ 'rc_title' => 'Test',
+ 'rc_this_oldid' => 42,
+ 'rc_last_oldid' => 41,
+ 'rc_source' => 'test',
+ ] ],
+ 'ipblocks' => [ 'ipblocks', 'ipb_by', 'ipb_id', [
+ 'ipb_range_start' => '',
+ 'ipb_range_end' => '',
+ 'ipb_timestamp' => $db->timestamp(),
+ 'ipb_expiry' => $db->getInfinity(),
+ ] ],
+ 'revision' => [ 'revision', 'rev_user', 'rev_id', [
+ 'rev_page' => 42,
+ 'rev_text_id' => 42,
+ 'rev_len' => 0,
+ 'rev_timestamp' => $db->timestamp(),
+ ] ],
+ ];
+ }
+
+ public static function provideStages() {
+ return [
+ 'MIGRATION_OLD' => [ MIGRATION_OLD ],
+ 'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH ],
+ 'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW ],
+ 'MIGRATION_NEW' => [ MIGRATION_NEW ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideStages
+ * @param int $stage
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage Must use getInsertValuesWithTempTable() for rev_user
+ */
+ public function testInsertWrong( $stage ) {
+ $m = $this->makeMigration( $stage );
+ $m->getInsertValues( $this->db, 'rev_user', $this->getTestUser()->getUser() );
+ }
+
+ /**
+ * @dataProvider provideStages
+ * @param int $stage
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage Must use getInsertValues() for rc_user
+ */
+ public function testInsertWithTempTableWrong( $stage ) {
+ $m = $this->makeMigration( $stage );
+ $m->getInsertValuesWithTempTable( $this->db, 'rc_user', $this->getTestUser()->getUser() );
+ }
+
+ /**
+ * @dataProvider provideStages
+ * @param int $stage
+ */
+ public function testInsertWithTempTableDeprecated( $stage ) {
+ $wrap = TestingAccessWrapper::newFromClass( ActorMigration::class );
+ $wrap->formerTempTables += [ 'rc_user' => '1.30' ];
+
+ $this->hideDeprecated( 'ActorMigration::getInsertValuesWithTempTable for rc_user' );
+ $m = $this->makeMigration( $stage );
+ list( $fields, $callback )
+ = $m->getInsertValuesWithTempTable( $this->db, 'rc_user', $this->getTestUser()->getUser() );
+ $this->assertTrue( is_callable( $callback ) );
+ }
+
+ /**
+ * @dataProvider provideStages
+ * @param int $stage
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage $extra[rev_timestamp] is not provided
+ */
+ public function testInsertWithTempTableCallbackMissingFields( $stage ) {
+ $m = $this->makeMigration( $stage );
+ list( $fields, $callback )
+ = $m->getInsertValuesWithTempTable( $this->db, 'rev_user', $this->getTestUser()->getUser() );
+ $callback( 1, [] );
+ }
+
+ public function testInsertUserIdentity() {
+ $user = $this->getTestUser()->getUser();
+ $userIdentity = $this->getMock( UserIdentity::class );
+ $userIdentity->method( 'getId' )->willReturn( $user->getId() );
+ $userIdentity->method( 'getName' )->willReturn( $user->getName() );
+ $userIdentity->method( 'getActorId' )->willReturn( 0 );
+
+ list( $cFields, $cCallback ) = CommentStore::newKey( 'rev_comment' )
+ ->insertWithTempTable( $this->db, '' );
+ $m = $this->makeMigration( MIGRATION_WRITE_BOTH );
+ list( $fields, $callback ) =
+ $m->getInsertValuesWithTempTable( $this->db, 'rev_user', $userIdentity );
+ $extraFields = [
+ 'rev_page' => 42,
+ 'rev_text_id' => 42,
+ 'rev_len' => 0,
+ 'rev_timestamp' => $this->db->timestamp(),
+ ] + $cFields;
+ $this->db->insert( 'revision', $extraFields + $fields, __METHOD__ );
+ $id = $this->db->insertId();
+ $callback( $id, $extraFields );
+ $cCallback( $id );
+
+ $qi = Revision::getQueryInfo();
+ $row = $this->db->selectRow(
+ $qi['tables'], $qi['fields'], [ 'rev_id' => $id ], __METHOD__, [], $qi['joins']
+ );
+ $this->assertSame( $user->getId(), (int)$row->rev_user );
+ $this->assertSame( $user->getName(), $row->rev_user_text );
+ $this->assertSame( $user->getActorId(), (int)$row->rev_actor );
+
+ $m = $this->makeMigration( MIGRATION_WRITE_BOTH );
+ $fields = $m->getInsertValues( $this->db, 'dummy_user', $userIdentity );
+ $this->assertSame( $user->getId(), $fields['dummy_user'] );
+ $this->assertSame( $user->getName(), $fields['dummy_user_text'] );
+ $this->assertSame( $user->getActorId(), $fields['dummy_actor'] );
+ }
+
+ public function testConstructor() {
+ $m = ActorMigration::newMigration();
+ $this->assertInstanceOf( ActorMigration::class, $m );
+ $this->assertSame( $m, ActorMigration::newMigration() );
+ }
+
+ /**
+ * @dataProvider provideIsAnon
+ * @param int $stage
+ * @param string $isAnon
+ * @param string $isNotAnon
+ */
+ public function testIsAnon( $stage, $isAnon, $isNotAnon ) {
+ $m = $this->makeMigration( $stage );
+ $this->assertSame( $isAnon, $m->isAnon( 'foo' ) );
+ $this->assertSame( $isNotAnon, $m->isNotAnon( 'foo' ) );
+ }
+
+ public static function provideIsAnon() {
+ return [
+ 'MIGRATION_OLD' => [ MIGRATION_OLD, 'foo = 0', 'foo != 0' ],
+ 'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH, 'foo = 0', 'foo != 0' ],
+ 'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW, 'foo = 0', 'foo != 0' ],
+ 'MIGRATION_NEW' => [ MIGRATION_NEW, 'foo IS NULL', 'foo IS NOT NULL' ],
+ ];
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/AutopromoteTest.php b/www/wiki/tests/phpunit/includes/AutopromoteTest.php
index 785aa4e3..8c4a488e 100644
--- a/www/wiki/tests/phpunit/includes/AutopromoteTest.php
+++ b/www/wiki/tests/phpunit/includes/AutopromoteTest.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @covers Autopromote
+ */
class AutopromoteTest extends MediaWikiTestCase {
/**
* T157718: Verify Autopromote does not perform edit count lookup if requirement is 0 or invalid
@@ -17,7 +20,7 @@ class AutopromoteTest extends MediaWikiTestCase {
] );
/** @var PHPUnit_Framework_MockObject_MockObject|User $userMock */
- $userMock = $this->getMock( 'User', [ 'getEditCount' ] );
+ $userMock = $this->getMock( User::class, [ 'getEditCount' ] );
if ( $requirement > 0 ) {
$userMock->expects( $this->once() )
->method( 'getEditCount' )
diff --git a/www/wiki/tests/phpunit/includes/BlockTest.php b/www/wiki/tests/phpunit/includes/BlockTest.php
index 63d05a06..19780a68 100644
--- a/www/wiki/tests/phpunit/includes/BlockTest.php
+++ b/www/wiki/tests/phpunit/includes/BlockTest.php
@@ -6,69 +6,63 @@
*/
class BlockTest extends MediaWikiLangTestCase {
- /** @var Block */
- private $block;
- private $madeAt;
-
- /* variable used to save up the blockID we insert in this test suite */
- private $blockId;
-
- function addDBData() {
- $user = User::newFromName( 'UTBlockee' );
- if ( $user->getId() == 0 ) {
- $user->addToDatabase();
- TestUser::setPasswordForUser( $user, 'UTBlockeePassword' );
-
- $user->saveSettings();
- }
+ /**
+ * @return User
+ */
+ private function getUserForBlocking() {
+ $testUser = $this->getMutableTestUser();
+ $user = $testUser->getUser();
+ $user->addToDatabase();
+ TestUser::setPasswordForUser( $user, 'UTBlockeePassword' );
+ $user->saveSettings();
+ return $user;
+ }
+ /**
+ * @param User $user
+ *
+ * @return Block
+ * @throws MWException
+ */
+ private function addBlockForUser( User $user ) {
// Delete the last round's block if it's still there
- $oldBlock = Block::newFromTarget( 'UTBlockee' );
+ $oldBlock = Block::newFromTarget( $user->getName() );
if ( $oldBlock ) {
// An old block will prevent our new one from saving.
$oldBlock->delete();
}
$blockOptions = [
- 'address' => 'UTBlockee',
+ 'address' => $user->getName(),
'user' => $user->getId(),
+ 'by' => $this->getTestSysop()->getUser()->getId(),
'reason' => 'Parce que',
'expiry' => time() + 100500,
];
- $this->block = new Block( $blockOptions );
- $this->madeAt = wfTimestamp( TS_MW );
+ $block = new Block( $blockOptions );
- $this->block->insert();
+ $block->insert();
// save up ID for use in assertion. Since ID is an autoincrement,
// its value might change depending on the order the tests are run.
// ApiBlockTest insert its own blocks!
- $newBlockId = $this->block->getId();
- if ( $newBlockId ) {
- $this->blockId = $newBlockId;
- } else {
+ if ( !$block->getId() ) {
throw new MWException( "Failed to insert block for BlockTest; old leftover block remaining?" );
}
$this->addXffBlocks();
- }
- /**
- * debug function : dump the ipblocks table
- */
- function dumpBlocks() {
- $v = $this->db->select( 'ipblocks', '*' );
- print "Got " . $v->numRows() . " rows. Full dump follow:\n";
- foreach ( $v as $row ) {
- print_r( $row );
- }
+ return $block;
}
/**
* @covers Block::newFromTarget
*/
public function testINewFromTargetReturnsCorrectBlock() {
+ $user = $this->getUserForBlocking();
+ $block = $this->addBlockForUser( $user );
+
$this->assertTrue(
- $this->block->equals( Block::newFromTarget( 'UTBlockee' ) ),
+ $block->equals( Block::newFromTarget( $user->getName() ) ),
"newFromTarget() returns the same block as the one that was made"
);
}
@@ -77,18 +71,26 @@ class BlockTest extends MediaWikiLangTestCase {
* @covers Block::newFromID
*/
public function testINewFromIDReturnsCorrectBlock() {
+ $user = $this->getUserForBlocking();
+ $block = $this->addBlockForUser( $user );
+
$this->assertTrue(
- $this->block->equals( Block::newFromID( $this->blockId ) ),
+ $block->equals( Block::newFromID( $block->getId() ) ),
"newFromID() returns the same block as the one that was made"
);
}
/**
* per T28425
+ * @covers Block::__construct
*/
public function testBug26425BlockTimestampDefaultsToTime() {
+ $user = $this->getUserForBlocking();
+ $block = $this->addBlockForUser( $user );
+ $madeAt = wfTimestamp( TS_MW );
+
// delta to stop one-off errors when things happen to go over a second mark.
- $delta = abs( $this->madeAt - $this->block->mTimestamp );
+ $delta = abs( $madeAt - $block->mTimestamp );
$this->assertLessThan(
2,
$delta,
@@ -105,9 +107,12 @@ class BlockTest extends MediaWikiLangTestCase {
* @covers Block::newFromTarget
*/
public function testBug29116NewFromTargetWithEmptyIp( $vagueTarget ) {
- $block = Block::newFromTarget( 'UTBlockee', $vagueTarget );
+ $user = $this->getUserForBlocking();
+ $initialBlock = $this->addBlockForUser( $user );
+ $block = Block::newFromTarget( $user->getName(), $vagueTarget );
+
$this->assertTrue(
- $this->block->equals( $block ),
+ $initialBlock->equals( $block ),
"newFromTarget() returns the same block as the one that was made when "
. "given empty vagueTarget param " . var_export( $vagueTarget, true )
);
@@ -157,7 +162,7 @@ class BlockTest extends MediaWikiLangTestCase {
'enableAutoblock' => true,
'hideName' => true,
'blockEmail' => true,
- 'byText' => 'MetaWikiUser',
+ 'byText' => 'm>MetaWikiUser',
];
$block = new Block( $blockOptions );
$block->insert();
@@ -170,7 +175,7 @@ class BlockTest extends MediaWikiLangTestCase {
);
$this->assertInstanceOf(
- 'Block',
+ Block::class,
$userBlock,
"'$username' block block object should be existent"
);
@@ -211,7 +216,7 @@ class BlockTest extends MediaWikiLangTestCase {
'enableAutoblock' => true,
'hideName' => true,
'blockEmail' => true,
- 'byText' => 'MetaWikiUser',
+ 'byText' => 'Meta>MetaWikiUser',
];
$block = new Block( $blockOptions );
@@ -227,8 +232,9 @@ class BlockTest extends MediaWikiLangTestCase {
'Correct blockee name'
);
$this->assertEquals( $userId, $block->getTarget()->getId(), 'Correct blockee id' );
- $this->assertEquals( 'MetaWikiUser', $block->getBlocker(), 'Correct blocker name' );
- $this->assertEquals( 'MetaWikiUser', $block->getByName(), 'Correct blocker name' );
+ $this->assertEquals( 'Meta>MetaWikiUser', $block->getBlocker()->getName(),
+ 'Correct blocker name' );
+ $this->assertEquals( 'Meta>MetaWikiUser', $block->getByName(), 'Correct blocker name' );
$this->assertEquals( 0, $block->getBy(), 'Correct blocker id' );
}
@@ -279,6 +285,7 @@ class BlockTest extends MediaWikiLangTestCase {
],
];
+ $blocker = $this->getTestUser()->getUser();
foreach ( $blockList as $insBlock ) {
$target = $insBlock['target'];
@@ -290,7 +297,7 @@ class BlockTest extends MediaWikiLangTestCase {
$block = new Block();
$block->setTarget( $target );
- $block->setBlocker( 'testblocker@global' );
+ $block->setBlocker( $blocker );
$block->mReason = $insBlock['desc'];
$block->mExpiry = 'infinity';
$block->prevents( 'createaccount', $insBlock['ACDisable'] );
@@ -351,6 +358,9 @@ class BlockTest extends MediaWikiLangTestCase {
* @covers Block::chooseBlock
*/
public function testBlocksOnXff( $xff, $exCount, $exResult ) {
+ $user = $this->getUserForBlocking();
+ $this->addBlockForUser( $user );
+
$list = array_map( 'trim', explode( ',', $xff ) );
$xffblocks = Block::getBlocksForIPList( $list, true );
$this->assertEquals( $exCount, count( $xffblocks ), 'Number of blocks for ' . $xff );
@@ -358,6 +368,9 @@ class BlockTest extends MediaWikiLangTestCase {
$this->assertEquals( $exResult, $block->mReason, 'Correct block type for XFF header ' . $xff );
}
+ /**
+ * @covers Block::__construct
+ */
public function testDeprecatedConstructor() {
$this->hideDeprecated( 'Block::__construct with multiple arguments' );
$username = 'UnthinkablySecretRandomUsername';
@@ -381,7 +394,7 @@ class BlockTest extends MediaWikiLangTestCase {
$block = new Block(
/* address */ $username,
/* user */ 0,
- /* by */ 0,
+ /* by */ $this->getTestSysop()->getUser()->getId(),
/* reason */ $reason,
/* timestamp */ 0,
/* auto */ false,
@@ -410,13 +423,21 @@ class BlockTest extends MediaWikiLangTestCase {
);
}
+ /**
+ * @covers Block::getSystemBlockType
+ * @covers Block::insert
+ * @covers Block::doAutoblock
+ */
public function testSystemBlocks() {
+ $user = $this->getUserForBlocking();
+ $this->addBlockForUser( $user );
+
$blockOptions = [
- 'address' => 'UTBlockee',
+ 'address' => $user->getName(),
'reason' => 'test system block',
'timestamp' => wfTimestampNow(),
'expiry' => $this->db->getInfinity(),
- 'byText' => 'MetaWikiUser',
+ 'byText' => 'MediaWiki default',
'systemBlock' => 'test',
'enableAutoblock' => true,
];
@@ -438,4 +459,5 @@ class BlockTest extends MediaWikiLangTestCase {
$this->assertSame( 'Cannot autoblock from a system block', $ex->getMessage() );
}
}
+
}
diff --git a/www/wiki/tests/phpunit/includes/CollationTest.php b/www/wiki/tests/phpunit/includes/CollationTest.php
deleted file mode 100644
index bf283aae..00000000
--- a/www/wiki/tests/phpunit/includes/CollationTest.php
+++ /dev/null
@@ -1,117 +0,0 @@
-<?php
-
-/**
- * Class CollationTest
- * @covers Collation
- * @covers IcuCollation
- * @covers IdentityCollation
- * @covers UppercaseCollation
- */
-class CollationTest extends MediaWikiLangTestCase {
- protected function setUp() {
- parent::setUp();
- $this->checkPHPExtension( 'intl' );
- }
-
- /**
- * Test to make sure, that if you
- * have "X" and "XY", the binary
- * sortkey also has "X" being a
- * prefix of "XY". Our collation
- * code makes this assumption.
- *
- * @param string $lang Language code for collator
- * @param string $base Base string
- * @param string $extended String containing base as a prefix.
- *
- * @dataProvider prefixDataProvider
- */
- public function testIsPrefix( $lang, $base, $extended ) {
- $cp = Collator::create( $lang );
- $cp->setStrength( Collator::PRIMARY );
- $baseBin = $cp->getSortKey( $base );
- // Remove sortkey terminator
- $baseBin = rtrim( $baseBin, "\0" );
- $extendedBin = $cp->getSortKey( $extended );
- $this->assertStringStartsWith( $baseBin, $extendedBin, "$base is not a prefix of $extended" );
- }
-
- public static function prefixDataProvider() {
- return [
- [ 'en', 'A', 'AA' ],
- [ 'en', 'A', 'AAA' ],
- [ 'en', 'Д', 'ДЂ' ],
- [ 'en', 'Д', 'ДA' ],
- // 'Ʒ' should expand to 'Z ' (note space).
- [ 'fi', 'Z', 'Ʒ' ],
- // 'Þ' should expand to 'th'
- [ 'sv', 't', 'Þ' ],
- // Javanese is a limited use alphabet, so should have 3 bytes
- // per character, so do some tests with it.
- [ 'en', 'ꦲ', 'ꦲꦤ' ],
- [ 'en', 'ꦲ', 'ꦲД' ],
- [ 'en', 'A', 'Aꦲ' ],
- ];
- }
-
- /**
- * Opposite of testIsPrefix
- *
- * @dataProvider notPrefixDataProvider
- */
- public function testNotIsPrefix( $lang, $base, $extended ) {
- $cp = Collator::create( $lang );
- $cp->setStrength( Collator::PRIMARY );
- $baseBin = $cp->getSortKey( $base );
- // Remove sortkey terminator
- $baseBin = rtrim( $baseBin, "\0" );
- $extendedBin = $cp->getSortKey( $extended );
- $this->assertStringStartsNotWith( $baseBin, $extendedBin, "$base is a prefix of $extended" );
- }
-
- public static function notPrefixDataProvider() {
- return [
- [ 'en', 'A', 'B' ],
- [ 'en', 'AC', 'ABC' ],
- [ 'en', 'Z', 'Ʒ' ],
- [ 'en', 'A', 'ꦲ' ],
- ];
- }
-
- /**
- * Test correct first letter is fetched.
- *
- * @param string $collation Collation name (aka uca-en)
- * @param string $string String to get first letter of
- * @param string $firstLetter Expected first letter.
- *
- * @dataProvider firstLetterProvider
- */
- public function testGetFirstLetter( $collation, $string, $firstLetter ) {
- $col = Collation::factory( $collation );
- $this->assertEquals( $firstLetter, $col->getFirstLetter( $string ) );
- }
-
- function firstLetterProvider() {
- return [
- [ 'uppercase', 'Abc', 'A' ],
- [ 'uppercase', 'abc', 'A' ],
- [ 'identity', 'abc', 'a' ],
- [ 'uca-en', 'abc', 'A' ],
- [ 'uca-en', ' ', ' ' ],
- [ 'uca-en', 'Êveryone', 'E' ],
- [ 'uca-vi', 'Êveryone', 'Ê' ],
- // Make sure thorn is not a first letter.
- [ 'uca-sv', 'The', 'T' ],
- [ 'uca-sv', 'Å', 'Å' ],
- [ 'uca-hu', 'dzsdo', 'Dzs' ],
- [ 'uca-hu', 'dzdso', 'Dz' ],
- [ 'uca-hu', 'CSD', 'Cs' ],
- [ 'uca-root', 'CSD', 'C' ],
- [ 'uca-fi', 'Ǥ', 'G' ],
- [ 'uca-fi', 'Ŧ', 'T' ],
- [ 'uca-fi', 'Ʒ', 'Z' ],
- [ 'uca-fi', 'Ŋ', 'N' ],
- ];
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/CommentStoreTest.php b/www/wiki/tests/phpunit/includes/CommentStoreTest.php
index 9369f306..a5108976 100644
--- a/www/wiki/tests/phpunit/includes/CommentStoreTest.php
+++ b/www/wiki/tests/phpunit/includes/CommentStoreTest.php
@@ -20,11 +20,22 @@ class CommentStoreTest extends MediaWikiLangTestCase {
/**
* Create a store for a particular stage
* @param int $stage
+ * @return CommentStore
+ */
+ protected function makeStore( $stage ) {
+ global $wgContLang;
+ $store = new CommentStore( $wgContLang, $stage );
+ return $store;
+ }
+
+ /**
+ * Create a store for a particular stage and key (for testing deprecated behaviour)
+ * @param int $stage
* @param string $key
* @return CommentStore
*/
- protected function makeStore( $stage, $key ) {
- $store = new CommentStore( $key );
+ protected function makeStoreWithKey( $stage, $key ) {
+ $store = CommentStore::newKey( $key );
TestingAccessWrapper::newFromObject( $store )->stage = $stage;
return $store;
}
@@ -35,12 +46,24 @@ class CommentStoreTest extends MediaWikiLangTestCase {
* @param string $key
* @param array $expect
*/
- public function testGetFields( $stage, $key, $expect ) {
- $store = $this->makeStore( $stage, $key );
+ public function testGetFields_withKeyConstruction( $stage, $key, $expect ) {
+ $store = $this->makeStoreWithKey( $stage, $key );
$result = $store->getFields();
$this->assertEquals( $expect, $result );
}
+ /**
+ * @dataProvider provideGetFields
+ * @param int $stage
+ * @param string $key
+ * @param array $expect
+ */
+ public function testGetFields( $stage, $key, $expect ) {
+ $store = $this->makeStore( $stage );
+ $result = $store->getFields( $key );
+ $this->assertEquals( $expect, $result );
+ }
+
public static function provideGetFields() {
return [
'Simple table, old' => [
@@ -110,12 +133,24 @@ class CommentStoreTest extends MediaWikiLangTestCase {
* @param string $key
* @param array $expect
*/
- public function testGetJoin( $stage, $key, $expect ) {
- $store = $this->makeStore( $stage, $key );
+ public function testGetJoin_withKeyConstruction( $stage, $key, $expect ) {
+ $store = $this->makeStoreWithKey( $stage, $key );
$result = $store->getJoin();
$this->assertEquals( $expect, $result );
}
+ /**
+ * @dataProvider provideGetJoin
+ * @param int $stage
+ * @param string $key
+ * @param array $expect
+ */
+ public function testGetJoin( $stage, $key, $expect ) {
+ $store = $this->makeStore( $stage );
+ $result = $store->getJoin( $key );
+ $this->assertEquals( $expect, $result );
+ }
+
public static function provideGetJoin() {
return [
'Simple table, old' => [
@@ -343,11 +378,106 @@ class CommentStoreTest extends MediaWikiLangTestCase {
$extraFields['ipb_address'] = __CLASS__ . "#$writeStage";
}
- $wstore = $this->makeStore( $writeStage, $key );
+ $wstore = $this->makeStore( $writeStage );
$usesTemp = $key === 'rev_comment';
if ( $usesTemp ) {
- list( $fields, $callback ) = $wstore->insertWithTempTable( $this->db, $comment, $data );
+ list( $fields, $callback ) = $wstore->insertWithTempTable(
+ $this->db, $key, $comment, $data
+ );
+ } else {
+ $fields = $wstore->insert( $this->db, $key, $comment, $data );
+ }
+
+ if ( $writeStage <= MIGRATION_WRITE_BOTH ) {
+ $this->assertSame( $expect['text'], $fields[$key], "old field, stage=$writeStage" );
+ } else {
+ $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
+ }
+ if ( $writeStage >= MIGRATION_WRITE_BOTH && !$usesTemp ) {
+ $this->assertArrayHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
+ } else {
+ $this->assertArrayNotHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
+ }
+
+ $this->db->insert( $table, $extraFields + $fields, __METHOD__ );
+ $id = $this->db->insertId();
+ if ( $usesTemp ) {
+ $callback( $id );
+ }
+
+ for ( $readStage = $readRange[0]; $readStage <= $readRange[1]; $readStage++ ) {
+ $rstore = $this->makeStore( $readStage );
+
+ $fieldRow = $this->db->selectRow(
+ $table,
+ $rstore->getFields( $key ),
+ [ $pk => $id ],
+ __METHOD__
+ );
+
+ $queryInfo = $rstore->getJoin( $key );
+ $joinRow = $this->db->selectRow(
+ [ $table ] + $queryInfo['tables'],
+ $queryInfo['fields'],
+ [ $pk => $id ],
+ __METHOD__,
+ [],
+ $queryInfo['joins']
+ );
+
+ $this->assertComment(
+ $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
+ $rstore->getCommentLegacy( $this->db, $key, $fieldRow ),
+ "w=$writeStage, r=$readStage, from getFields()"
+ );
+ $this->assertComment(
+ $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
+ $rstore->getComment( $key, $joinRow ),
+ "w=$writeStage, r=$readStage, from getJoin()"
+ );
+ }
+ }
+ }
+
+ /**
+ * @dataProvider provideInsertRoundTrip
+ * @param string $table
+ * @param string $key
+ * @param string $pk
+ * @param string $extraFields
+ * @param string|Message $comment
+ * @param array|null $data
+ * @param array $expect
+ */
+ public function testInsertRoundTrip_withKeyConstruction(
+ $table, $key, $pk, $extraFields, $comment, $data, $expect
+ ) {
+ $expectOld = [
+ 'text' => $expect['text'],
+ 'message' => new RawMessage( '$1', [ $expect['text'] ] ),
+ 'data' => null,
+ ];
+
+ $stages = [
+ MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_NEW ],
+ MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_NEW ],
+ MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
+ MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
+ ];
+
+ foreach ( $stages as $writeStage => $readRange ) {
+ if ( $key === 'ipb_reason' ) {
+ $extraFields['ipb_address'] = __CLASS__ . "#$writeStage";
+ }
+
+ $wstore = $this->makeStoreWithKey( $writeStage, $key );
+ $usesTemp = $key === 'rev_comment';
+
+ if ( $usesTemp ) {
+ list( $fields, $callback ) = $wstore->insertWithTempTable(
+ $this->db, $comment, $data
+ );
} else {
$fields = $wstore->insert( $this->db, $comment, $data );
}
@@ -370,7 +500,7 @@ class CommentStoreTest extends MediaWikiLangTestCase {
}
for ( $readStage = $readRange[0]; $readStage <= $readRange[1]; $readStage++ ) {
- $rstore = $this->makeStore( $readStage, $key );
+ $rstore = $this->makeStoreWithKey( $readStage, $key );
$fieldRow = $this->db->selectRow(
$table,
@@ -412,7 +542,6 @@ class CommentStoreTest extends MediaWikiLangTestCase {
$ipbfields = [
'ipb_range_start' => '',
'ipb_range_end' => '',
- 'ipb_by' => 0,
'ipb_timestamp' => $db->timestamp(),
'ipb_expiry' => $db->getInfinity(),
];
@@ -420,8 +549,6 @@ class CommentStoreTest extends MediaWikiLangTestCase {
'rev_page' => 42,
'rev_text_id' => 42,
'rev_len' => 0,
- 'rev_user' => 0,
- 'rev_user_text' => '',
'rev_timestamp' => $db->timestamp(),
];
$comStoreComment = new CommentStoreComment(
@@ -518,26 +645,26 @@ class CommentStoreTest extends MediaWikiLangTestCase {
}
public function testGetCommentErrors() {
- MediaWiki\suppressWarnings();
- $reset = new ScopedCallback( 'MediaWiki\restoreWarnings' );
+ Wikimedia\suppressWarnings();
+ $reset = new ScopedCallback( 'Wikimedia\restoreWarnings' );
- $store = $this->makeStore( MIGRATION_OLD, 'dummy' );
- $res = $store->getComment( [ 'dummy' => 'comment' ] );
+ $store = $this->makeStore( MIGRATION_OLD );
+ $res = $store->getComment( 'dummy', [ 'dummy' => 'comment' ] );
$this->assertSame( '', $res->text );
- $res = $store->getComment( [ 'dummy' => 'comment' ], true );
+ $res = $store->getComment( 'dummy', [ 'dummy' => 'comment' ], true );
$this->assertSame( 'comment', $res->text );
- $store = $this->makeStore( MIGRATION_NEW, 'dummy' );
+ $store = $this->makeStore( MIGRATION_NEW );
try {
- $store->getComment( [ 'dummy' => 'comment' ] );
+ $store->getComment( 'dummy', [ 'dummy' => 'comment' ] );
$this->fail( 'Expected exception not thrown' );
} catch ( InvalidArgumentException $ex ) {
$this->assertSame( '$row does not contain fields needed for comment dummy', $ex->getMessage() );
}
- $res = $store->getComment( [ 'dummy' => 'comment' ], true );
+ $res = $store->getComment( 'dummy', [ 'dummy' => 'comment' ], true );
$this->assertSame( 'comment', $res->text );
try {
- $store->getComment( [ 'dummy_id' => 1 ] );
+ $store->getComment( 'dummy', [ 'dummy_id' => 1 ] );
$this->fail( 'Expected exception not thrown' );
} catch ( InvalidArgumentException $ex ) {
$this->assertSame(
@@ -547,19 +674,19 @@ class CommentStoreTest extends MediaWikiLangTestCase {
);
}
- $store = $this->makeStore( MIGRATION_NEW, 'rev_comment' );
+ $store = $this->makeStore( MIGRATION_NEW );
try {
- $store->getComment( [ 'rev_comment' => 'comment' ] );
+ $store->getComment( 'rev_comment', [ 'rev_comment' => 'comment' ] );
$this->fail( 'Expected exception not thrown' );
} catch ( InvalidArgumentException $ex ) {
$this->assertSame(
'$row does not contain fields needed for comment rev_comment', $ex->getMessage()
);
}
- $res = $store->getComment( [ 'rev_comment' => 'comment' ], true );
+ $res = $store->getComment( 'rev_comment', [ 'rev_comment' => 'comment' ], true );
$this->assertSame( 'comment', $res->text );
try {
- $store->getComment( [ 'rev_comment_pk' => 1 ] );
+ $store->getComment( 'rev_comment', [ 'rev_comment_pk' => 1 ] );
$this->fail( 'Expected exception not thrown' );
} catch ( InvalidArgumentException $ex ) {
$this->assertSame(
@@ -586,8 +713,8 @@ class CommentStoreTest extends MediaWikiLangTestCase {
* @expectedExceptionMessage Must use insertWithTempTable() for rev_comment
*/
public function testInsertWrong( $stage ) {
- $store = $this->makeStore( $stage, 'rev_comment' );
- $store->insert( $this->db, 'foo' );
+ $store = $this->makeStore( $stage );
+ $store->insert( $this->db, 'rev_comment', 'foo' );
}
/**
@@ -597,8 +724,8 @@ class CommentStoreTest extends MediaWikiLangTestCase {
* @expectedExceptionMessage Must use insert() for ipb_reason
*/
public function testInsertWithTempTableWrong( $stage ) {
- $store = $this->makeStore( $stage, 'ipb_reason' );
- $store->insertWithTempTable( $this->db, 'foo' );
+ $store = $this->makeStore( $stage );
+ $store->insertWithTempTable( $this->db, 'ipb_reason', 'foo' );
}
/**
@@ -610,8 +737,8 @@ class CommentStoreTest extends MediaWikiLangTestCase {
$wrap->formerTempTables += [ 'ipb_reason' => '1.30' ];
$this->hideDeprecated( 'CommentStore::insertWithTempTable for ipb_reason' );
- $store = $this->makeStore( $stage, 'ipb_reason' );
- list( $fields, $callback ) = $store->insertWithTempTable( $this->db, 'foo' );
+ $store = $this->makeStore( $stage );
+ list( $fields, $callback ) = $store->insertWithTempTable( $this->db, 'ipb_reason', 'foo' );
$this->assertTrue( is_callable( $callback ) );
}
@@ -620,8 +747,8 @@ class CommentStoreTest extends MediaWikiLangTestCase {
$truncated1 = str_repeat( '💣', 63 ) . '...';
$truncated2 = str_repeat( '💣', CommentStore::COMMENT_CHARACTER_LIMIT - 3 ) . '...';
- $store = $this->makeStore( MIGRATION_WRITE_BOTH, 'ipb_reason' );
- $fields = $store->insert( $this->db, $comment );
+ $store = $this->makeStore( MIGRATION_WRITE_BOTH );
+ $fields = $store->insert( $this->db, 'ipb_reason', $comment );
$this->assertSame( $truncated1, $fields['ipb_reason'] );
$stored = $this->db->selectField(
'comment', 'comment_text', [ 'comment_id' => $fields['ipb_reason_id'] ], __METHOD__
@@ -634,13 +761,17 @@ class CommentStoreTest extends MediaWikiLangTestCase {
* @expectedExceptionMessage Comment data is too long (65611 bytes, maximum is 65535)
*/
public function testInsertTooMuchData() {
- $store = $this->makeStore( MIGRATION_WRITE_BOTH, 'ipb_reason' );
- $store->insert( $this->db, 'foo', [
+ $store = $this->makeStore( MIGRATION_WRITE_BOTH );
+ $store->insert( $this->db, 'ipb_reason', 'foo', [
'long' => str_repeat( '💣', 16400 )
] );
}
- public function testConstructor() {
+ public function testGetStore() {
+ $this->assertInstanceOf( CommentStore::class, CommentStore::getStore() );
+ }
+
+ public function testNewKey() {
$this->assertInstanceOf( CommentStore::class, CommentStore::newKey( 'dummy' ) );
}
diff --git a/www/wiki/tests/phpunit/includes/EditPageTest.php b/www/wiki/tests/phpunit/includes/EditPageTest.php
index ad0d07a4..8f0826b5 100644
--- a/www/wiki/tests/phpunit/includes/EditPageTest.php
+++ b/www/wiki/tests/phpunit/includes/EditPageTest.php
@@ -29,10 +29,18 @@ class EditPageTest extends MediaWikiLangTestCase {
$wgNamespaceContentModels[12312] = "testing";
$wgContentHandlers["testing"] = 'DummyContentHandlerForTesting';
- MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ MWNamespace::clearCaches();
$wgContLang->resetNamespaces(); # reset namespace cache
}
+ protected function tearDown() {
+ global $wgContLang;
+
+ MWNamespace::clearCaches();
+ $wgContLang->resetNamespaces(); # reset namespace cache
+ parent::tearDown();
+ }
+
/**
* @dataProvider provideExtractSectionTitle
* @covers EditPage::extractSectionTitle
@@ -709,7 +717,7 @@ hello
$ep->importFormData( $req );
$this->setExpectedException(
- 'MWException',
+ MWException::class,
'This content model is not supported: testing'
);
diff --git a/www/wiki/tests/phpunit/includes/ExtraParserTest.php b/www/wiki/tests/phpunit/includes/ExtraParserTest.php
index a4e3bb94..75ebd31a 100644
--- a/www/wiki/tests/phpunit/includes/ExtraParserTest.php
+++ b/www/wiki/tests/phpunit/includes/ExtraParserTest.php
@@ -26,7 +26,6 @@ class ExtraParserTest extends MediaWikiTestCase {
// FIXME: This test should pass without setting global content language
$this->options = ParserOptions::newFromUserAndLang( new User, $contLang );
$this->options->setTemplateCallback( [ __CLASS__, 'statelessFetchTemplate' ] );
- $this->options->setWrapOutputClass( false );
$this->parser = new Parser;
MagicWord::clearCache();
@@ -41,9 +40,8 @@ class ExtraParserTest extends MediaWikiTestCase {
$title = Title::newFromText( 'Unit test' );
$options = ParserOptions::newFromUser( new User() );
- $options->setWrapOutputClass( false );
$this->assertEquals( "<p>$longLine</p>",
- $this->parser->parse( $longLine, $title, $options )->getText() );
+ $this->parser->parse( $longLine, $title, $options )->getText( [ 'unwrap' => true ] ) );
}
/**
@@ -55,7 +53,7 @@ class ExtraParserTest extends MediaWikiTestCase {
$parserOutput = $this->parser->parse( "Test\n{{Foo}}\n{{Bar}}", $title, $this->options );
$this->assertEquals(
"<p>Test\nContent of <i>Template:Foo</i>\nContent of <i>Template:Bar</i>\n</p>",
- $parserOutput->getText()
+ $parserOutput->getText( [ 'unwrap' => true ] )
);
}
@@ -193,7 +191,6 @@ class ExtraParserTest extends MediaWikiTestCase {
}
/**
- * @group Database
* @covers Parser::parse
*/
public function testTrackingCategory() {
@@ -207,7 +204,6 @@ class ExtraParserTest extends MediaWikiTestCase {
}
/**
- * @group Database
* @covers Parser::parse
*/
public function testTrackingCategorySpecial() {
diff --git a/www/wiki/tests/phpunit/includes/FauxRequestTest.php b/www/wiki/tests/phpunit/includes/FauxRequestTest.php
index 9fe694da..9e7d6802 100644
--- a/www/wiki/tests/phpunit/includes/FauxRequestTest.php
+++ b/www/wiki/tests/phpunit/includes/FauxRequestTest.php
@@ -2,7 +2,11 @@
use MediaWiki\Session\SessionManager;
-class FauxRequestTest extends PHPUnit_Framework_TestCase {
+class FauxRequestTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+ use PHPUnit4And6Compat;
+
/**
* @covers FauxRequest::__construct
*/
@@ -39,13 +43,19 @@ class FauxRequestTest extends PHPUnit_Framework_TestCase {
$this->assertEquals( '', $req->getText( 'z' ) );
}
- // Integration test for parent method.
+ /**
+ * Integration test for parent method
+ * @covers FauxRequest::getVal
+ */
public function testGetVal() {
$req = new FauxRequest( [ 'crlf' => "A\r\nb" ] );
$this->assertSame( "A\r\nb", $req->getVal( 'crlf' ), 'CRLF' );
}
- // Integration test for parent method.
+ /**
+ * Integration test for parent method
+ * @covers FauxRequest::getRawVal
+ */
public function testGetRawVal() {
$req = new FauxRequest( [
'x' => 'Value',
diff --git a/www/wiki/tests/phpunit/includes/GitInfoTest.php b/www/wiki/tests/phpunit/includes/GitInfoTest.php
index ae858f5d..1037b370 100644
--- a/www/wiki/tests/phpunit/includes/GitInfoTest.php
+++ b/www/wiki/tests/phpunit/includes/GitInfoTest.php
@@ -4,6 +4,30 @@
*/
class GitInfoTest extends MediaWikiTestCase {
+ public static function setUpBeforeClass() {
+ mkdir( __DIR__ . '/../data/gitrepo' );
+ mkdir( __DIR__ . '/../data/gitrepo/1' );
+ mkdir( __DIR__ . '/../data/gitrepo/2' );
+ mkdir( __DIR__ . '/../data/gitrepo/3' );
+ mkdir( __DIR__ . '/../data/gitrepo/1/.git' );
+ mkdir( __DIR__ . '/../data/gitrepo/1/.git/refs' );
+ mkdir( __DIR__ . '/../data/gitrepo/1/.git/refs/heads' );
+ file_put_contents( __DIR__ . '/../data/gitrepo/1/.git/HEAD',
+ "ref: refs/heads/master\n" );
+ file_put_contents( __DIR__ . '/../data/gitrepo/1/.git/refs/heads/master',
+ "0123456789012345678901234567890123abcdef\n" );
+ file_put_contents( __DIR__ . '/../data/gitrepo/1/.git/packed-refs',
+ "abcdef6789012345678901234567890123456789 refs/heads/master\n" );
+ file_put_contents( __DIR__ . '/../data/gitrepo/2/.git',
+ "gitdir: ../1/.git\n" );
+ file_put_contents( __DIR__ . '/../data/gitrepo/3/.git',
+ 'gitdir: ' . __DIR__ . "/../data/gitrepo/1/.git\n" );
+ }
+
+ public static function tearDownAfterClass() {
+ wfRecursiveRemoveDir( __DIR__ . '/../data/gitrepo' );
+ }
+
protected function setUp() {
parent::setUp();
$this->setMwGlobals( 'wgGitInfoCacheDirectory', __DIR__ . '/../data/gitinfo' );
@@ -43,4 +67,36 @@ class GitInfoTest extends MediaWikiTestCase {
$this->assertTrue( $fixture->cacheIsComplete() );
}
+ public function testReadingHead() {
+ $dir = __DIR__ . '/../data/gitrepo/1';
+ $fixture = new GitInfo( $dir );
+
+ $this->assertEquals( 'refs/heads/master', $fixture->getHead() );
+ $this->assertEquals( '0123456789012345678901234567890123abcdef', $fixture->getHeadSHA1() );
+ }
+
+ public function testIndirection() {
+ $dir = __DIR__ . '/../data/gitrepo/2';
+ $fixture = new GitInfo( $dir );
+
+ $this->assertEquals( 'refs/heads/master', $fixture->getHead() );
+ $this->assertEquals( '0123456789012345678901234567890123abcdef', $fixture->getHeadSHA1() );
+ }
+
+ public function testIndirection2() {
+ $dir = __DIR__ . '/../data/gitrepo/3';
+ $fixture = new GitInfo( $dir );
+
+ $this->assertEquals( 'refs/heads/master', $fixture->getHead() );
+ $this->assertEquals( '0123456789012345678901234567890123abcdef', $fixture->getHeadSHA1() );
+ }
+
+ public function testReadingPackedRefs() {
+ $dir = __DIR__ . '/../data/gitrepo/1';
+ unlink( __DIR__ . '/../data/gitrepo/1/.git/refs/heads/master' );
+ $fixture = new GitInfo( $dir );
+
+ $this->assertEquals( 'refs/heads/master', $fixture->getHead() );
+ $this->assertEquals( 'abcdef6789012345678901234567890123456789', $fixture->getHeadSHA1() );
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/GlobalFunctions/GlobalTest.php b/www/wiki/tests/phpunit/includes/GlobalFunctions/GlobalTest.php
index 5e54b8d4..ee4819fa 100644
--- a/www/wiki/tests/phpunit/includes/GlobalFunctions/GlobalTest.php
+++ b/www/wiki/tests/phpunit/includes/GlobalFunctions/GlobalTest.php
@@ -474,25 +474,45 @@ class GlobalTest extends MediaWikiTestCase {
}
/**
+ * @covers ::wfMerge
+ */
+ public function testMerge_worksWithLessParameters() {
+ $this->markTestSkippedIfNoDiff3();
+
+ $mergedText = null;
+ $successfulMerge = wfMerge( "old1\n\nold2", "old1\n\nnew2", "new1\n\nold2", $mergedText );
+
+ $mergedText = null;
+ $conflictingMerge = wfMerge( 'old', 'old and mine', 'old and yours', $mergedText );
+
+ $this->assertEquals( true, $successfulMerge );
+ $this->assertEquals( false, $conflictingMerge );
+ }
+
+ /**
* @param string $old Text as it was in the database
* @param string $mine Text submitted while user was editing
* @param string $yours Text submitted by the user
* @param bool $expectedMergeResult Whether the merge should be a success
* @param string $expectedText Text after merge has been completed
+ * @param string $expectedMergeAttemptResult Diff3 output if conflicts occur
*
* @dataProvider provideMerge()
* @group medium
* @covers ::wfMerge
*/
- public function testMerge( $old, $mine, $yours, $expectedMergeResult, $expectedText ) {
+ public function testMerge( $old, $mine, $yours, $expectedMergeResult, $expectedText,
+ $expectedMergeAttemptResult ) {
$this->markTestSkippedIfNoDiff3();
$mergedText = null;
- $isMerged = wfMerge( $old, $mine, $yours, $mergedText );
+ $attemptMergeResult = null;
+ $isMerged = wfMerge( $old, $mine, $yours, $mergedText, $mergeAttemptResult );
$msg = 'Merge should be a ';
$msg .= $expectedMergeResult ? 'success' : 'failure';
$this->assertEquals( $expectedMergeResult, $isMerged, $msg );
+ $this->assertEquals( $expectedMergeAttemptResult, $mergeAttemptResult );
if ( $isMerged ) {
// Verify the merged text
@@ -530,6 +550,9 @@ class GlobalTest extends MediaWikiTestCase {
"one one one ONE ONE\n" .
"\n" .
"two two TWO TWO\n", // note: will always end in a newline
+
+ // mergeAttemptResult:
+ "",
],
// #1: conflict, fail
@@ -552,6 +575,13 @@ class GlobalTest extends MediaWikiTestCase {
// result:
null,
+
+ // mergeAttemptResult:
+ "1,3c\n" .
+ "one one one\n" .
+ "\n" .
+ "two two\n" .
+ ".\n",
],
];
}
@@ -681,9 +711,9 @@ class GlobalTest extends MediaWikiTestCase {
public function testWfMkdirParents() {
// Should not return true if file exists instead of directory
$fname = $this->getNewTempFile();
- MediaWiki\suppressWarnings();
+ Wikimedia\suppressWarnings();
$ok = wfMkdirParents( $fname );
- MediaWiki\restoreWarnings();
+ Wikimedia\restoreWarnings();
$this->assertFalse( $ok );
}
@@ -722,6 +752,9 @@ class GlobalTest extends MediaWikiTestCase {
);
}
+ /**
+ * @covers ::wfMemcKey
+ */
public function testWfMemcKey() {
$cache = ObjectCache::getLocalClusterInstance();
$this->assertEquals(
@@ -730,6 +763,9 @@ class GlobalTest extends MediaWikiTestCase {
);
}
+ /**
+ * @covers ::wfForeignMemcKey
+ */
public function testWfForeignMemcKey() {
$cache = ObjectCache::getLocalClusterInstance();
$keyspace = $this->readAttribute( $cache, 'keyspace' );
@@ -739,6 +775,9 @@ class GlobalTest extends MediaWikiTestCase {
);
}
+ /**
+ * @covers ::wfGlobalCacheKey
+ */
public function testWfGlobalCacheKey() {
$cache = ObjectCache::getLocalClusterInstance();
$this->assertEquals(
diff --git a/www/wiki/tests/phpunit/includes/GlobalFunctions/wfArrayFilterTest.php b/www/wiki/tests/phpunit/includes/GlobalFunctions/wfArrayFilterTest.php
index 388aee79..1011a37c 100644
--- a/www/wiki/tests/phpunit/includes/GlobalFunctions/wfArrayFilterTest.php
+++ b/www/wiki/tests/phpunit/includes/GlobalFunctions/wfArrayFilterTest.php
@@ -1,6 +1,11 @@
<?php
-class WfArrayFilterTest extends \PHPUnit_Framework_TestCase {
+/**
+ * @group GlobalFunctions
+ * @covers ::wfArrayFilter
+ * @covers ::wfArrayFilterByKey
+ */
+class WfArrayFilterTest extends \PHPUnit\Framework\TestCase {
public function testWfArrayFilter() {
$arr = [ 'a' => 1, 'b' => 2, 'c' => 3 ];
$filtered = wfArrayFilter( $arr, function ( $val, $key ) {
diff --git a/www/wiki/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php b/www/wiki/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php
deleted file mode 100644
index 8fbca6cf..00000000
--- a/www/wiki/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php
+++ /dev/null
@@ -1,121 +0,0 @@
-<?php
-/**
- * @group GlobalFunctions
- * @covers ::wfBCP47
- */
-class WfBCP47Test extends MediaWikiTestCase {
- /**
- * test @see wfBCP47().
- * Please note the BCP 47 explicitly state that language codes are case
- * insensitive, there are some exceptions to the rule :)
- * This test is used to verify our formatting against all lower and
- * all upper cases language code.
- *
- * @see https://tools.ietf.org/html/bcp47
- * @dataProvider provideLanguageCodes()
- */
- public function testBCP47( $code, $expected ) {
- $code = strtolower( $code );
- $this->assertEquals( $expected, wfBCP47( $code ),
- "Applying BCP47 standard to lower case '$code'"
- );
-
- $code = strtoupper( $code );
- $this->assertEquals( $expected, wfBCP47( $code ),
- "Applying BCP47 standard to upper case '$code'"
- );
- }
-
- /**
- * Array format is ($code, $expected)
- */
- public static function provideLanguageCodes() {
- return [
- // Extracted from BCP 47 (list not exhaustive)
- # 2.1.1
- [ 'en-ca-x-ca', 'en-CA-x-ca' ],
- [ 'sgn-be-fr', 'sgn-BE-FR' ],
- [ 'az-latn-x-latn', 'az-Latn-x-latn' ],
- # 2.2
- [ 'sr-Latn-RS', 'sr-Latn-RS' ],
- [ 'az-arab-ir', 'az-Arab-IR' ],
-
- # 2.2.5
- [ 'sl-nedis', 'sl-nedis' ],
- [ 'de-ch-1996', 'de-CH-1996' ],
-
- # 2.2.6
- [
- 'en-latn-gb-boont-r-extended-sequence-x-private',
- 'en-Latn-GB-boont-r-extended-sequence-x-private'
- ],
-
- // Examples from BCP 47 Appendix A
- # Simple language subtag:
- [ 'DE', 'de' ],
- [ 'fR', 'fr' ],
- [ 'ja', 'ja' ],
-
- # Language subtag plus script subtag:
- [ 'zh-hans', 'zh-Hans' ],
- [ 'sr-cyrl', 'sr-Cyrl' ],
- [ 'sr-latn', 'sr-Latn' ],
-
- # Extended language subtags and their primary language subtag
- # counterparts:
- [ 'zh-cmn-hans-cn', 'zh-cmn-Hans-CN' ],
- [ 'cmn-hans-cn', 'cmn-Hans-CN' ],
- [ 'zh-yue-hk', 'zh-yue-HK' ],
- [ 'yue-hk', 'yue-HK' ],
-
- # Language-Script-Region:
- [ 'zh-hans-cn', 'zh-Hans-CN' ],
- [ 'sr-latn-RS', 'sr-Latn-RS' ],
-
- # Language-Variant:
- [ 'sl-rozaj', 'sl-rozaj' ],
- [ 'sl-rozaj-biske', 'sl-rozaj-biske' ],
- [ 'sl-nedis', 'sl-nedis' ],
-
- # Language-Region-Variant:
- [ 'de-ch-1901', 'de-CH-1901' ],
- [ 'sl-it-nedis', 'sl-IT-nedis' ],
-
- # Language-Script-Region-Variant:
- [ 'hy-latn-it-arevela', 'hy-Latn-IT-arevela' ],
-
- # Language-Region:
- [ 'de-de', 'de-DE' ],
- [ 'en-us', 'en-US' ],
- [ 'es-419', 'es-419' ],
-
- # Private use subtags:
- [ 'de-ch-x-phonebk', 'de-CH-x-phonebk' ],
- [ 'az-arab-x-aze-derbend', 'az-Arab-x-aze-derbend' ],
- /**
- * Previous test does not reflect the BCP 47 which states:
- * az-Arab-x-AZE-derbend
- * AZE being private, it should be lower case, hence the test above
- * should probably be:
- * [ 'az-arab-x-aze-derbend', 'az-Arab-x-AZE-derbend' ],
- */
-
- # Private use registry values:
- [ 'x-whatever', 'x-whatever' ],
- [ 'qaa-qaaa-qm-x-southern', 'qaa-Qaaa-QM-x-southern' ],
- [ 'de-qaaa', 'de-Qaaa' ],
- [ 'sr-latn-qm', 'sr-Latn-QM' ],
- [ 'sr-qaaa-rs', 'sr-Qaaa-RS' ],
-
- # Tags that use extensions
- [ 'en-us-u-islamcal', 'en-US-u-islamcal' ],
- [ 'zh-cn-a-myext-x-private', 'zh-CN-a-myext-x-private' ],
- [ 'en-a-myext-b-another', 'en-a-myext-b-another' ],
-
- # Invalid:
- // de-419-DE
- // a-DE
- // ar-a-aaa-b-bbb-a-ccc
- ];
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/GlobalFunctions/wfStringToBoolTest.php b/www/wiki/tests/phpunit/includes/GlobalFunctions/wfStringToBoolTest.php
new file mode 100644
index 00000000..7f56b605
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/GlobalFunctions/wfStringToBoolTest.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfStringToBool
+ */
+class WfStringToBoolTest extends MediaWikiTestCase {
+
+ public function getTestCases() {
+ return [
+ [ 'true', true ],
+ [ 'on', true ],
+ [ 'yes', true ],
+ [ 'TRUE', true ],
+ [ 'YeS', true ],
+ [ 'On', true ],
+ [ '1', true ],
+ [ '+1', true ],
+ [ '01', true ],
+ [ '-001', true ],
+ [ ' 1', true ],
+ [ '-1 ', true ],
+ [ '', false ],
+ [ '0', false ],
+ [ 'false', false ],
+ [ 'NO', false ],
+ [ 'NOT', false ],
+ [ 'never', false ],
+ [ '!&', false ],
+ [ '-0', false ],
+ [ '+0', false ],
+ [ 'forget about it', false ],
+ [ ' on', false ],
+ [ 'true ', false ],
+ ];
+ }
+
+ /**
+ * @dataProvider getTestCases
+ * @param string $str
+ * @param bool $bool
+ */
+ public function testStr2Bool( $str, $bool ) {
+ if ( $bool ) {
+ $this->assertTrue( wfStringToBool( $str ) );
+ } else {
+ $this->assertFalse( wfStringToBool( $str ) );
+ }
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/HooksTest.php b/www/wiki/tests/phpunit/includes/HooksTest.php
index 87acb52e..efe92ec4 100644
--- a/www/wiki/tests/phpunit/includes/HooksTest.php
+++ b/www/wiki/tests/phpunit/includes/HooksTest.php
@@ -54,6 +54,8 @@ class HooksTest extends MediaWikiTestCase {
*/
public function testOldStyleHooks( $msg, array $hook, $expectedFoo, $expectedBar ) {
global $wgHooks;
+
+ $this->hideDeprecated( 'wfRunHooks' );
$foo = $bar = 'original';
$wgHooks['MediaWikiHooksTest001'][] = $hook;
@@ -67,6 +69,7 @@ class HooksTest extends MediaWikiTestCase {
* @dataProvider provideHooks
* @covers Hooks::register
* @covers Hooks::run
+ * @covers Hooks::callHook
*/
public function testNewStyleHooks( $msg, $hook, $expectedFoo, $expectedBar ) {
$foo = $bar = 'original';
@@ -83,6 +86,7 @@ class HooksTest extends MediaWikiTestCase {
* @covers Hooks::register
* @covers Hooks::getHandlers
* @covers Hooks::run
+ * @covers Hooks::callHook
*/
public function testNewStyleHookInteraction() {
global $wgHooks;
@@ -122,6 +126,7 @@ class HooksTest extends MediaWikiTestCase {
/**
* @expectedException MWException
* @covers Hooks::run
+ * @covers Hooks::callHook
*/
public function testUncallableFunction() {
Hooks::register( 'MediaWikiHooksTest001', 'ThisFunctionDoesntExist' );
@@ -130,6 +135,7 @@ class HooksTest extends MediaWikiTestCase {
/**
* @covers Hooks::run
+ * @covers Hooks::callHook
*/
public function testFalseReturn() {
Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) {
@@ -147,6 +153,7 @@ class HooksTest extends MediaWikiTestCase {
/**
* @covers Hooks::runWithoutAbort
+ * @covers Hooks::callHook
*/
public function testRunWithoutAbort() {
$list = [];
@@ -169,6 +176,7 @@ class HooksTest extends MediaWikiTestCase {
/**
* @covers Hooks::runWithoutAbort
+ * @covers Hooks::callHook
*/
public function testRunWithoutAbortWarning() {
Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) {
diff --git a/www/wiki/tests/phpunit/includes/HtmlTest.php b/www/wiki/tests/phpunit/includes/HtmlTest.php
index f3d49161..6695fce3 100644
--- a/www/wiki/tests/phpunit/includes/HtmlTest.php
+++ b/www/wiki/tests/phpunit/includes/HtmlTest.php
@@ -1,5 +1,4 @@
<?php
-/** tests for includes/Html.php */
class HtmlTest extends MediaWikiTestCase {
@@ -386,6 +385,9 @@ class HtmlTest extends MediaWikiTestCase {
);
}
+ /**
+ * @covers Html::namespaceSelector
+ */
public function testCanFilterOutNamespaces() {
$this->assertEquals(
'<select id="namespace" name="namespace">' . "\n" .
@@ -408,6 +410,9 @@ class HtmlTest extends MediaWikiTestCase {
);
}
+ /**
+ * @covers Html::namespaceSelector
+ */
public function testCanDisableANamespaces() {
$this->assertEquals(
'<select id="namespace" name="namespace">' . "\n" .
@@ -448,6 +453,47 @@ class HtmlTest extends MediaWikiTestCase {
}
/**
+ * @covers Html::warningBox
+ * @covers Html::messageBox
+ */
+ public function testWarningBox() {
+ $this->assertEquals(
+ Html::warningBox( 'warn' ),
+ '<div class="warningbox">warn</div>'
+ );
+ }
+
+ /**
+ * @covers Html::errorBox
+ * @covers Html::messageBox
+ */
+ public function testErrorBox() {
+ $this->assertEquals(
+ Html::errorBox( 'err' ),
+ '<div class="errorbox">err</div>'
+ );
+ $this->assertEquals(
+ Html::errorBox( 'err', 'heading' ),
+ '<div class="errorbox"><h2>heading</h2>err</div>'
+ );
+ }
+
+ /**
+ * @covers Html::successBox
+ * @covers Html::messageBox
+ */
+ public function testSuccessBox() {
+ $this->assertEquals(
+ Html::successBox( 'great' ),
+ '<div class="successbox">great</div>'
+ );
+ $this->assertEquals(
+ Html::successBox( '<script>beware no escaping!</script>' ),
+ '<div class="successbox"><script>beware no escaping!</script></div>'
+ );
+ }
+
+ /**
* List of input element types values introduced by HTML5
* Full list at https://www.w3.org/TR/html-markup/input.html
*/
@@ -637,6 +683,9 @@ class HtmlTest extends MediaWikiTestCase {
return $ret;
}
+ /**
+ * @covers Html::input
+ */
public function testWrapperInput() {
$this->assertEquals(
'<input type="radio" value="testval" name="testname"/>',
@@ -650,6 +699,9 @@ class HtmlTest extends MediaWikiTestCase {
);
}
+ /**
+ * @covers Html::check
+ */
public function testWrapperCheck() {
$this->assertEquals(
'<input type="checkbox" value="1" name="testname"/>',
@@ -668,6 +720,9 @@ class HtmlTest extends MediaWikiTestCase {
);
}
+ /**
+ * @covers Html::radio
+ */
public function testWrapperRadio() {
$this->assertEquals(
'<input type="radio" value="1" name="testname"/>',
@@ -686,6 +741,9 @@ class HtmlTest extends MediaWikiTestCase {
);
}
+ /**
+ * @covers Html::label
+ */
public function testWrapperLabel() {
$this->assertEquals(
'<label for="testid">testlabel</label>',
diff --git a/www/wiki/tests/phpunit/includes/HttpTest.php b/www/wiki/tests/phpunit/includes/HttpTest.php
deleted file mode 100644
index 4c2e02be..00000000
--- a/www/wiki/tests/phpunit/includes/HttpTest.php
+++ /dev/null
@@ -1,534 +0,0 @@
-<?php
-
-/**
- * @group Http
- */
-class HttpTest extends MediaWikiTestCase {
- /**
- * @dataProvider cookieDomains
- * @covers Cookie::validateCookieDomain
- */
- public function testValidateCookieDomain( $expected, $domain, $origin = null ) {
- if ( $origin ) {
- $ok = Cookie::validateCookieDomain( $domain, $origin );
- $msg = "$domain against origin $origin";
- } else {
- $ok = Cookie::validateCookieDomain( $domain );
- $msg = "$domain";
- }
- $this->assertEquals( $expected, $ok, $msg );
- }
-
- public static function cookieDomains() {
- return [
- [ false, "org" ],
- [ false, ".org" ],
- [ true, "wikipedia.org" ],
- [ true, ".wikipedia.org" ],
- [ false, "co.uk" ],
- [ false, ".co.uk" ],
- [ false, "gov.uk" ],
- [ false, ".gov.uk" ],
- [ true, "supermarket.uk" ],
- [ false, "uk" ],
- [ false, ".uk" ],
- [ false, "127.0.0." ],
- [ false, "127." ],
- [ false, "127.0.0.1." ],
- [ true, "127.0.0.1" ],
- [ false, "333.0.0.1" ],
- [ true, "example.com" ],
- [ false, "example.com." ],
- [ true, ".example.com" ],
-
- [ true, ".example.com", "www.example.com" ],
- [ false, "example.com", "www.example.com" ],
- [ true, "127.0.0.1", "127.0.0.1" ],
- [ false, "127.0.0.1", "localhost" ],
- ];
- }
-
- /**
- * Test Http::isValidURI()
- * @bug 27854 : Http::isValidURI is too lax
- * @dataProvider provideURI
- * @covers Http::isValidURI
- */
- public function testIsValidUri( $expect, $URI, $message = '' ) {
- $this->assertEquals(
- $expect,
- (bool)Http::isValidURI( $URI ),
- $message
- );
- }
-
- /**
- * @covers Http::getProxy
- */
- public function testGetProxy() {
- $this->setMwGlobals( 'wgHTTPProxy', 'proxy.domain.tld' );
- $this->assertEquals(
- 'proxy.domain.tld',
- Http::getProxy()
- );
- }
-
- /**
- * Feeds URI to test a long regular expression in Http::isValidURI
- */
- public static function provideURI() {
- /** Format: 'boolean expectation', 'URI to test', 'Optional message' */
- return [
- [ false, '¿non sens before!! http://a', 'Allow anything before URI' ],
-
- # (http|https) - only two schemes allowed
- [ true, 'http://www.example.org/' ],
- [ true, 'https://www.example.org/' ],
- [ true, 'http://www.example.org', 'URI without directory' ],
- [ true, 'http://a', 'Short name' ],
- [ true, 'http://étoile', 'Allow UTF-8 in hostname' ], # 'étoile' is french for 'star'
- [ false, '\\host\directory', 'CIFS share' ],
- [ false, 'gopher://host/dir', 'Reject gopher scheme' ],
- [ false, 'telnet://host', 'Reject telnet scheme' ],
-
- # :\/\/ - double slashes
- [ false, 'http//example.org', 'Reject missing colon in protocol' ],
- [ false, 'http:/example.org', 'Reject missing slash in protocol' ],
- [ false, 'http:example.org', 'Must have two slashes' ],
- # Following fail since hostname can be made of anything
- [ false, 'http:///example.org', 'Must have exactly two slashes, not three' ],
-
- # (\w+:{0,1}\w*@)? - optional user:pass
- [ true, 'http://user@host', 'Username provided' ],
- [ true, 'http://user:@host', 'Username provided, no password' ],
- [ true, 'http://user:pass@host', 'Username and password provided' ],
-
- # (\S+) - host part is made of anything not whitespaces
- // commented these out in order to remove @group Broken
- // @todo are these valid tests? if so, fix Http::isValidURI so it can handle them
- // array( false, 'http://!"èèè¿¿¿~~\'', 'hostname is made of any non whitespace' ),
- // array( false, 'http://exam:ple.org/', 'hostname can not use colons!' ),
-
- # (:[0-9]+)? - port number
- [ true, 'http://example.org:80/' ],
- [ true, 'https://example.org:80/' ],
- [ true, 'http://example.org:443/' ],
- [ true, 'https://example.org:443/' ],
-
- # Part after the hostname is / or / with something else
- [ true, 'http://example/#' ],
- [ true, 'http://example/!' ],
- [ true, 'http://example/:' ],
- [ true, 'http://example/.' ],
- [ true, 'http://example/?' ],
- [ true, 'http://example/+' ],
- [ true, 'http://example/=' ],
- [ true, 'http://example/&' ],
- [ true, 'http://example/%' ],
- [ true, 'http://example/@' ],
- [ true, 'http://example/-' ],
- [ true, 'http://example//' ],
- [ true, 'http://example/&' ],
-
- # Fragment
- [ true, 'http://exam#ple.org', ], # This one is valid, really!
- [ true, 'http://example.org:80#anchor' ],
- [ true, 'http://example.org/?id#anchor' ],
- [ true, 'http://example.org/?#anchor' ],
-
- [ false, 'http://a ¿non !!sens after', 'Allow anything after URI' ],
- ];
- }
-
- /**
- * Warning:
- *
- * These tests are for code that makes use of an artifact of how CURL
- * handles header reporting on redirect pages, and will need to be
- * rewritten when bug 29232 is taken care of (high-level handling of
- * HTTP redirects).
- */
- public function testRelativeRedirections() {
- $h = MWHttpRequestTester::factory( 'http://oldsite/file.ext', [], __METHOD__ );
-
- # Forge a Location header
- $h->setRespHeaders( 'location', [
- 'http://newsite/file.ext',
- '/newfile.ext',
- ]
- );
- # Verify we correctly fix the Location
- $this->assertEquals(
- 'http://newsite/newfile.ext',
- $h->getFinalUrl(),
- "Relative file path Location: interpreted as full URL"
- );
-
- $h->setRespHeaders( 'location', [
- 'https://oldsite/file.ext'
- ]
- );
- $this->assertEquals(
- 'https://oldsite/file.ext',
- $h->getFinalUrl(),
- "Location to the HTTPS version of the site"
- );
-
- $h->setRespHeaders( 'location', [
- '/anotherfile.ext',
- 'http://anotherfile/hoster.ext',
- 'https://anotherfile/hoster.ext'
- ]
- );
- $this->assertEquals(
- 'https://anotherfile/hoster.ext',
- $h->getFinalUrl( "Relative file path Location: should keep the latest host and scheme!" )
- );
- }
-
- /**
- * Constant values are from PHP 5.3.28 using cURL 7.24.0
- * @see http://php.net/manual/en/curl.constants.php
- *
- * All constant values are present so that developers don’t need to remember
- * to add them if added at a later date. The commented out constants were
- * not found anywhere in the MediaWiki core code.
- *
- * Commented out constants that were not available in:
- * HipHop VM 3.3.0 (rel)
- * Compiler: heads/master-0-g08810d920dfff59e0774cf2d651f92f13a637175
- * Repo schema: 3214fc2c684a4520485f715ee45f33f2182324b1
- * Extension API: 20140829
- *
- * Commented out constants that were removed in PHP 5.6.0
- *
- * @covers CurlHttpRequest::execute
- */
- public function provideCurlConstants() {
- return [
- [ 'CURLAUTH_ANY' ],
- [ 'CURLAUTH_ANYSAFE' ],
- [ 'CURLAUTH_BASIC' ],
- [ 'CURLAUTH_DIGEST' ],
- [ 'CURLAUTH_GSSNEGOTIATE' ],
- [ 'CURLAUTH_NTLM' ],
- // array( 'CURLCLOSEPOLICY_CALLBACK' ), // removed in PHP 5.6.0
- // array( 'CURLCLOSEPOLICY_LEAST_RECENTLY_USED' ), // removed in PHP 5.6.0
- // array( 'CURLCLOSEPOLICY_LEAST_TRAFFIC' ), // removed in PHP 5.6.0
- // array( 'CURLCLOSEPOLICY_OLDEST' ), // removed in PHP 5.6.0
- // array( 'CURLCLOSEPOLICY_SLOWEST' ), // removed in PHP 5.6.0
- [ 'CURLE_ABORTED_BY_CALLBACK' ],
- [ 'CURLE_BAD_CALLING_ORDER' ],
- [ 'CURLE_BAD_CONTENT_ENCODING' ],
- [ 'CURLE_BAD_FUNCTION_ARGUMENT' ],
- [ 'CURLE_BAD_PASSWORD_ENTERED' ],
- [ 'CURLE_COULDNT_CONNECT' ],
- [ 'CURLE_COULDNT_RESOLVE_HOST' ],
- [ 'CURLE_COULDNT_RESOLVE_PROXY' ],
- [ 'CURLE_FAILED_INIT' ],
- [ 'CURLE_FILESIZE_EXCEEDED' ],
- [ 'CURLE_FILE_COULDNT_READ_FILE' ],
- [ 'CURLE_FTP_ACCESS_DENIED' ],
- [ 'CURLE_FTP_BAD_DOWNLOAD_RESUME' ],
- [ 'CURLE_FTP_CANT_GET_HOST' ],
- [ 'CURLE_FTP_CANT_RECONNECT' ],
- [ 'CURLE_FTP_COULDNT_GET_SIZE' ],
- [ 'CURLE_FTP_COULDNT_RETR_FILE' ],
- [ 'CURLE_FTP_COULDNT_SET_ASCII' ],
- [ 'CURLE_FTP_COULDNT_SET_BINARY' ],
- [ 'CURLE_FTP_COULDNT_STOR_FILE' ],
- [ 'CURLE_FTP_COULDNT_USE_REST' ],
- [ 'CURLE_FTP_PORT_FAILED' ],
- [ 'CURLE_FTP_QUOTE_ERROR' ],
- [ 'CURLE_FTP_SSL_FAILED' ],
- [ 'CURLE_FTP_USER_PASSWORD_INCORRECT' ],
- [ 'CURLE_FTP_WEIRD_227_FORMAT' ],
- [ 'CURLE_FTP_WEIRD_PASS_REPLY' ],
- [ 'CURLE_FTP_WEIRD_PASV_REPLY' ],
- [ 'CURLE_FTP_WEIRD_SERVER_REPLY' ],
- [ 'CURLE_FTP_WEIRD_USER_REPLY' ],
- [ 'CURLE_FTP_WRITE_ERROR' ],
- [ 'CURLE_FUNCTION_NOT_FOUND' ],
- [ 'CURLE_GOT_NOTHING' ],
- [ 'CURLE_HTTP_NOT_FOUND' ],
- [ 'CURLE_HTTP_PORT_FAILED' ],
- [ 'CURLE_HTTP_POST_ERROR' ],
- [ 'CURLE_HTTP_RANGE_ERROR' ],
- [ 'CURLE_LDAP_CANNOT_BIND' ],
- [ 'CURLE_LDAP_INVALID_URL' ],
- [ 'CURLE_LDAP_SEARCH_FAILED' ],
- [ 'CURLE_LIBRARY_NOT_FOUND' ],
- [ 'CURLE_MALFORMAT_USER' ],
- [ 'CURLE_OBSOLETE' ],
- [ 'CURLE_OK' ],
- [ 'CURLE_OPERATION_TIMEOUTED' ],
- [ 'CURLE_OUT_OF_MEMORY' ],
- [ 'CURLE_PARTIAL_FILE' ],
- [ 'CURLE_READ_ERROR' ],
- [ 'CURLE_RECV_ERROR' ],
- [ 'CURLE_SEND_ERROR' ],
- [ 'CURLE_SHARE_IN_USE' ],
- // array( 'CURLE_SSH' ), // not present in HHVM 3.3.0-dev
- [ 'CURLE_SSL_CACERT' ],
- [ 'CURLE_SSL_CERTPROBLEM' ],
- [ 'CURLE_SSL_CIPHER' ],
- [ 'CURLE_SSL_CONNECT_ERROR' ],
- [ 'CURLE_SSL_ENGINE_NOTFOUND' ],
- [ 'CURLE_SSL_ENGINE_SETFAILED' ],
- [ 'CURLE_SSL_PEER_CERTIFICATE' ],
- [ 'CURLE_TELNET_OPTION_SYNTAX' ],
- [ 'CURLE_TOO_MANY_REDIRECTS' ],
- [ 'CURLE_UNKNOWN_TELNET_OPTION' ],
- [ 'CURLE_UNSUPPORTED_PROTOCOL' ],
- [ 'CURLE_URL_MALFORMAT' ],
- [ 'CURLE_URL_MALFORMAT_USER' ],
- [ 'CURLE_WRITE_ERROR' ],
- [ 'CURLFTPAUTH_DEFAULT' ],
- [ 'CURLFTPAUTH_SSL' ],
- [ 'CURLFTPAUTH_TLS' ],
- // array( 'CURLFTPMETHOD_MULTICWD' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLFTPMETHOD_NOCWD' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLFTPMETHOD_SINGLECWD' ), // not present in HHVM 3.3.0-dev
- [ 'CURLFTPSSL_ALL' ],
- [ 'CURLFTPSSL_CONTROL' ],
- [ 'CURLFTPSSL_NONE' ],
- [ 'CURLFTPSSL_TRY' ],
- // array( 'CURLINFO_CERTINFO' ), // not present in HHVM 3.3.0-dev
- [ 'CURLINFO_CONNECT_TIME' ],
- [ 'CURLINFO_CONTENT_LENGTH_DOWNLOAD' ],
- [ 'CURLINFO_CONTENT_LENGTH_UPLOAD' ],
- [ 'CURLINFO_CONTENT_TYPE' ],
- [ 'CURLINFO_EFFECTIVE_URL' ],
- [ 'CURLINFO_FILETIME' ],
- [ 'CURLINFO_HEADER_OUT' ],
- [ 'CURLINFO_HEADER_SIZE' ],
- [ 'CURLINFO_HTTP_CODE' ],
- [ 'CURLINFO_NAMELOOKUP_TIME' ],
- [ 'CURLINFO_PRETRANSFER_TIME' ],
- [ 'CURLINFO_PRIVATE' ],
- [ 'CURLINFO_REDIRECT_COUNT' ],
- [ 'CURLINFO_REDIRECT_TIME' ],
- // array( 'CURLINFO_REDIRECT_URL' ), // not present in HHVM 3.3.0-dev
- [ 'CURLINFO_REQUEST_SIZE' ],
- [ 'CURLINFO_SIZE_DOWNLOAD' ],
- [ 'CURLINFO_SIZE_UPLOAD' ],
- [ 'CURLINFO_SPEED_DOWNLOAD' ],
- [ 'CURLINFO_SPEED_UPLOAD' ],
- [ 'CURLINFO_SSL_VERIFYRESULT' ],
- [ 'CURLINFO_STARTTRANSFER_TIME' ],
- [ 'CURLINFO_TOTAL_TIME' ],
- [ 'CURLMSG_DONE' ],
- [ 'CURLM_BAD_EASY_HANDLE' ],
- [ 'CURLM_BAD_HANDLE' ],
- [ 'CURLM_CALL_MULTI_PERFORM' ],
- [ 'CURLM_INTERNAL_ERROR' ],
- [ 'CURLM_OK' ],
- [ 'CURLM_OUT_OF_MEMORY' ],
- [ 'CURLOPT_AUTOREFERER' ],
- [ 'CURLOPT_BINARYTRANSFER' ],
- [ 'CURLOPT_BUFFERSIZE' ],
- [ 'CURLOPT_CAINFO' ],
- [ 'CURLOPT_CAPATH' ],
- // array( 'CURLOPT_CERTINFO' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLOPT_CLOSEPOLICY' ), // removed in PHP 5.6.0
- [ 'CURLOPT_CONNECTTIMEOUT' ],
- [ 'CURLOPT_CONNECTTIMEOUT_MS' ],
- [ 'CURLOPT_COOKIE' ],
- [ 'CURLOPT_COOKIEFILE' ],
- [ 'CURLOPT_COOKIEJAR' ],
- [ 'CURLOPT_COOKIESESSION' ],
- [ 'CURLOPT_CRLF' ],
- [ 'CURLOPT_CUSTOMREQUEST' ],
- [ 'CURLOPT_DNS_CACHE_TIMEOUT' ],
- [ 'CURLOPT_DNS_USE_GLOBAL_CACHE' ],
- [ 'CURLOPT_EGDSOCKET' ],
- [ 'CURLOPT_ENCODING' ],
- [ 'CURLOPT_FAILONERROR' ],
- [ 'CURLOPT_FILE' ],
- [ 'CURLOPT_FILETIME' ],
- [ 'CURLOPT_FOLLOWLOCATION' ],
- [ 'CURLOPT_FORBID_REUSE' ],
- [ 'CURLOPT_FRESH_CONNECT' ],
- [ 'CURLOPT_FTPAPPEND' ],
- [ 'CURLOPT_FTPLISTONLY' ],
- [ 'CURLOPT_FTPPORT' ],
- [ 'CURLOPT_FTPSSLAUTH' ],
- [ 'CURLOPT_FTP_CREATE_MISSING_DIRS' ],
- // array( 'CURLOPT_FTP_FILEMETHOD' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLOPT_FTP_SKIP_PASV_IP' ), // not present in HHVM 3.3.0-dev
- [ 'CURLOPT_FTP_SSL' ],
- [ 'CURLOPT_FTP_USE_EPRT' ],
- [ 'CURLOPT_FTP_USE_EPSV' ],
- [ 'CURLOPT_HEADER' ],
- [ 'CURLOPT_HEADERFUNCTION' ],
- [ 'CURLOPT_HTTP200ALIASES' ],
- [ 'CURLOPT_HTTPAUTH' ],
- [ 'CURLOPT_HTTPGET' ],
- [ 'CURLOPT_HTTPHEADER' ],
- [ 'CURLOPT_HTTPPROXYTUNNEL' ],
- [ 'CURLOPT_HTTP_VERSION' ],
- [ 'CURLOPT_INFILE' ],
- [ 'CURLOPT_INFILESIZE' ],
- [ 'CURLOPT_INTERFACE' ],
- [ 'CURLOPT_IPRESOLVE' ],
- // array( 'CURLOPT_KEYPASSWD' ), // not present in HHVM 3.3.0-dev
- [ 'CURLOPT_KRB4LEVEL' ],
- [ 'CURLOPT_LOW_SPEED_LIMIT' ],
- [ 'CURLOPT_LOW_SPEED_TIME' ],
- [ 'CURLOPT_MAXCONNECTS' ],
- [ 'CURLOPT_MAXREDIRS' ],
- // array( 'CURLOPT_MAX_RECV_SPEED_LARGE' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLOPT_MAX_SEND_SPEED_LARGE' ), // not present in HHVM 3.3.0-dev
- [ 'CURLOPT_NETRC' ],
- [ 'CURLOPT_NOBODY' ],
- [ 'CURLOPT_NOPROGRESS' ],
- [ 'CURLOPT_NOSIGNAL' ],
- [ 'CURLOPT_PORT' ],
- [ 'CURLOPT_POST' ],
- [ 'CURLOPT_POSTFIELDS' ],
- [ 'CURLOPT_POSTQUOTE' ],
- [ 'CURLOPT_POSTREDIR' ],
- [ 'CURLOPT_PRIVATE' ],
- [ 'CURLOPT_PROGRESSFUNCTION' ],
- // array( 'CURLOPT_PROTOCOLS' ), // not present in HHVM 3.3.0-dev
- [ 'CURLOPT_PROXY' ],
- [ 'CURLOPT_PROXYAUTH' ],
- [ 'CURLOPT_PROXYPORT' ],
- [ 'CURLOPT_PROXYTYPE' ],
- [ 'CURLOPT_PROXYUSERPWD' ],
- [ 'CURLOPT_PUT' ],
- [ 'CURLOPT_QUOTE' ],
- [ 'CURLOPT_RANDOM_FILE' ],
- [ 'CURLOPT_RANGE' ],
- [ 'CURLOPT_READDATA' ],
- [ 'CURLOPT_READFUNCTION' ],
- // array( 'CURLOPT_REDIR_PROTOCOLS' ), // not present in HHVM 3.3.0-dev
- [ 'CURLOPT_REFERER' ],
- [ 'CURLOPT_RESUME_FROM' ],
- [ 'CURLOPT_RETURNTRANSFER' ],
- // array( 'CURLOPT_SSH_AUTH_TYPES' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLOPT_SSH_HOST_PUBLIC_KEY_MD5' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLOPT_SSH_PRIVATE_KEYFILE' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLOPT_SSH_PUBLIC_KEYFILE' ), // not present in HHVM 3.3.0-dev
- [ 'CURLOPT_SSLCERT' ],
- [ 'CURLOPT_SSLCERTPASSWD' ],
- [ 'CURLOPT_SSLCERTTYPE' ],
- [ 'CURLOPT_SSLENGINE' ],
- [ 'CURLOPT_SSLENGINE_DEFAULT' ],
- [ 'CURLOPT_SSLKEY' ],
- [ 'CURLOPT_SSLKEYPASSWD' ],
- [ 'CURLOPT_SSLKEYTYPE' ],
- [ 'CURLOPT_SSLVERSION' ],
- [ 'CURLOPT_SSL_CIPHER_LIST' ],
- [ 'CURLOPT_SSL_VERIFYHOST' ],
- [ 'CURLOPT_SSL_VERIFYPEER' ],
- [ 'CURLOPT_STDERR' ],
- [ 'CURLOPT_TCP_NODELAY' ],
- [ 'CURLOPT_TIMECONDITION' ],
- [ 'CURLOPT_TIMEOUT' ],
- [ 'CURLOPT_TIMEOUT_MS' ],
- [ 'CURLOPT_TIMEVALUE' ],
- [ 'CURLOPT_TRANSFERTEXT' ],
- [ 'CURLOPT_UNRESTRICTED_AUTH' ],
- [ 'CURLOPT_UPLOAD' ],
- [ 'CURLOPT_URL' ],
- [ 'CURLOPT_USERAGENT' ],
- [ 'CURLOPT_USERPWD' ],
- [ 'CURLOPT_VERBOSE' ],
- [ 'CURLOPT_WRITEFUNCTION' ],
- [ 'CURLOPT_WRITEHEADER' ],
- // array( 'CURLPROTO_ALL' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLPROTO_DICT' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLPROTO_FILE' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLPROTO_FTP' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLPROTO_FTPS' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLPROTO_HTTP' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLPROTO_HTTPS' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLPROTO_LDAP' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLPROTO_LDAPS' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLPROTO_SCP' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLPROTO_SFTP' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLPROTO_TELNET' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLPROTO_TFTP' ), // not present in HHVM 3.3.0-dev
- [ 'CURLPROXY_HTTP' ],
- // array( 'CURLPROXY_SOCKS4' ), // not present in HHVM 3.3.0-dev
- [ 'CURLPROXY_SOCKS5' ],
- // array( 'CURLSSH_AUTH_DEFAULT' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLSSH_AUTH_HOST' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLSSH_AUTH_KEYBOARD' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLSSH_AUTH_NONE' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLSSH_AUTH_PASSWORD' ), // not present in HHVM 3.3.0-dev
- // array( 'CURLSSH_AUTH_PUBLICKEY' ), // not present in HHVM 3.3.0-dev
- [ 'CURLVERSION_NOW' ],
- [ 'CURL_HTTP_VERSION_1_0' ],
- [ 'CURL_HTTP_VERSION_1_1' ],
- [ 'CURL_HTTP_VERSION_NONE' ],
- [ 'CURL_IPRESOLVE_V4' ],
- [ 'CURL_IPRESOLVE_V6' ],
- [ 'CURL_IPRESOLVE_WHATEVER' ],
- [ 'CURL_NETRC_IGNORED' ],
- [ 'CURL_NETRC_OPTIONAL' ],
- [ 'CURL_NETRC_REQUIRED' ],
- [ 'CURL_TIMECOND_IFMODSINCE' ],
- [ 'CURL_TIMECOND_IFUNMODSINCE' ],
- [ 'CURL_TIMECOND_LASTMOD' ],
- [ 'CURL_VERSION_IPV6' ],
- [ 'CURL_VERSION_KERBEROS4' ],
- [ 'CURL_VERSION_LIBZ' ],
- [ 'CURL_VERSION_SSL' ],
- ];
- }
-
- /**
- * Added this test based on an issue experienced with HHVM 3.3.0-dev
- * where it did not define a cURL constant.
- *
- * @bug 70570
- * @dataProvider provideCurlConstants
- */
- public function testCurlConstants( $value ) {
- $this->assertTrue( defined( $value ), $value . ' not defined' );
- }
-}
-
-/**
- * Class to let us overwrite MWHttpRequest respHeaders variable
- */
-class MWHttpRequestTester extends MWHttpRequest {
- // function derived from the MWHttpRequest factory function but
- // returns appropriate tester class here
- public static function factory( $url, $options = null, $caller = __METHOD__ ) {
- if ( !Http::$httpEngine ) {
- Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php';
- } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
- throw new MWException( __METHOD__ . ': curl (http://php.net/curl) is not installed, but' .
- 'Http::$httpEngine is set to "curl"' );
- }
-
- switch ( Http::$httpEngine ) {
- case 'curl':
- return new CurlHttpRequestTester( $url, $options, $caller );
- case 'php':
- if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
- throw new MWException( __METHOD__ .
- ': allow_url_fopen needs to be enabled for pure PHP HTTP requests to work. '
- . 'If possible, curl should be used instead. See http://php.net/curl.' );
- }
-
- return new PhpHttpRequestTester( $url, $options, $caller );
- default:
- }
- }
-}
-
-class CurlHttpRequestTester extends CurlHttpRequest {
- function setRespHeaders( $name, $value ) {
- $this->respHeaders[$name] = $value;
- }
-}
-
-class PhpHttpRequestTester extends PhpHttpRequest {
- function setRespHeaders( $name, $value ) {
- $this->respHeaders[$name] = $value;
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/LicensesTest.php b/www/wiki/tests/phpunit/includes/LicensesTest.php
index c7b3ce89..0e96bf44 100644
--- a/www/wiki/tests/phpunit/includes/LicensesTest.php
+++ b/www/wiki/tests/phpunit/includes/LicensesTest.php
@@ -20,6 +20,6 @@ class LicensesTest extends MediaWikiTestCase {
'name' => 'AnotherName',
'licenses' => $str,
] );
- $this->assertThat( $lc, $this->isInstanceOf( 'Licenses' ) );
+ $this->assertThat( $lc, $this->isInstanceOf( Licenses::class ) );
}
}
diff --git a/www/wiki/tests/phpunit/includes/LinkFilterTest.php b/www/wiki/tests/phpunit/includes/LinkFilterTest.php
index ed4958f2..51b54d2c 100644
--- a/www/wiki/tests/phpunit/includes/LinkFilterTest.php
+++ b/www/wiki/tests/phpunit/includes/LinkFilterTest.php
@@ -3,6 +3,7 @@
use Wikimedia\Rdbms\LikeMatch;
/**
+ * @covers LinkFilter
* @group Database
*/
class LinkFilterTest extends MediaWikiLangTestCase {
diff --git a/www/wiki/tests/phpunit/includes/LinkerTest.php b/www/wiki/tests/phpunit/includes/LinkerTest.php
index f4844f89..f9e2cc17 100644
--- a/www/wiki/tests/phpunit/includes/LinkerTest.php
+++ b/www/wiki/tests/phpunit/includes/LinkerTest.php
@@ -5,7 +5,6 @@ use MediaWiki\MediaWikiServices;
/**
* @group Database
*/
-
class LinkerTest extends MediaWikiLangTestCase {
/**
@@ -133,7 +132,7 @@ class LinkerTest extends MediaWikiLangTestCase {
public function provideCasesForFormatComment() {
$wikiId = 'enwiki'; // $wgConf has a fake entry for this
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return [
// Linker::formatComment
[
@@ -257,7 +256,7 @@ class LinkerTest extends MediaWikiLangTestCase {
false, false, $wikiId
],
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
/**
@@ -289,7 +288,7 @@ class LinkerTest extends MediaWikiLangTestCase {
}
public static function provideCasesForFormatLinksInComment() {
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return [
[
'foo bar <a href="/wiki/Special:BlankPage" title="Special:BlankPage">Special:BlankPage</a>',
@@ -312,43 +311,43 @@ class LinkerTest extends MediaWikiLangTestCase {
'enwiki',
],
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
public static function provideLinkBeginHook() {
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return [
// Modify $html
[
- function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+ function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
$html = 'foobar';
},
'<a href="/wiki/Special:BlankPage" title="Special:BlankPage">foobar</a>'
],
// Modify $attribs
[
- function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+ function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
$attribs['bar'] = 'baz';
},
'<a href="/wiki/Special:BlankPage" title="Special:BlankPage" bar="baz">Special:BlankPage</a>'
],
// Modify $query
[
- function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+ function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
$query['bar'] = 'baz';
},
'<a href="/w/index.php?title=Special:BlankPage&amp;bar=baz" title="Special:BlankPage">Special:BlankPage</a>'
],
// Force HTTP $options
[
- function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+ function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
$options = [ 'http' ];
},
'<a href="http://example.org/wiki/Special:BlankPage" title="Special:BlankPage">Special:BlankPage</a>'
],
// Force 'forcearticlepath' in $options
[
- function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+ function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
$options = [ 'forcearticlepath' ];
$query['foo'] = 'bar';
},
@@ -356,14 +355,14 @@ class LinkerTest extends MediaWikiLangTestCase {
],
// Abort early
[
- function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+ function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
$ret = 'foobar';
return false;
},
'foobar'
],
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
/**
diff --git a/www/wiki/tests/phpunit/includes/ListToggleTest.php b/www/wiki/tests/phpunit/includes/ListToggleTest.php
new file mode 100644
index 00000000..3574545e
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/ListToggleTest.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @covers ListToggle
+ */
+class ListToggleTest extends MediaWikiTestCase {
+
+ /**
+ * @covers ListToggle::__construct
+ */
+ public function testConstruct() {
+ $output = $this->getMockBuilder( OutputPage::class )
+ ->setMethods( null )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $listToggle = new ListToggle( $output );
+
+ $this->assertInstanceOf( ListToggle::class, $listToggle );
+ $this->assertContains( 'mediawiki.checkboxtoggle', $output->getModules() );
+ $this->assertContains( 'mediawiki.checkboxtoggle.styles', $output->getModuleStyles() );
+ }
+
+ /**
+ * @covers ListToggle::getHTML
+ */
+ public function testGetHTML() {
+ $output = $this->createMock( OutputPage::class );
+ $output->expects( $this->any() )
+ ->method( 'msg' )
+ ->will( $this->returnCallback( function ( $key ) {
+ return wfMessage( $key )->inLanguage( 'qqx' );
+ } ) );
+ $output->expects( $this->once() )
+ ->method( 'getLanguage' )
+ ->will( $this->returnValue( Language::factory( 'qqx' ) ) );
+
+ $listToggle = new ListToggle( $output );
+
+ $html = $listToggle->getHTML();
+ $this->assertEquals( '<div class="mw-checkbox-toggle-controls">' .
+ '(checkbox-select: <a class="mw-checkbox-all" role="button"' .
+ ' tabindex="0">(checkbox-all)</a>(comma-separator)' .
+ '<a class="mw-checkbox-none" role="button" tabindex="0">' .
+ '(checkbox-none)</a>(comma-separator)<a class="mw-checkbox-invert" ' .
+ 'role="button" tabindex="0">(checkbox-invert)</a>)</div>',
+ $html );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/MWNamespaceTest.php b/www/wiki/tests/phpunit/includes/MWNamespaceTest.php
index 498532f7..15e2defc 100644
--- a/www/wiki/tests/phpunit/includes/MWNamespaceTest.php
+++ b/www/wiki/tests/phpunit/includes/MWNamespaceTest.php
@@ -5,12 +5,8 @@
* @file
*/
-/**
- * Test class for MWNamespace.
- * @todo covers tags
- * @todo FIXME: this test file is a mess
- */
class MWNamespaceTest extends MediaWikiTestCase {
+
protected function setUp() {
parent::setUp();
@@ -27,15 +23,20 @@ class MWNamespaceTest extends MediaWikiTestCase {
] );
}
-# ### START OF TESTS #########################################################
-
/**
* @todo Write more texts, handle $wgAllowImageMoving setting
* @covers MWNamespace::isMovable
*/
public function testIsMovable() {
$this->assertFalse( MWNamespace::isMovable( NS_SPECIAL ) );
- # @todo FIXME: Write more tests!!
+ }
+
+ private function assertIsSubject( $ns ) {
+ $this->assertTrue( MWNamespace::isSubject( $ns ) );
+ }
+
+ private function assertIsNotSubject( $ns ) {
+ $this->assertFalse( MWNamespace::isSubject( $ns ) );
}
/**
@@ -58,6 +59,14 @@ class MWNamespaceTest extends MediaWikiTestCase {
$this->assertIsNotSubject( 101 ); # user defined
}
+ private function assertIsTalk( $ns ) {
+ $this->assertTrue( MWNamespace::isTalk( $ns ) );
+ }
+
+ private function assertIsNotTalk( $ns ) {
+ $this->assertFalse( MWNamespace::isTalk( $ns ) );
+ }
+
/**
* Reverse of testIsSubject().
* Please update testIsSubject() if you change assertions below
@@ -155,18 +164,6 @@ class MWNamespaceTest extends MediaWikiTestCase {
}
/**
- * @todo Implement testExists().
- */
- /*
- public function testExists() {
- // Remove the following lines when you implement this test.
- $this->markTestIncomplete(
- 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.'
- );
- }
- */
-
- /**
* Test MWNamespace::equals
* Note if we add a namespace registration system with keys like 'MAIN'
* we should add tests here for equivilance on things like 'MAIN' == 0
@@ -216,52 +213,6 @@ class MWNamespaceTest extends MediaWikiTestCase {
);
}
- /**
- * @todo Implement testGetCanonicalNamespaces().
- */
- /*
- public function testGetCanonicalNamespaces() {
- // Remove the following lines when you implement this test.
- $this->markTestIncomplete(
- 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.'
- );
- }
- */
- /**
- * @todo Implement testGetCanonicalName().
- */
- /*
- public function testGetCanonicalName() {
- // Remove the following lines when you implement this test.
- $this->markTestIncomplete(
- 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.'
- );
- }
- */
- /**
- * @todo Implement testGetCanonicalIndex().
- */
- /*
- public function testGetCanonicalIndex() {
- // Remove the following lines when you implement this test.
- $this->markTestIncomplete(
- 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.'
- );
- }
- */
-
- /**
- * @todo Implement testGetValidNamespaces().
- */
- /*
- public function testGetValidNamespaces() {
- // Remove the following lines when you implement this test.
- $this->markTestIncomplete(
- 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.'
- );
- }
- */
-
public function provideHasTalkNamespace() {
return [
[ NS_MEDIA, false ],
@@ -301,6 +252,14 @@ class MWNamespaceTest extends MediaWikiTestCase {
$this->assertSame( $actual, $expected, "NS $index" );
}
+ private function assertIsContent( $ns ) {
+ $this->assertTrue( MWNamespace::isContent( $ns ) );
+ }
+
+ private function assertIsNotContent( $ns ) {
+ $this->assertFalse( MWNamespace::isContent( $ns ) );
+ }
+
/**
* @covers MWNamespace::isContent
*/
@@ -340,6 +299,14 @@ class MWNamespaceTest extends MediaWikiTestCase {
$this->assertIsContent( NS_MAIN );
}
+ private function assertIsWatchable( $ns ) {
+ $this->assertTrue( MWNamespace::isWatchable( $ns ) );
+ }
+
+ private function assertIsNotWatchable( $ns ) {
+ $this->assertFalse( MWNamespace::isWatchable( $ns ) );
+ }
+
/**
* @covers MWNamespace::isWatchable
*/
@@ -357,6 +324,14 @@ class MWNamespaceTest extends MediaWikiTestCase {
$this->assertIsWatchable( 101 );
}
+ private function assertHasSubpages( $ns ) {
+ $this->assertTrue( MWNamespace::hasSubpages( $ns ) );
+ }
+
+ private function assertHasNotSubpages( $ns ) {
+ $this->assertFalse( MWNamespace::hasSubpages( $ns ) );
+ }
+
/**
* @covers MWNamespace::hasSubpages
*/
@@ -465,6 +440,14 @@ class MWNamespaceTest extends MediaWikiTestCase {
"Subject namespaces should not have NS_SPECIAL" );
}
+ private function assertIsCapitalized( $ns ) {
+ $this->assertTrue( MWNamespace::isCapitalized( $ns ) );
+ }
+
+ private function assertIsNotCapitalized( $ns ) {
+ $this->assertFalse( MWNamespace::isCapitalized( $ns ) );
+ }
+
/**
* Some namespaces are always capitalized per code definition
* in MWNamespace::$alwaysCapitalizedNamespaces
@@ -585,48 +568,11 @@ class MWNamespaceTest extends MediaWikiTestCase {
$this->assertFalse( MWNamespace::isNonincludable( NS_TEMPLATE ) );
}
- # ###### HELPERS ###########################################################
- function __call( $method, $args ) {
- // Call the real method if it exists
- if ( method_exists( $this, $method ) ) {
- return $this->$method( $args );
- }
-
- if ( preg_match(
- '/^assert(Has|Is|Can)(Not|)(Subject|Talk|Watchable|Content|Subpages|Capitalized)$/',
- $method,
- $m
- ) ) {
- # Interprets arguments:
- $ns = $args[0];
- $msg = isset( $args[1] ) ? $args[1] : " dummy message";
-
- # Forge the namespace constant name:
- if ( $ns === 0 ) {
- $ns_name = "NS_MAIN";
- } else {
- $ns_name = "NS_" . strtoupper( MWNamespace::getCanonicalName( $ns ) );
- }
- # ... and the MWNamespace method name
- $nsMethod = strtolower( $m[1] ) . $m[3];
-
- $expect = ( $m[2] === '' );
- $expect_name = $expect ? 'TRUE' : 'FALSE';
-
- return $this->assertEquals( $expect,
- MWNamespace::$nsMethod( $ns, $msg ),
- "MWNamespace::$nsMethod( $ns_name ) should returns $expect_name"
- );
- }
-
- throw new Exception( __METHOD__ . " could not find a method named $method\n" );
- }
-
- function assertSameSubject( $ns1, $ns2, $msg = '' ) {
- $this->assertTrue( MWNamespace::subjectEquals( $ns1, $ns2, $msg ) );
+ private function assertSameSubject( $ns1, $ns2, $msg = '' ) {
+ $this->assertTrue( MWNamespace::subjectEquals( $ns1, $ns2 ), $msg );
}
- function assertDifferentSubject( $ns1, $ns2, $msg = '' ) {
- $this->assertFalse( MWNamespace::subjectEquals( $ns1, $ns2, $msg ) );
+ private function assertDifferentSubject( $ns1, $ns2, $msg = '' ) {
+ $this->assertFalse( MWNamespace::subjectEquals( $ns1, $ns2 ), $msg );
}
}
diff --git a/www/wiki/tests/phpunit/includes/MWTimestampTest.php b/www/wiki/tests/phpunit/includes/MWTimestampTest.php
index c1a46fed..9735eebd 100644
--- a/www/wiki/tests/phpunit/includes/MWTimestampTest.php
+++ b/www/wiki/tests/phpunit/includes/MWTimestampTest.php
@@ -23,7 +23,7 @@ class MWTimestampTest extends MediaWikiLangTestCase {
$expectedOutput, // The expected output
$desc // Description
) {
- $user = $this->createMock( 'User' );
+ $user = $this->createMock( User::class );
$user->expects( $this->any() )
->method( 'getOption' )
->with( 'timecorrection' )
@@ -156,7 +156,7 @@ class MWTimestampTest extends MediaWikiLangTestCase {
$expectedOutput, // The expected output
$desc // Description
) {
- $user = $this->createMock( 'User' );
+ $user = $this->createMock( User::class );
$user->expects( $this->any() )
->method( 'getOption' )
->with( 'timecorrection' )
diff --git a/www/wiki/tests/phpunit/includes/MediaWikiServicesTest.php b/www/wiki/tests/phpunit/includes/MediaWikiServicesTest.php
index b301b8b7..03588aec 100644
--- a/www/wiki/tests/phpunit/includes/MediaWikiServicesTest.php
+++ b/www/wiki/tests/phpunit/includes/MediaWikiServicesTest.php
@@ -1,5 +1,6 @@
<?php
-use Liuggio\StatsdClient\Factory\StatsdDataFactory;
+
+use Mediawiki\Http\HttpRequestFactory;
use MediaWiki\Interwiki\InterwikiLookup;
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\Linker\LinkRendererFactory;
@@ -7,8 +8,12 @@ use MediaWiki\MediaWikiServices;
use MediaWiki\Services\DestructibleService;
use MediaWiki\Services\SalvageableService;
use MediaWiki\Services\ServiceDisabledException;
-use Wikimedia\Rdbms\LBFactory;
use MediaWiki\Shell\CommandFactory;
+use MediaWiki\Storage\BlobStore;
+use MediaWiki\Storage\BlobStoreFactory;
+use MediaWiki\Storage\RevisionLookup;
+use MediaWiki\Storage\RevisionStore;
+use MediaWiki\Storage\SqlBlobStore;
/**
* @covers MediaWiki\MediaWikiServices
@@ -49,14 +54,14 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
public function testGetInstance() {
$services = MediaWikiServices::getInstance();
- $this->assertInstanceOf( 'MediaWiki\\MediaWikiServices', $services );
+ $this->assertInstanceOf( MediaWikiServices::class, $services );
}
public function testForceGlobalInstance() {
$newServices = $this->newMediaWikiServices();
$oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
- $this->assertInstanceOf( 'MediaWiki\\MediaWikiServices', $oldServices );
+ $this->assertInstanceOf( MediaWikiServices::class, $oldServices );
$this->assertNotSame( $oldServices, $newServices );
$theServices = MediaWikiServices::getInstance();
@@ -145,7 +150,7 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
$newServices = $this->newMediaWikiServices();
$oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
- $lbFactory = $this->getMockBuilder( 'LBFactorySimple' )
+ $lbFactory = $this->getMockBuilder( \Wikimedia\Rdbms\LBFactorySimple::class )
->disableOriginalConstructor()
->getMock();
@@ -173,6 +178,9 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
MediaWikiServices::forceGlobalInstance( $oldServices );
$newServices->destroy();
+
+ // No exception was thrown, avoid being risky
+ $this->assertTrue( true );
}
public function testResetChildProcessServices() {
@@ -220,7 +228,7 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
'Test',
function () use ( &$serviceCounter ) {
$serviceCounter++;
- $service = $this->createMock( 'MediaWiki\Services\DestructibleService' );
+ $service = $this->createMock( MediaWiki\Services\DestructibleService::class );
$service->expects( $this->once() )->method( 'destroy' );
return $service;
}
@@ -249,7 +257,7 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
$services->defineService(
'Test',
function () {
- $service = $this->createMock( 'MediaWiki\Services\DestructibleService' );
+ $service = $this->createMock( MediaWiki\Services\DestructibleService::class );
$service->expects( $this->never() )->method( 'destroy' );
return $service;
}
@@ -311,7 +319,7 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
'SearchEngineConfig' => [ 'SearchEngineConfig', SearchEngineConfig::class ],
'SkinFactory' => [ 'SkinFactory', SkinFactory::class ],
'DBLoadBalancerFactory' => [ 'DBLoadBalancerFactory', Wikimedia\Rdbms\LBFactory::class ],
- 'DBLoadBalancer' => [ 'DBLoadBalancer', 'LoadBalancer' ],
+ 'DBLoadBalancer' => [ 'DBLoadBalancer', Wikimedia\Rdbms\LoadBalancer::class ],
'WatchedItemStore' => [ 'WatchedItemStore', WatchedItemStore::class ],
'WatchedItemQueryService' => [ 'WatchedItemQueryService', WatchedItemQueryService::class ],
'CryptRand' => [ 'CryptRand', CryptRand::class ],
@@ -333,6 +341,13 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
'LocalServerObjectCache' => [ 'LocalServerObjectCache', BagOStuff::class ],
'VirtualRESTServiceClient' => [ 'VirtualRESTServiceClient', VirtualRESTServiceClient::class ],
'ShellCommandFactory' => [ 'ShellCommandFactory', CommandFactory::class ],
+ 'BlobStoreFactory' => [ 'BlobStoreFactory', BlobStoreFactory::class ],
+ 'BlobStore' => [ 'BlobStore', BlobStore::class ],
+ '_SqlBlobStore' => [ '_SqlBlobStore', SqlBlobStore::class ],
+ 'RevisionStore' => [ 'RevisionStore', RevisionStore::class ],
+ 'RevisionLookup' => [ 'RevisionLookup', RevisionLookup::class ],
+ 'HttpRequestFactory' => [ 'HttpRequestFactory', HttpRequestFactory::class ],
+ 'CommentStore' => [ 'CommentStore', CommentStore::class ],
];
}
diff --git a/www/wiki/tests/phpunit/includes/MediaWikiVersionFetcherTest.php b/www/wiki/tests/phpunit/includes/MediaWikiVersionFetcherTest.php
index fa59ef29..87a7dffd 100644
--- a/www/wiki/tests/phpunit/includes/MediaWikiVersionFetcherTest.php
+++ b/www/wiki/tests/phpunit/includes/MediaWikiVersionFetcherTest.php
@@ -10,7 +10,9 @@
*
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
-class MediaWikiVersionFetcherTest extends PHPUnit_Framework_TestCase {
+class MediaWikiVersionFetcherTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
public function testReturnsResult() {
$versionFetcher = new MediaWikiVersionFetcher();
diff --git a/www/wiki/tests/phpunit/includes/MergeHistoryTest.php b/www/wiki/tests/phpunit/includes/MergeHistoryTest.php
index f44ae322..54db581c 100644
--- a/www/wiki/tests/phpunit/includes/MergeHistoryTest.php
+++ b/www/wiki/tests/phpunit/includes/MergeHistoryTest.php
@@ -68,7 +68,7 @@ class MergeHistoryTest extends MediaWikiTestCase {
public function testIsValidMergeRevisionLimit() {
$limit = MergeHistory::REVISION_LIMIT;
- $mh = $this->getMockBuilder( 'MergeHistory' )
+ $mh = $this->getMockBuilder( MergeHistory::class )
->setMethods( [ 'getRevisionCount' ] )
->setConstructorArgs( [
Title::newFromText( 'Test' ),
diff --git a/www/wiki/tests/phpunit/includes/MessageTest.php b/www/wiki/tests/phpunit/includes/MessageTest.php
index 912bffe6..70f4af9e 100644
--- a/www/wiki/tests/phpunit/includes/MessageTest.php
+++ b/www/wiki/tests/phpunit/includes/MessageTest.php
@@ -1,7 +1,11 @@
<?php
+use Wikimedia\ObjectFactory;
use Wikimedia\TestingAccessWrapper;
+/**
+ * @group Database
+ */
class MessageTest extends MediaWikiLangTestCase {
protected function setUp() {
@@ -24,7 +28,7 @@ class MessageTest extends MediaWikiLangTestCase {
$this->assertSame( $params, $message->getParams() );
$this->assertEquals( $expectedLang, $message->getLanguage() );
- $messageSpecifier = $this->getMockForAbstractClass( 'MessageSpecifier' );
+ $messageSpecifier = $this->getMockForAbstractClass( MessageSpecifier::class );
$messageSpecifier->expects( $this->any() )
->method( 'getKey' )->will( $this->returnValue( $key ) );
$messageSpecifier->expects( $this->any() )
@@ -197,16 +201,16 @@ class MessageTest extends MediaWikiLangTestCase {
* @covers ::wfMessage
*/
public function testWfMessage() {
- $this->assertInstanceOf( 'Message', wfMessage( 'mainpage' ) );
- $this->assertInstanceOf( 'Message', wfMessage( 'i-dont-exist-evar' ) );
+ $this->assertInstanceOf( Message::class, wfMessage( 'mainpage' ) );
+ $this->assertInstanceOf( Message::class, wfMessage( 'i-dont-exist-evar' ) );
}
/**
* @covers Message::newFromKey
*/
public function testNewFromKey() {
- $this->assertInstanceOf( 'Message', Message::newFromKey( 'mainpage' ) );
- $this->assertInstanceOf( 'Message', Message::newFromKey( 'i-dont-exist-evar' ) );
+ $this->assertInstanceOf( Message::class, Message::newFromKey( 'mainpage' ) );
+ $this->assertInstanceOf( Message::class, Message::newFromKey( 'i-dont-exist-evar' ) );
}
/**
@@ -467,7 +471,6 @@ class MessageTest extends MediaWikiLangTestCase {
/**
* FIXME: This should not need database, but Language#formatExpiry does (T57912)
- * @group Database
* @covers Message::expiryParam
* @covers Message::expiryParams
*/
@@ -810,7 +813,7 @@ class MessageTest extends MediaWikiLangTestCase {
$msg = unserialize( serialize( $msg ) );
$this->assertSame( '(<a>foo</a>)', $msg->parse() );
$title = TestingAccessWrapper::newFromObject( $msg )->title;
- $this->assertInstanceOf( 'Title', $title );
+ $this->assertInstanceOf( Title::class, $title );
$this->assertSame( 'Testing', $title->getFullText() );
$msg = new Message( 'mainpage' );
diff --git a/www/wiki/tests/phpunit/includes/MimeMagicTest.php b/www/wiki/tests/phpunit/includes/MimeMagicTest.php
deleted file mode 100644
index e00cf0ce..00000000
--- a/www/wiki/tests/phpunit/includes/MimeMagicTest.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-class MimeMagicTest extends PHPUnit_Framework_TestCase {
-
- /** @var MimeMagic */
- private $mimeMagic;
-
- function setUp() {
- $this->mimeMagic = MimeMagic::singleton();
- parent::setUp();
- }
-
- /**
- * @dataProvider providerImproveTypeFromExtension
- * @param string $ext File extension (no leading dot)
- * @param string $oldMime Initially detected MIME
- * @param string $expectedMime MIME type after taking extension into account
- */
- function testImproveTypeFromExtension( $ext, $oldMime, $expectedMime ) {
- $actualMime = $this->mimeMagic->improveTypeFromExtension( $oldMime, $ext );
- $this->assertEquals( $expectedMime, $actualMime );
- }
-
- function providerImproveTypeFromExtension() {
- return [
- [ 'gif', 'image/gif', 'image/gif' ],
- [ 'gif', 'unknown/unknown', 'unknown/unknown' ],
- [ 'wrl', 'unknown/unknown', 'model/vrml' ],
- [ 'txt', 'text/plain', 'text/plain' ],
- [ 'csv', 'text/plain', 'text/csv' ],
- [ 'tsv', 'text/plain', 'text/tab-separated-values' ],
- [ 'js', 'text/javascript', 'application/javascript' ],
- [ 'js', 'application/x-javascript', 'application/javascript' ],
- [ 'json', 'text/plain', 'application/json' ],
- [ 'foo', 'application/x-opc+zip', 'application/zip' ],
- [ 'docx', 'application/x-opc+zip',
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ],
- [ 'djvu', 'image/x-djvu', 'image/vnd.djvu' ],
- [ 'wav', 'audio/wav', 'audio/wav' ],
- ];
- }
-
- /**
- * Test to make sure that encoder=ffmpeg2theora doesn't trigger
- * MEDIATYPE_VIDEO (bug 63584)
- */
- function testOggRecognize() {
- $oggFile = __DIR__ . '/../data/media/say-test.ogg';
- $actualType = $this->mimeMagic->getMediaType( $oggFile, 'application/ogg' );
- $this->assertEquals( $actualType, MEDIATYPE_AUDIO );
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/OutputPageTest.php b/www/wiki/tests/phpunit/includes/OutputPageTest.php
index 52103f97..88c585fe 100644
--- a/www/wiki/tests/phpunit/includes/OutputPageTest.php
+++ b/www/wiki/tests/phpunit/includes/OutputPageTest.php
@@ -266,15 +266,15 @@ class OutputPageTest extends MediaWikiTestCase {
'UploadPath' => $uploadPath,
] );
- MediaWiki\suppressWarnings();
+ Wikimedia\suppressWarnings();
$actual = OutputPage::transformResourcePath( $conf, $path );
- MediaWiki\restoreWarnings();
+ Wikimedia\restoreWarnings();
$this->assertEquals( $expected ?: $path, $actual );
}
public static function provideMakeResourceLoaderLink() {
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return [
// Single only=scripts load
[
@@ -297,7 +297,7 @@ class OutputPageTest extends MediaWikiTestCase {
. "});</script>"
],
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
/**
@@ -311,7 +311,7 @@ class OutputPageTest extends MediaWikiTestCase {
'wgResourceLoaderDebug' => false,
'wgLoadScript' => 'http://127.0.0.1:8080/w/load.php',
] );
- $class = new ReflectionClass( 'OutputPage' );
+ $class = new ReflectionClass( OutputPage::class );
$method = $class->getMethod( 'makeResourceLoaderLink' );
$method->setAccessible( true );
$ctx = new RequestContext();
@@ -345,6 +345,7 @@ class OutputPageTest extends MediaWikiTestCase {
}
public static function provideBuildExemptModules() {
+ // phpcs:disable Generic.Files.LineLength
return [
'empty' => [
'exemptStyleModules' => [],
@@ -354,7 +355,6 @@ class OutputPageTest extends MediaWikiTestCase {
'exemptStyleModules' => [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ],
'<meta name="ResourceLoaderDynamicStyles" content=""/>',
],
- // @codingStandardsIgnoreStart Generic.Files.LineLength
'default logged-out' => [
'exemptStyleModules' => [ 'site' => [ 'site.styles' ] ],
'<meta name="ResourceLoaderDynamicStyles" content=""/>' . "\n" .
@@ -377,8 +377,8 @@ class OutputPageTest extends MediaWikiTestCase {
'<link rel="stylesheet" href="/w/load.php?debug=false&amp;lang=en&amp;modules=example.user&amp;only=styles&amp;skin=fallback&amp;version=0a56zyi"/>' . "\n" .
'<link rel="stylesheet" href="/w/load.php?debug=false&amp;lang=en&amp;modules=user.styles&amp;only=styles&amp;skin=fallback&amp;version=1e9z0ox"/>',
],
- // @codingStandardsIgnoreEnd Generic.Files.LineLength
];
+ // phpcs:enable
}
/**
@@ -398,7 +398,7 @@ class OutputPageTest extends MediaWikiTestCase {
$ctx = new RequestContext();
$ctx->setSkin( SkinFactory::getDefaultInstance()->makeSkin( 'fallback' ) );
$ctx->setLanguage( 'en' );
- $outputPage = $this->getMockBuilder( 'OutputPage' )
+ $outputPage = $this->getMockBuilder( OutputPage::class )
->setConstructorArgs( [ $ctx ] )
->setMethods( [ 'isUserCssPreview', 'buildCssLinksArray' ] )
->getMock();
@@ -434,7 +434,7 @@ class OutputPageTest extends MediaWikiTestCase {
*/
public function testVaryHeaders( $calls, $vary, $key ) {
// get rid of default Vary fields
- $outputPage = $this->getMockBuilder( 'OutputPage' )
+ $outputPage = $this->getMockBuilder( OutputPage::class )
->setConstructorArgs( [ new RequestContext() ] )
->setMethods( [ 'getCacheVaryCookies' ] )
->getMock();
@@ -525,7 +525,7 @@ class OutputPageTest extends MediaWikiTestCase {
$this->assertTrue( $outputPage->haveCacheVaryCookies() );
}
- /*
+ /**
* @covers OutputPage::addCategoryLinks
* @covers OutputPage::getCategories
*/
@@ -539,7 +539,7 @@ class OutputPageTest extends MediaWikiTestCase {
'page_title' => 'Test2'
]
] );
- $outputPage = $this->getMockBuilder( 'OutputPage' )
+ $outputPage = $this->getMockBuilder( OutputPage::class )
->setConstructorArgs( [ new RequestContext() ] )
->setMethods( [ 'addCategoryLinksToLBAndGetResult' ] )
->getMock();
@@ -643,6 +643,17 @@ class OutputPageTest extends MediaWikiTestCase {
[
[
'ResourceBasePath' => '/w',
+ 'Logo' => '/img/default.png',
+ 'LogoHD' => [
+ 'svg' => '/img/vector.svg',
+ ],
+ ],
+ 'Link: </img/vector.svg>;rel=preload;as=image'
+
+ ],
+ [
+ [
+ 'ResourceBasePath' => '/w',
'Logo' => '/w/test.jpg',
'LogoHD' => false,
'UploadPath' => '/w/images',
@@ -685,10 +696,6 @@ class NullMessageBlobStore extends MessageBlobStore {
return [];
}
- public function insertMessageBlob( $name, ResourceLoaderModule $module, $lang ) {
- return false;
- }
-
public function updateModule( $name, ResourceLoaderModule $module, $lang ) {
}
diff --git a/www/wiki/tests/phpunit/includes/PageArchiveTest.php b/www/wiki/tests/phpunit/includes/PageArchiveTest.php
index 6420c395..623d4a65 100644
--- a/www/wiki/tests/phpunit/includes/PageArchiveTest.php
+++ b/www/wiki/tests/phpunit/includes/PageArchiveTest.php
@@ -11,8 +11,9 @@
* ^--- important, causes tests not to fail with timeout
*/
class PageArchiveTest extends MediaWikiTestCase {
+
/**
- * @var WikiPage $archivedPage
+ * @var PageArchive $archivedPage
*/
private $archivedPage;
@@ -49,6 +50,10 @@ class PageArchiveTest extends MediaWikiTestCase {
protected function setUp() {
parent::setUp();
+ $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->overrideMwServices();
+
// First create our dummy page
$page = Title::newFromText( 'PageArchiveTest_thePage' );
$page = new WikiPage( $page );
@@ -78,33 +83,183 @@ class PageArchiveTest extends MediaWikiTestCase {
/**
* @covers PageArchive::undelete
+ * @covers PageArchive::undeleteRevisions
*/
public function testUndeleteRevisions() {
// First make sure old revisions are archived
$dbr = wfGetDB( DB_REPLICA );
- $res = $dbr->select( 'archive', '*', [ 'ar_rev_id' => $this->ipRevId ] );
+ $arQuery = Revision::getArchiveQueryInfo();
+ $res = $dbr->select(
+ $arQuery['tables'],
+ $arQuery['fields'],
+ [ 'ar_rev_id' => $this->ipRevId ],
+ __METHOD__,
+ [],
+ $arQuery['joins']
+ );
$row = $res->fetchObject();
$this->assertEquals( $this->ipEditor, $row->ar_user_text );
// Should not be in revision
- $res = $dbr->select( 'revision', '*', [ 'rev_id' => $this->ipRevId ] );
+ $res = $dbr->select( 'revision', '1', [ 'rev_id' => $this->ipRevId ] );
$this->assertFalse( $res->fetchObject() );
// Should not be in ip_changes
- $res = $dbr->select( 'ip_changes', '*', [ 'ipc_rev_id' => $this->ipRevId ] );
+ $res = $dbr->select( 'ip_changes', '1', [ 'ipc_rev_id' => $this->ipRevId ] );
$this->assertFalse( $res->fetchObject() );
// Restore the page
$this->archivedPage->undelete( [] );
// Should be back in revision
- $res = $dbr->select( 'revision', '*', [ 'rev_id' => $this->ipRevId ] );
+ $revQuery = Revision::getQueryInfo();
+ $res = $dbr->select(
+ $revQuery['tables'],
+ $revQuery['fields'],
+ [ 'rev_id' => $this->ipRevId ],
+ __METHOD__,
+ [],
+ $revQuery['joins']
+ );
$row = $res->fetchObject();
$this->assertEquals( $this->ipEditor, $row->rev_user_text );
// Should be back in ip_changes
- $res = $dbr->select( 'ip_changes', '*', [ 'ipc_rev_id' => $this->ipRevId ] );
+ $res = $dbr->select( 'ip_changes', [ 'ipc_hex' ], [ 'ipc_rev_id' => $this->ipRevId ] );
$row = $res->fetchObject();
$this->assertEquals( IP::toHex( $this->ipEditor ), $row->ipc_hex );
}
+
+ /**
+ * @covers PageArchive::listRevisions
+ */
+ public function testListRevisions() {
+ $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->overrideMwServices();
+
+ $revisions = $this->archivedPage->listRevisions();
+ $this->assertEquals( 2, $revisions->numRows() );
+
+ // Get the rows as arrays
+ $row1 = (array)$revisions->current();
+ $row2 = (array)$revisions->next();
+ // Unset the timestamps (we assume they will be right...
+ $this->assertInternalType( 'string', $row1['ar_timestamp'] );
+ $this->assertInternalType( 'string', $row2['ar_timestamp'] );
+ unset( $row1['ar_timestamp'] );
+ unset( $row2['ar_timestamp'] );
+
+ $this->assertEquals(
+ [
+ 'ar_minor_edit' => '0',
+ 'ar_user' => '0',
+ 'ar_user_text' => '2600:387:ed7:947e:8c16:a1ad:dd34:1dd7',
+ 'ar_actor' => null,
+ 'ar_len' => '11',
+ 'ar_deleted' => '0',
+ 'ar_rev_id' => '3',
+ 'ar_sha1' => '0qdrpxl537ivfnx4gcpnzz0285yxryy',
+ 'ar_page_id' => '2',
+ 'ar_comment_text' => 'just a test',
+ 'ar_comment_data' => null,
+ 'ar_comment_cid' => null,
+ 'ar_content_format' => null,
+ 'ar_content_model' => null,
+ 'ts_tags' => null,
+ 'ar_id' => '2',
+ 'ar_namespace' => '0',
+ 'ar_title' => 'PageArchiveTest_thePage',
+ 'ar_text_id' => '3',
+ 'ar_parent_id' => '2',
+ ],
+ $row1
+ );
+ $this->assertEquals(
+ [
+ 'ar_minor_edit' => '0',
+ 'ar_user' => '0',
+ 'ar_user_text' => '127.0.0.1',
+ 'ar_actor' => null,
+ 'ar_len' => '7',
+ 'ar_deleted' => '0',
+ 'ar_rev_id' => '2',
+ 'ar_sha1' => 'pr0s8e18148pxhgjfa0gjrvpy8fiyxc',
+ 'ar_page_id' => '2',
+ 'ar_comment_text' => 'testing',
+ 'ar_comment_data' => null,
+ 'ar_comment_cid' => null,
+ 'ar_content_format' => null,
+ 'ar_content_model' => null,
+ 'ts_tags' => null,
+ 'ar_id' => '1',
+ 'ar_namespace' => '0',
+ 'ar_title' => 'PageArchiveTest_thePage',
+ 'ar_text_id' => '2',
+ 'ar_parent_id' => '0',
+ ],
+ $row2
+ );
+ }
+
+ /**
+ * @covers PageArchive::listPagesBySearch
+ */
+ public function testListPagesBySearch() {
+ $pages = PageArchive::listPagesBySearch( 'PageArchiveTest_thePage' );
+ $this->assertSame( 1, $pages->numRows() );
+
+ $page = (array)$pages->current();
+
+ $this->assertSame(
+ [
+ 'ar_namespace' => '0',
+ 'ar_title' => 'PageArchiveTest_thePage',
+ 'count' => '2',
+ ],
+ $page
+ );
+ }
+
+ /**
+ * @covers PageArchive::listPagesBySearch
+ */
+ public function testListPagesByPrefix() {
+ $pages = PageArchive::listPagesByPrefix( 'PageArchiveTest' );
+ $this->assertSame( 1, $pages->numRows() );
+
+ $page = (array)$pages->current();
+
+ $this->assertSame(
+ [
+ 'ar_namespace' => '0',
+ 'ar_title' => 'PageArchiveTest_thePage',
+ 'count' => '2',
+ ],
+ $page
+ );
+ }
+
+ /**
+ * @covers PageArchive::getTextFromRow
+ */
+ public function testGetTextFromRow() {
+ $row = (object)[ 'ar_text_id' => 2 ];
+ $text = $this->archivedPage->getTextFromRow( $row );
+ $this->assertSame( 'testing', $text );
+ }
+
+ /**
+ * @covers PageArchive::getLastRevisionText
+ */
+ public function testGetLastRevisionText() {
+ $text = $this->archivedPage->getLastRevisionText();
+ $this->assertSame( 'Lorem Ipsum', $text );
+ }
+
+ /**
+ * @covers PageArchive::isDeleted
+ */
+ public function testIsDeleted() {
+ $this->assertTrue( $this->archivedPage->isDeleted() );
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/PagePropsTest.php b/www/wiki/tests/phpunit/includes/PagePropsTest.php
index 29c9e228..f602cdab 100644
--- a/www/wiki/tests/phpunit/includes/PagePropsTest.php
+++ b/www/wiki/tests/phpunit/includes/PagePropsTest.php
@@ -1,13 +1,15 @@
<?php
/**
+ * @covers PageProps
+ *
* @group Database
* ^--- tell jenkins this test needs the database
*
* @group medium
* ^--- tell phpunit that these test cases may take longer than 2 seconds.
*/
-class TestPageProps extends MediaWikiLangTestCase {
+class PagePropsTest extends MediaWikiLangTestCase {
/**
* @var Title $title1
@@ -35,7 +37,7 @@ class TestPageProps extends MediaWikiLangTestCase {
$wgNamespaceContentModels[12312] = 'DUMMY';
$wgContentHandlers['DUMMY'] = 'DummyContentHandlerForTesting';
- MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ MWNamespace::clearCaches();
$wgContLang->resetNamespaces(); # reset namespace cache
if ( !$this->the_properties ) {
@@ -81,7 +83,7 @@ class TestPageProps extends MediaWikiLangTestCase {
unset( $wgNamespaceContentModels[12312] );
unset( $wgContentHandlers['DUMMY'] );
- MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ MWNamespace::clearCaches();
$wgContLang->resetNamespaces(); # reset namespace cache
}
diff --git a/www/wiki/tests/phpunit/includes/PathRouterTest.php b/www/wiki/tests/phpunit/includes/PathRouterTest.php
index 67364cb3..fc6a70b1 100644
--- a/www/wiki/tests/phpunit/includes/PathRouterTest.php
+++ b/www/wiki/tests/phpunit/includes/PathRouterTest.php
@@ -236,7 +236,7 @@ class PathRouterTest extends MediaWikiTestCase {
* Ensure the router doesn't choke on long paths.
*/
public function testLength() {
- // @codingStandardsIgnoreStart Ignore long line warnings
+ // phpcs:disable Generic.Files.LineLength
$matches = $this->basicRouter->parse(
"/wiki/Lorem_ipsum_dolor_sit_amet,_consectetur_adipisicing_elit,_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua._Ut_enim_ad_minim_veniam,_quis_nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat._Duis_aute_irure_dolor_in_reprehenderit_in_voluptate_velit_esse_cillum_dolore_eu_fugiat_nulla_pariatur._Excepteur_sint_occaecat_cupidatat_non_proident,_sunt_in_culpa_qui_officia_deserunt_mollit_anim_id_est_laborum."
);
@@ -244,7 +244,7 @@ class PathRouterTest extends MediaWikiTestCase {
$matches,
[ 'title' => "Lorem_ipsum_dolor_sit_amet,_consectetur_adipisicing_elit,_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua._Ut_enim_ad_minim_veniam,_quis_nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat._Duis_aute_irure_dolor_in_reprehenderit_in_voluptate_velit_esse_cillum_dolore_eu_fugiat_nulla_pariatur._Excepteur_sint_occaecat_cupidatat_non_proident,_sunt_in_culpa_qui_officia_deserunt_mollit_anim_id_est_laborum." ]
);
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
/**
diff --git a/www/wiki/tests/phpunit/includes/PreferencesTest.php b/www/wiki/tests/phpunit/includes/PreferencesTest.php
index d78c1e7d..4d3b195c 100644
--- a/www/wiki/tests/phpunit/includes/PreferencesTest.php
+++ b/www/wiki/tests/phpunit/includes/PreferencesTest.php
@@ -44,6 +44,7 @@ class PreferencesTest extends MediaWikiTestCase {
/**
* Placeholder to verify T36302
* @covers Preferences::profilePreferences
+ * @deprecated replaced by DefaultPreferencesFactoryTest::testEmailAuthentication()
*/
public function testEmailAuthenticationFieldWhenUserHasNoEmail() {
$prefs = $this->prefsFor( 'noemail' );
@@ -56,6 +57,7 @@ class PreferencesTest extends MediaWikiTestCase {
/**
* Placeholder to verify T36302
* @covers Preferences::profilePreferences
+ * @deprecated replaced by DefaultPreferencesFactoryTest::testEmailAuthentication()
*/
public function testEmailAuthenticationFieldWhenUserEmailNotAuthenticated() {
$prefs = $this->prefsFor( 'notauth' );
@@ -68,6 +70,7 @@ class PreferencesTest extends MediaWikiTestCase {
/**
* Placeholder to verify T36302
* @covers Preferences::profilePreferences
+ * @deprecated replaced by DefaultPreferencesFactoryTest::testEmailAuthentication()
*/
public function testEmailAuthenticationFieldWhenUserEmailIsAuthenticated() {
$prefs = $this->prefsFor( 'auth' );
@@ -77,86 +80,11 @@ class PreferencesTest extends MediaWikiTestCase {
$this->assertEquals( 'mw-email-authenticated', $prefs['emailauthentication']['cssclass'] );
}
- /**
- * Test that PreferencesFormPreSave hook has correct data:
- * - user Object is passed
- * - oldUserOptions contains previous user options (before save)
- * - formData and User object have set up new properties
- *
- * @see https://phabricator.wikimedia.org/T169365
- * @covers Preferences::tryFormSubmit
- */
- public function testPreferencesFormPreSaveHookHasCorrectData() {
- $oldOptions = [
- 'test' => 'abc',
- 'option' => 'old'
- ];
- $newOptions = [
- 'test' => 'abc',
- 'option' => 'new'
- ];
- $configMock = new HashConfig( [
- 'HiddenPrefs' => []
- ] );
- $form = $this->getMockBuilder( PreferencesForm::class )
- ->disableOriginalConstructor()
- ->getMock();
-
- $userMock = $this->getMockBuilder( User::class )
- ->disableOriginalConstructor()
- ->getMock();
- $userMock->method( 'getOptions' )
- ->willReturn( $oldOptions );
- $userMock->method( 'isAllowedAny' )
- ->willReturn( true );
- $userMock->method( 'isAllowed' )
- ->willReturn( true );
-
- $userMock->expects( $this->exactly( 2 ) )
- ->method( 'setOption' )
- ->withConsecutive(
- [ $this->equalTo( 'test' ), $this->equalTo( $newOptions[ 'test' ] ) ],
- [ $this->equalTo( 'option' ), $this->equalTo( $newOptions[ 'option' ] ) ]
- );
-
- $form->expects( $this->any() )
- ->method( 'getModifiedUser' )
- ->willReturn( $userMock );
-
- $form->expects( $this->any() )
- ->method( 'getContext' )
- ->willReturn( $this->context );
-
- $form->expects( $this->any() )
- ->method( 'getConfig' )
- ->willReturn( $configMock );
-
- $this->setTemporaryHook( 'PreferencesFormPreSave', function (
- $formData, $form, $user, &$result, $oldUserOptions )
- use ( $newOptions, $oldOptions, $userMock ) {
- $this->assertSame( $userMock, $user );
- foreach ( $newOptions as $option => $value ) {
- $this->assertSame( $value, $formData[ $option ] );
- }
- foreach ( $oldOptions as $option => $value ) {
- $this->assertSame( $value, $oldUserOptions[ $option ] );
- }
- $this->assertEquals( true, $result );
- }
- );
-
- Preferences::tryFormSubmit( $newOptions, $form );
- }
-
/** Helper */
protected function prefsFor( $user_key ) {
- $preferences = [];
- Preferences::profilePreferences(
+ return Preferences::getPreferences(
$this->prefUsers[$user_key],
- $this->context,
- $preferences
+ $this->context
);
-
- return $preferences;
}
}
diff --git a/www/wiki/tests/phpunit/includes/PrefixSearchTest.php b/www/wiki/tests/phpunit/includes/PrefixSearchTest.php
index a6cf14a3..ed34a8ab 100644
--- a/www/wiki/tests/phpunit/includes/PrefixSearchTest.php
+++ b/www/wiki/tests/phpunit/includes/PrefixSearchTest.php
@@ -58,20 +58,23 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
'wgCapitalLinkOverrides' => [ self::NS_NONCAP => false ],
] );
- $this->originalHandlers = TestingAccessWrapper::newFromClass( 'Hooks' )->handlers;
- TestingAccessWrapper::newFromClass( 'Hooks' )->handlers = [];
+ $this->originalHandlers = TestingAccessWrapper::newFromClass( Hooks::class )->handlers;
+ TestingAccessWrapper::newFromClass( Hooks::class )->handlers = [];
// Clear caches so that our new namespace appears
- MWNamespace::getCanonicalNamespaces( true );
+ MWNamespace::clearCaches();
Language::factory( 'en' )->resetNamespaces();
SpecialPageFactory::resetList();
}
public function tearDown() {
+ MWNamespace::clearCaches();
+ Language::factory( 'en' )->resetNamespaces();
+
parent::tearDown();
- TestingAccessWrapper::newFromClass( 'Hooks' )->handlers = $this->originalHandlers;
+ TestingAccessWrapper::newFromClass( Hooks::class )->handlers = $this->originalHandlers;
SpecialPageFactory::resetList();
}
diff --git a/www/wiki/tests/phpunit/includes/RevisionContentHandlerDbTest.php b/www/wiki/tests/phpunit/includes/RevisionContentHandlerDbTest.php
new file mode 100644
index 00000000..fa0153d3
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/RevisionContentHandlerDbTest.php
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * @group Database
+ * @group medium
+ * @group ContentHandler
+ */
+class RevisionContentHandlerDbTest extends RevisionDbTestBase {
+
+ protected function getContentHandlerUseDB() {
+ return true;
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/RevisionDbTestBase.php b/www/wiki/tests/phpunit/includes/RevisionDbTestBase.php
new file mode 100644
index 00000000..5de34d1b
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/RevisionDbTestBase.php
@@ -0,0 +1,1505 @@
+<?php
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\RevisionStore;
+use MediaWiki\Storage\IncompleteRevisionException;
+use MediaWiki\Storage\RevisionRecord;
+
+/**
+ * RevisionDbTestBase contains test cases for the Revision class that have Database interactions.
+ *
+ * @group Database
+ * @group medium
+ */
+abstract class RevisionDbTestBase extends MediaWikiTestCase {
+
+ /**
+ * @var WikiPage $testPage
+ */
+ private $testPage;
+
+ public function __construct( $name = null, array $data = [], $dataName = '' ) {
+ parent::__construct( $name, $data, $dataName );
+
+ $this->tablesUsed = array_merge( $this->tablesUsed,
+ [
+ 'page',
+ 'revision',
+ 'ip_changes',
+ 'text',
+ 'archive',
+
+ 'recentchanges',
+ 'logging',
+
+ 'page_props',
+ 'pagelinks',
+ 'categorylinks',
+ 'langlinks',
+ 'externallinks',
+ 'imagelinks',
+ 'templatelinks',
+ 'iwlinks'
+ ]
+ );
+ }
+
+ protected function setUp() {
+ global $wgContLang;
+
+ parent::setUp();
+
+ $this->mergeMwGlobalArrayValue(
+ 'wgExtraNamespaces',
+ [
+ 12312 => 'Dummy',
+ 12313 => 'Dummy_talk',
+ ]
+ );
+
+ $this->mergeMwGlobalArrayValue(
+ 'wgNamespaceContentModels',
+ [
+ 12312 => DummyContentForTesting::MODEL_ID,
+ ]
+ );
+
+ $this->mergeMwGlobalArrayValue(
+ 'wgContentHandlers',
+ [
+ DummyContentForTesting::MODEL_ID => 'DummyContentHandlerForTesting',
+ RevisionTestModifyableContent::MODEL_ID => 'RevisionTestModifyableContentHandler',
+ ]
+ );
+
+ $this->setMwGlobals( 'wgContentHandlerUseDB', $this->getContentHandlerUseDB() );
+
+ MWNamespace::clearCaches();
+ // Reset namespace cache
+ $wgContLang->resetNamespaces();
+
+ if ( !$this->testPage ) {
+ /**
+ * We have to create a new page for each subclass as the page creation may result
+ * in different DB fields being filled based on configuration.
+ */
+ $this->testPage = $this->createPage( __CLASS__, __CLASS__ );
+ }
+ }
+
+ protected function tearDown() {
+ global $wgContLang;
+
+ parent::tearDown();
+
+ MWNamespace::clearCaches();
+ // Reset namespace cache
+ $wgContLang->resetNamespaces();
+ }
+
+ abstract protected function getContentHandlerUseDB();
+
+ private function makeRevisionWithProps( $props = null ) {
+ if ( $props === null ) {
+ $props = [];
+ }
+
+ if ( !isset( $props['content'] ) && !isset( $props['text'] ) ) {
+ $props['text'] = 'Lorem Ipsum';
+ }
+
+ if ( !isset( $props['user_text'] ) ) {
+ $user = $this->getTestUser()->getUser();
+ $props['user_text'] = $user->getName();
+ $props['user'] = $user->getId();
+ }
+
+ if ( !isset( $props['user'] ) ) {
+ $props['user'] = 0;
+ }
+
+ if ( !isset( $props['comment'] ) ) {
+ $props['comment'] = 'just a test';
+ }
+
+ if ( !isset( $props['page'] ) ) {
+ $props['page'] = $this->testPage->getId();
+ }
+
+ if ( !isset( $props['content_model'] ) ) {
+ $props['content_model'] = CONTENT_MODEL_WIKITEXT;
+ }
+
+ $rev = new Revision( $props );
+
+ $dbw = wfGetDB( DB_MASTER );
+ $rev->insertOn( $dbw );
+
+ return $rev;
+ }
+
+ /**
+ * @param string $titleString
+ * @param string $text
+ * @param string|null $model
+ *
+ * @return WikiPage
+ */
+ private function createPage( $titleString, $text, $model = null ) {
+ if ( !preg_match( '/:/', $titleString ) &&
+ ( $model === null || $model === CONTENT_MODEL_WIKITEXT )
+ ) {
+ $ns = $this->getDefaultWikitextNS();
+ $titleString = MWNamespace::getCanonicalName( $ns ) . ':' . $titleString;
+ }
+
+ $title = Title::newFromText( $titleString );
+ $wikipage = new WikiPage( $title );
+
+ // Delete the article if it already exists
+ if ( $wikipage->exists() ) {
+ $wikipage->doDeleteArticle( "done" );
+ }
+
+ $content = ContentHandler::makeContent( $text, $title, $model );
+ $wikipage->doEditContent( $content, __METHOD__, EDIT_NEW );
+
+ return $wikipage;
+ }
+
+ private function assertRevEquals( Revision $orig, Revision $rev = null ) {
+ $this->assertNotNull( $rev, 'missing revision' );
+
+ $this->assertEquals( $orig->getId(), $rev->getId() );
+ $this->assertEquals( $orig->getPage(), $rev->getPage() );
+ $this->assertEquals( $orig->getTimestamp(), $rev->getTimestamp() );
+ $this->assertEquals( $orig->getUser(), $rev->getUser() );
+ $this->assertEquals( $orig->getContentModel(), $rev->getContentModel() );
+ $this->assertEquals( $orig->getContentFormat(), $rev->getContentFormat() );
+ $this->assertEquals( $orig->getSha1(), $rev->getSha1() );
+ }
+
+ /**
+ * @covers Revision::getRecentChange
+ */
+ public function testGetRecentChange() {
+ $rev = $this->testPage->getRevision();
+ $recentChange = $rev->getRecentChange();
+
+ // Make sure various attributes look right / the correct entry has been retrieved.
+ $this->assertEquals( $rev->getTimestamp(), $recentChange->getAttribute( 'rc_timestamp' ) );
+ $this->assertEquals(
+ $rev->getTitle()->getNamespace(),
+ $recentChange->getAttribute( 'rc_namespace' )
+ );
+ $this->assertEquals(
+ $rev->getTitle()->getDBkey(),
+ $recentChange->getAttribute( 'rc_title' )
+ );
+ $this->assertEquals( $rev->getUser(), $recentChange->getAttribute( 'rc_user' ) );
+ $this->assertEquals( $rev->getUserText(), $recentChange->getAttribute( 'rc_user_text' ) );
+ $this->assertEquals( $rev->getComment(), $recentChange->getAttribute( 'rc_comment' ) );
+ $this->assertEquals( $rev->getPage(), $recentChange->getAttribute( 'rc_cur_id' ) );
+ $this->assertEquals( $rev->getId(), $recentChange->getAttribute( 'rc_this_oldid' ) );
+ }
+
+ /**
+ * @covers Revision::insertOn
+ */
+ public function testInsertOn_success() {
+ $parentId = $this->testPage->getLatest();
+
+ // If an ExternalStore is set don't use it.
+ $this->setMwGlobals( 'wgDefaultExternalStore', false );
+
+ $rev = new Revision( [
+ 'page' => $this->testPage->getId(),
+ 'title' => $this->testPage->getTitle(),
+ 'text' => 'Revision Text',
+ 'comment' => 'Revision comment',
+ ] );
+
+ $revId = $rev->insertOn( wfGetDB( DB_MASTER ) );
+
+ $this->assertInternalType( 'integer', $revId );
+ $this->assertSame( $revId, $rev->getId() );
+
+ // getTextId() must be an int!
+ $this->assertInternalType( 'integer', $rev->getTextId() );
+
+ $mainSlot = $rev->getRevisionRecord()->getSlot( 'main', RevisionRecord::RAW );
+
+ // we currently only support storage in the text table
+ $textId = MediaWikiServices::getInstance()
+ ->getBlobStore()
+ ->getTextIdFromAddress( $mainSlot->getAddress() );
+
+ $this->assertSelect(
+ 'text',
+ [ 'old_id', 'old_text' ],
+ "old_id = $textId",
+ [ [ strval( $textId ), 'Revision Text' ] ]
+ );
+ $this->assertSelect(
+ 'revision',
+ [
+ 'rev_id',
+ 'rev_page',
+ 'rev_text_id',
+ 'rev_minor_edit',
+ 'rev_deleted',
+ 'rev_len',
+ 'rev_parent_id',
+ 'rev_sha1',
+ ],
+ "rev_id = {$rev->getId()}",
+ [ [
+ strval( $rev->getId() ),
+ strval( $this->testPage->getId() ),
+ strval( $textId ),
+ '0',
+ '0',
+ '13',
+ strval( $parentId ),
+ 's0ngbdoxagreuf2vjtuxzwdz64n29xm',
+ ] ]
+ );
+ }
+
+ /**
+ * @covers Revision::insertOn
+ */
+ public function testInsertOn_exceptionOnNoPage() {
+ // If an ExternalStore is set don't use it.
+ $this->setMwGlobals( 'wgDefaultExternalStore', false );
+ $this->setExpectedException(
+ IncompleteRevisionException::class,
+ "rev_page field must not be 0!"
+ );
+
+ $title = Title::newFromText( 'Nonexistant-' . __METHOD__ );
+ $rev = new Revision( [], 0, $title );
+
+ $rev->insertOn( wfGetDB( DB_MASTER ) );
+ }
+
+ /**
+ * @covers Revision::newFromTitle
+ */
+ public function testNewFromTitle_withoutId() {
+ $latestRevId = $this->testPage->getLatest();
+
+ $rev = Revision::newFromTitle( $this->testPage->getTitle() );
+
+ $this->assertTrue( $this->testPage->getTitle()->equals( $rev->getTitle() ) );
+ $this->assertEquals( $latestRevId, $rev->getId() );
+ }
+
+ /**
+ * @covers Revision::newFromTitle
+ */
+ public function testNewFromTitle_withId() {
+ $latestRevId = $this->testPage->getLatest();
+
+ $rev = Revision::newFromTitle( $this->testPage->getTitle(), $latestRevId );
+
+ $this->assertTrue( $this->testPage->getTitle()->equals( $rev->getTitle() ) );
+ $this->assertEquals( $latestRevId, $rev->getId() );
+ }
+
+ /**
+ * @covers Revision::newFromTitle
+ */
+ public function testNewFromTitle_withBadId() {
+ $latestRevId = $this->testPage->getLatest();
+
+ $rev = Revision::newFromTitle( $this->testPage->getTitle(), $latestRevId + 1 );
+
+ $this->assertNull( $rev );
+ }
+
+ /**
+ * @covers Revision::newFromRow
+ */
+ public function testNewFromRow() {
+ $orig = $this->makeRevisionWithProps();
+
+ $dbr = wfGetDB( DB_REPLICA );
+ $revQuery = Revision::getQueryInfo();
+ $res = $dbr->select( $revQuery['tables'], $revQuery['fields'], [ 'rev_id' => $orig->getId() ],
+ __METHOD__, [], $revQuery['joins'] );
+ $this->assertTrue( is_object( $res ), 'query failed' );
+
+ $row = $res->fetchObject();
+ $res->free();
+
+ $rev = Revision::newFromRow( $row );
+
+ $this->assertRevEquals( $orig, $rev );
+ }
+
+ public function provideNewFromArchiveRow() {
+ yield [
+ function ( $f ) {
+ return $f;
+ },
+ ];
+ yield [
+ function ( $f ) {
+ return $f + [ 'ar_namespace', 'ar_title' ];
+ },
+ ];
+ yield [
+ function ( $f ) {
+ unset( $f['ar_text_id'] );
+ return $f;
+ },
+ ];
+ yield [
+ function ( $f ) {
+ unset( $f['ar_page_id'] );
+ return $f;
+ },
+ ];
+ yield [
+ function ( $f ) {
+ unset( $f['ar_parent_id'] );
+ return $f;
+ },
+ ];
+ yield [
+ function ( $f ) {
+ unset( $f['ar_rev_id'] );
+ return $f;
+ },
+ ];
+ yield [
+ function ( $f ) {
+ unset( $f['ar_sha1'] );
+ return $f;
+ },
+ ];
+ }
+
+ /**
+ * @dataProvider provideNewFromArchiveRow
+ * @covers Revision::newFromArchiveRow
+ */
+ public function testNewFromArchiveRow( $selectModifier ) {
+ $services = MediaWikiServices::getInstance();
+
+ $store = new RevisionStore(
+ $services->getDBLoadBalancer(),
+ $services->getService( '_SqlBlobStore' ),
+ $services->getMainWANObjectCache(),
+ $services->getCommentStore(),
+ $services->getActorMigration()
+ );
+
+ $store->setContentHandlerUseDB( $this->getContentHandlerUseDB() );
+ $this->setService( 'RevisionStore', $store );
+
+ $page = $this->createPage(
+ 'RevisionStorageTest_testNewFromArchiveRow',
+ 'Lorem Ipsum',
+ CONTENT_MODEL_WIKITEXT
+ );
+ $orig = $page->getRevision();
+ $page->doDeleteArticle( 'test Revision::newFromArchiveRow' );
+
+ $dbr = wfGetDB( DB_REPLICA );
+ $arQuery = Revision::getArchiveQueryInfo();
+ $arQuery['fields'] = $selectModifier( $arQuery['fields'] );
+ $res = $dbr->select(
+ $arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
+ __METHOD__, [], $arQuery['joins']
+ );
+ $this->assertTrue( is_object( $res ), 'query failed' );
+
+ $row = $res->fetchObject();
+ $res->free();
+
+ // MCR migration note: $row is now required to contain ar_title and ar_namespace.
+ // Alternatively, a Title object can be passed to RevisionStore::newRevisionFromArchiveRow
+ $rev = Revision::newFromArchiveRow( $row );
+
+ $this->assertRevEquals( $orig, $rev );
+ }
+
+ /**
+ * @covers Revision::newFromArchiveRow
+ */
+ public function testNewFromArchiveRowOverrides() {
+ $page = $this->createPage(
+ 'RevisionStorageTest_testNewFromArchiveRow',
+ 'Lorem Ipsum',
+ CONTENT_MODEL_WIKITEXT
+ );
+ $orig = $page->getRevision();
+ $page->doDeleteArticle( 'test Revision::newFromArchiveRow' );
+
+ $dbr = wfGetDB( DB_REPLICA );
+ $arQuery = Revision::getArchiveQueryInfo();
+ $res = $dbr->select(
+ $arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
+ __METHOD__, [], $arQuery['joins']
+ );
+ $this->assertTrue( is_object( $res ), 'query failed' );
+
+ $row = $res->fetchObject();
+ $res->free();
+
+ $rev = Revision::newFromArchiveRow( $row, [ 'comment_text' => 'SOMEOVERRIDE' ] );
+
+ $this->assertNotEquals( $orig->getComment(), $rev->getComment() );
+ $this->assertEquals( 'SOMEOVERRIDE', $rev->getComment() );
+ }
+
+ /**
+ * @covers Revision::newFromId
+ */
+ public function testNewFromId() {
+ $orig = $this->testPage->getRevision();
+ $rev = Revision::newFromId( $orig->getId() );
+ $this->assertRevEquals( $orig, $rev );
+ }
+
+ /**
+ * @covers Revision::newFromPageId
+ */
+ public function testNewFromPageId() {
+ $rev = Revision::newFromPageId( $this->testPage->getId() );
+ $this->assertRevEquals(
+ $this->testPage->getRevision(),
+ $rev
+ );
+ }
+
+ /**
+ * @covers Revision::newFromPageId
+ */
+ public function testNewFromPageIdWithLatestId() {
+ $rev = Revision::newFromPageId(
+ $this->testPage->getId(),
+ $this->testPage->getLatest()
+ );
+ $this->assertRevEquals(
+ $this->testPage->getRevision(),
+ $rev
+ );
+ }
+
+ /**
+ * @covers Revision::newFromPageId
+ */
+ public function testNewFromPageIdWithNotLatestId() {
+ $content = new WikitextContent( __METHOD__ );
+ $this->testPage->doEditContent( $content, __METHOD__ );
+ $rev = Revision::newFromPageId(
+ $this->testPage->getId(),
+ $this->testPage->getRevision()->getPrevious()->getId()
+ );
+ $this->assertRevEquals(
+ $this->testPage->getRevision()->getPrevious(),
+ $rev
+ );
+ }
+
+ /**
+ * @covers Revision::fetchRevision
+ */
+ public function testFetchRevision() {
+ // Hidden process cache assertion below
+ $this->testPage->getRevision()->getId();
+
+ $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
+ $id = $this->testPage->getRevision()->getId();
+
+ $this->hideDeprecated( 'Revision::fetchRevision' );
+ $res = Revision::fetchRevision( $this->testPage->getTitle() );
+
+ # note: order is unspecified
+ $rows = [];
+ while ( ( $row = $res->fetchObject() ) ) {
+ $rows[$row->rev_id] = $row;
+ }
+
+ $this->assertEmpty( $rows, 'expected empty set' );
+ }
+
+ /**
+ * @covers Revision::getPage
+ */
+ public function testGetPage() {
+ $page = $this->testPage;
+
+ $orig = $this->makeRevisionWithProps( [ 'page' => $page->getId() ] );
+ $rev = Revision::newFromId( $orig->getId() );
+
+ $this->assertEquals( $page->getId(), $rev->getPage() );
+ }
+
+ /**
+ * @covers Revision::isCurrent
+ */
+ public function testIsCurrent() {
+ $rev1 = $this->testPage->getRevision();
+
+ # @todo find out if this should be true
+ # $this->assertTrue( $rev1->isCurrent() );
+
+ $rev1x = Revision::newFromId( $rev1->getId() );
+ $this->assertTrue( $rev1x->isCurrent() );
+
+ $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
+ $rev2 = $this->testPage->getRevision();
+
+ # @todo find out if this should be true
+ # $this->assertTrue( $rev2->isCurrent() );
+
+ $rev1x = Revision::newFromId( $rev1->getId() );
+ $this->assertFalse( $rev1x->isCurrent() );
+
+ $rev2x = Revision::newFromId( $rev2->getId() );
+ $this->assertTrue( $rev2x->isCurrent() );
+ }
+
+ /**
+ * @covers Revision::getPrevious
+ */
+ public function testGetPrevious() {
+ $oldestRevision = $this->testPage->getOldestRevision();
+ $latestRevision = $this->testPage->getLatest();
+
+ $this->assertNull( $oldestRevision->getPrevious() );
+
+ $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
+ $newRevision = $this->testPage->getRevision();
+
+ $this->assertNotNull( $newRevision->getPrevious() );
+ $this->assertEquals( $latestRevision, $newRevision->getPrevious()->getId() );
+ }
+
+ /**
+ * @covers Revision::getNext
+ */
+ public function testGetNext() {
+ $rev1 = $this->testPage->getRevision();
+
+ $this->assertNull( $rev1->getNext() );
+
+ $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
+ $rev2 = $this->testPage->getRevision();
+
+ $this->assertNotNull( $rev1->getNext() );
+ $this->assertEquals( $rev2->getId(), $rev1->getNext()->getId() );
+ }
+
+ /**
+ * @covers Revision::newNullRevision
+ */
+ public function testNewNullRevision() {
+ $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
+ $orig = $this->testPage->getRevision();
+
+ $dbw = wfGetDB( DB_MASTER );
+ $rev = Revision::newNullRevision( $dbw, $this->testPage->getId(), 'a null revision', false );
+
+ $this->assertNotEquals( $orig->getId(), $rev->getId(),
+ 'new null revision should have a different id from the original revision' );
+ $this->assertEquals( $orig->getTextId(), $rev->getTextId(),
+ 'new null revision should have the same text id as the original revision' );
+ $this->assertEquals( $orig->getSha1(), $rev->getSha1(),
+ 'new null revision should have the same SHA1 as the original revision' );
+ $this->assertTrue( $orig->getRevisionRecord()->hasSameContent( $rev->getRevisionRecord() ),
+ 'new null revision should have the same content as the original revision' );
+ $this->assertEquals( __METHOD__, $rev->getContent()->getNativeData() );
+ }
+
+ /**
+ * @covers Revision::newNullRevision
+ */
+ public function testNewNullRevision_badPage() {
+ $dbw = wfGetDB( DB_MASTER );
+ $rev = Revision::newNullRevision( $dbw, -1, 'a null revision', false );
+
+ $this->assertNull( $rev );
+ }
+
+ /**
+ * @covers Revision::insertOn
+ */
+ public function testInsertOn() {
+ $ip = '2600:387:ed7:947e:8c16:a1ad:dd34:1dd7';
+
+ $orig = $this->makeRevisionWithProps( [
+ 'user_text' => $ip
+ ] );
+
+ // Make sure the revision was copied to ip_changes
+ $dbr = wfGetDB( DB_REPLICA );
+ $res = $dbr->select( 'ip_changes', '*', [ 'ipc_rev_id' => $orig->getId() ] );
+ $row = $res->fetchObject();
+
+ $this->assertEquals( IP::toHex( $ip ), $row->ipc_hex );
+ $this->assertEquals(
+ $orig->getTimestamp(),
+ wfTimestamp( TS_MW, $row->ipc_rev_timestamp )
+ );
+ }
+
+ public static function provideUserWasLastToEdit() {
+ yield 'actually the last edit' => [ 3, true ];
+ yield 'not the current edit, but still by this user' => [ 2, true ];
+ yield 'edit by another user' => [ 1, false ];
+ yield 'first edit, by this user, but another user edited in the mean time' => [ 0, false ];
+ }
+
+ /**
+ * @covers Revision::userWasLastToEdit
+ * @dataProvider provideUserWasLastToEdit
+ */
+ public function testUserWasLastToEdit( $sinceIdx, $expectedLast ) {
+ $userA = User::newFromName( "RevisionStorageTest_userA" );
+ $userB = User::newFromName( "RevisionStorageTest_userB" );
+
+ if ( $userA->getId() === 0 ) {
+ $userA = User::createNew( $userA->getName() );
+ }
+
+ if ( $userB->getId() === 0 ) {
+ $userB = User::createNew( $userB->getName() );
+ }
+
+ $ns = $this->getDefaultWikitextNS();
+
+ $dbw = wfGetDB( DB_MASTER );
+ $revisions = [];
+
+ // create revisions -----------------------------
+ $page = WikiPage::factory( Title::newFromText(
+ 'RevisionStorageTest_testUserWasLastToEdit', $ns ) );
+ $page->insertOn( $dbw );
+
+ $revisions[0] = new Revision( [
+ 'page' => $page->getId(),
+ // we need the title to determine the page's default content model
+ 'title' => $page->getTitle(),
+ 'timestamp' => '20120101000000',
+ 'user' => $userA->getId(),
+ 'text' => 'zero',
+ 'content_model' => CONTENT_MODEL_WIKITEXT,
+ 'comment' => 'edit zero'
+ ] );
+ $revisions[0]->insertOn( $dbw );
+
+ $revisions[1] = new Revision( [
+ 'page' => $page->getId(),
+ // still need the title, because $page->getId() is 0 (there's no entry in the page table)
+ 'title' => $page->getTitle(),
+ 'timestamp' => '20120101000100',
+ 'user' => $userA->getId(),
+ 'text' => 'one',
+ 'content_model' => CONTENT_MODEL_WIKITEXT,
+ 'comment' => 'edit one'
+ ] );
+ $revisions[1]->insertOn( $dbw );
+
+ $revisions[2] = new Revision( [
+ 'page' => $page->getId(),
+ 'title' => $page->getTitle(),
+ 'timestamp' => '20120101000200',
+ 'user' => $userB->getId(),
+ 'text' => 'two',
+ 'content_model' => CONTENT_MODEL_WIKITEXT,
+ 'comment' => 'edit two'
+ ] );
+ $revisions[2]->insertOn( $dbw );
+
+ $revisions[3] = new Revision( [
+ 'page' => $page->getId(),
+ 'title' => $page->getTitle(),
+ 'timestamp' => '20120101000300',
+ 'user' => $userA->getId(),
+ 'text' => 'three',
+ 'content_model' => CONTENT_MODEL_WIKITEXT,
+ 'comment' => 'edit three'
+ ] );
+ $revisions[3]->insertOn( $dbw );
+
+ $revisions[4] = new Revision( [
+ 'page' => $page->getId(),
+ 'title' => $page->getTitle(),
+ 'timestamp' => '20120101000200',
+ 'user' => $userA->getId(),
+ 'text' => 'zero',
+ 'content_model' => CONTENT_MODEL_WIKITEXT,
+ 'comment' => 'edit four'
+ ] );
+ $revisions[4]->insertOn( $dbw );
+
+ // test it ---------------------------------
+ $since = $revisions[$sinceIdx]->getTimestamp();
+
+ $revQuery = Revision::getQueryInfo();
+ $allRows = iterator_to_array( $dbw->select(
+ $revQuery['tables'],
+ [ 'rev_id', 'rev_timestamp', 'rev_user' => $revQuery['fields']['rev_user'] ],
+ [
+ 'rev_page' => $page->getId(),
+ //'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $since ) )
+ ],
+ __METHOD__,
+ [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ],
+ $revQuery['joins']
+ ) );
+
+ $wasLast = Revision::userWasLastToEdit( $dbw, $page->getId(), $userA->getId(), $since );
+
+ $this->assertEquals( $expectedLast, $wasLast );
+ }
+
+ /**
+ * @param string $text
+ * @param string $title
+ * @param string $model
+ * @param string $format
+ *
+ * @return Revision
+ */
+ private function newTestRevision( $text, $title = "Test",
+ $model = CONTENT_MODEL_WIKITEXT, $format = null
+ ) {
+ if ( is_string( $title ) ) {
+ $title = Title::newFromText( $title );
+ }
+
+ $content = ContentHandler::makeContent( $text, $title, $model, $format );
+
+ $rev = new Revision(
+ [
+ 'id' => 42,
+ 'page' => 23,
+ 'title' => $title,
+
+ 'content' => $content,
+ 'length' => $content->getSize(),
+ 'comment' => "testing",
+ 'minor_edit' => false,
+
+ 'content_format' => $format,
+ ]
+ );
+
+ return $rev;
+ }
+
+ public function provideGetContentModel() {
+ // NOTE: we expect the help namespace to always contain wikitext
+ return [
+ [ 'hello world', 'Help:Hello', null, null, CONTENT_MODEL_WIKITEXT ],
+ [ 'hello world', 'User:hello/there.css', null, null, CONTENT_MODEL_CSS ],
+ [ serialize( 'hello world' ), 'Dummy:Hello', null, null, DummyContentForTesting::MODEL_ID ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetContentModel
+ * @covers Revision::getContentModel
+ */
+ public function testGetContentModel( $text, $title, $model, $format, $expectedModel ) {
+ $rev = $this->newTestRevision( $text, $title, $model, $format );
+
+ $this->assertEquals( $expectedModel, $rev->getContentModel() );
+ }
+
+ public function provideGetContentFormat() {
+ // NOTE: we expect the help namespace to always contain wikitext
+ return [
+ [ 'hello world', 'Help:Hello', null, null, CONTENT_FORMAT_WIKITEXT ],
+ [ 'hello world', 'Help:Hello', CONTENT_MODEL_CSS, null, CONTENT_FORMAT_CSS ],
+ [ 'hello world', 'User:hello/there.css', null, null, CONTENT_FORMAT_CSS ],
+ [ serialize( 'hello world' ), 'Dummy:Hello', null, null, DummyContentForTesting::MODEL_ID ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetContentFormat
+ * @covers Revision::getContentFormat
+ */
+ public function testGetContentFormat( $text, $title, $model, $format, $expectedFormat ) {
+ $rev = $this->newTestRevision( $text, $title, $model, $format );
+
+ $this->assertEquals( $expectedFormat, $rev->getContentFormat() );
+ }
+
+ public function provideGetContentHandler() {
+ // NOTE: we expect the help namespace to always contain wikitext
+ return [
+ [ 'hello world', 'Help:Hello', null, null, WikitextContentHandler::class ],
+ [ 'hello world', 'User:hello/there.css', null, null, CssContentHandler::class ],
+ [ serialize( 'hello world' ), 'Dummy:Hello', null, null, DummyContentHandlerForTesting::class ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetContentHandler
+ * @covers Revision::getContentHandler
+ */
+ public function testGetContentHandler( $text, $title, $model, $format, $expectedClass ) {
+ $rev = $this->newTestRevision( $text, $title, $model, $format );
+
+ $this->assertEquals( $expectedClass, get_class( $rev->getContentHandler() ) );
+ }
+
+ public function provideGetContent() {
+ // NOTE: we expect the help namespace to always contain wikitext
+ return [
+ [ 'hello world', 'Help:Hello', null, null, Revision::FOR_PUBLIC, 'hello world' ],
+ [
+ serialize( 'hello world' ),
+ 'Hello',
+ DummyContentForTesting::MODEL_ID,
+ null,
+ Revision::FOR_PUBLIC,
+ serialize( 'hello world' )
+ ],
+ [
+ serialize( 'hello world' ),
+ 'Dummy:Hello',
+ null,
+ null,
+ Revision::FOR_PUBLIC,
+ serialize( 'hello world' )
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetContent
+ * @covers Revision::getContent
+ */
+ public function testGetContent( $text, $title, $model, $format,
+ $audience, $expectedSerialization
+ ) {
+ $rev = $this->newTestRevision( $text, $title, $model, $format );
+ $content = $rev->getContent( $audience );
+
+ $this->assertEquals(
+ $expectedSerialization,
+ is_null( $content ) ? null : $content->serialize( $format )
+ );
+ }
+
+ /**
+ * @covers Revision::getContent
+ */
+ public function testGetContent_failure() {
+ $rev = new Revision( [
+ 'page' => $this->testPage->getId(),
+ 'content_model' => $this->testPage->getContentModel(),
+ 'text_id' => 123456789, // not in the test DB
+ ] );
+
+ Wikimedia\suppressWarnings(); // bad text_id will trigger a warning.
+
+ $this->assertNull( $rev->getContent(),
+ "getContent() should return null if the revision's text blob could not be loaded." );
+
+ // NOTE: check this twice, once for lazy initialization, and once with the cached value.
+ $this->assertNull( $rev->getContent(),
+ "getContent() should return null if the revision's text blob could not be loaded." );
+
+ Wikimedia\restoreWarnings();
+ }
+
+ public function provideGetSize() {
+ return [
+ [ "hello world.", CONTENT_MODEL_WIKITEXT, 12 ],
+ [ serialize( "hello world." ), DummyContentForTesting::MODEL_ID, 12 ],
+ ];
+ }
+
+ /**
+ * @covers Revision::getSize
+ * @dataProvider provideGetSize
+ */
+ public function testGetSize( $text, $model, $expected_size ) {
+ $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSize', $model );
+ $this->assertEquals( $expected_size, $rev->getSize() );
+ }
+
+ public function provideGetSha1() {
+ return [
+ [ "hello world.", CONTENT_MODEL_WIKITEXT, Revision::base36Sha1( "hello world." ) ],
+ [
+ serialize( "hello world." ),
+ DummyContentForTesting::MODEL_ID,
+ Revision::base36Sha1( serialize( "hello world." ) )
+ ],
+ ];
+ }
+
+ /**
+ * @covers Revision::getSha1
+ * @dataProvider provideGetSha1
+ */
+ public function testGetSha1( $text, $model, $expected_hash ) {
+ $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSha1', $model );
+ $this->assertEquals( $expected_hash, $rev->getSha1() );
+ }
+
+ /**
+ * Tests whether $rev->getContent() returns a clone when needed.
+ *
+ * @covers Revision::getContent
+ */
+ public function testGetContentClone() {
+ $content = new RevisionTestModifyableContent( "foo" );
+
+ $rev = new Revision(
+ [
+ 'id' => 42,
+ 'page' => 23,
+ 'title' => Title::newFromText( "testGetContentClone_dummy" ),
+
+ 'content' => $content,
+ 'length' => $content->getSize(),
+ 'comment' => "testing",
+ 'minor_edit' => false,
+ ]
+ );
+
+ /** @var RevisionTestModifyableContent $content */
+ $content = $rev->getContent( Revision::RAW );
+ $content->setText( "bar" );
+
+ /** @var RevisionTestModifyableContent $content2 */
+ $content2 = $rev->getContent( Revision::RAW );
+ // content is mutable, expect clone
+ $this->assertNotSame( $content, $content2, "expected a clone" );
+ // clone should contain the original text
+ $this->assertEquals( "foo", $content2->getText() );
+
+ $content2->setText( "bla bla" );
+ // clones should be independent
+ $this->assertEquals( "bar", $content->getText() );
+ }
+
+ /**
+ * Tests whether $rev->getContent() returns the same object repeatedly if appropriate.
+ * @covers Revision::getContent
+ */
+ public function testGetContentUncloned() {
+ $rev = $this->newTestRevision( "hello", "testGetContentUncloned_dummy", CONTENT_MODEL_WIKITEXT );
+ $content = $rev->getContent( Revision::RAW );
+ $content2 = $rev->getContent( Revision::RAW );
+
+ // for immutable content like wikitext, this should be the same object
+ $this->assertSame( $content, $content2 );
+ }
+
+ /**
+ * @covers Revision::loadFromId
+ */
+ public function testLoadFromId() {
+ $rev = $this->testPage->getRevision();
+ $this->hideDeprecated( 'Revision::loadFromId' );
+ $this->assertRevEquals(
+ $rev,
+ Revision::loadFromId( wfGetDB( DB_MASTER ), $rev->getId() )
+ );
+ }
+
+ /**
+ * @covers Revision::loadFromPageId
+ */
+ public function testLoadFromPageId() {
+ $this->assertRevEquals(
+ $this->testPage->getRevision(),
+ Revision::loadFromPageId( wfGetDB( DB_MASTER ), $this->testPage->getId() )
+ );
+ }
+
+ /**
+ * @covers Revision::loadFromPageId
+ */
+ public function testLoadFromPageIdWithLatestRevId() {
+ $this->assertRevEquals(
+ $this->testPage->getRevision(),
+ Revision::loadFromPageId(
+ wfGetDB( DB_MASTER ),
+ $this->testPage->getId(),
+ $this->testPage->getLatest()
+ )
+ );
+ }
+
+ /**
+ * @covers Revision::loadFromPageId
+ */
+ public function testLoadFromPageIdWithNotLatestRevId() {
+ $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
+ $this->assertRevEquals(
+ $this->testPage->getRevision()->getPrevious(),
+ Revision::loadFromPageId(
+ wfGetDB( DB_MASTER ),
+ $this->testPage->getId(),
+ $this->testPage->getRevision()->getPrevious()->getId()
+ )
+ );
+ }
+
+ /**
+ * @covers Revision::loadFromTitle
+ */
+ public function testLoadFromTitle() {
+ $this->assertRevEquals(
+ $this->testPage->getRevision(),
+ Revision::loadFromTitle( wfGetDB( DB_MASTER ), $this->testPage->getTitle() )
+ );
+ }
+
+ /**
+ * @covers Revision::loadFromTitle
+ */
+ public function testLoadFromTitleWithLatestRevId() {
+ $this->assertRevEquals(
+ $this->testPage->getRevision(),
+ Revision::loadFromTitle(
+ wfGetDB( DB_MASTER ),
+ $this->testPage->getTitle(),
+ $this->testPage->getLatest()
+ )
+ );
+ }
+
+ /**
+ * @covers Revision::loadFromTitle
+ */
+ public function testLoadFromTitleWithNotLatestRevId() {
+ $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
+ $this->assertRevEquals(
+ $this->testPage->getRevision()->getPrevious(),
+ Revision::loadFromTitle(
+ wfGetDB( DB_MASTER ),
+ $this->testPage->getTitle(),
+ $this->testPage->getRevision()->getPrevious()->getId()
+ )
+ );
+ }
+
+ /**
+ * @covers Revision::loadFromTimestamp()
+ */
+ public function testLoadFromTimestamp() {
+ $this->assertRevEquals(
+ $this->testPage->getRevision(),
+ Revision::loadFromTimestamp(
+ wfGetDB( DB_MASTER ),
+ $this->testPage->getTitle(),
+ $this->testPage->getRevision()->getTimestamp()
+ )
+ );
+ }
+
+ /**
+ * @covers Revision::getParentLengths
+ */
+ public function testGetParentLengths_noRevIds() {
+ $this->assertSame(
+ [],
+ Revision::getParentLengths(
+ wfGetDB( DB_MASTER ),
+ []
+ )
+ );
+ }
+
+ /**
+ * @covers Revision::getParentLengths
+ */
+ public function testGetParentLengths_oneRevId() {
+ $text = '831jr091jr0921kr21kr0921kjr0921j09rj1';
+ $textLength = strlen( $text );
+
+ $this->testPage->doEditContent( new WikitextContent( $text ), __METHOD__ );
+ $rev[1] = $this->testPage->getLatest();
+
+ $this->assertSame(
+ [ $rev[1] => $textLength ],
+ Revision::getParentLengths(
+ wfGetDB( DB_MASTER ),
+ [ $rev[1] ]
+ )
+ );
+ }
+
+ /**
+ * @covers Revision::getParentLengths
+ */
+ public function testGetParentLengths_multipleRevIds() {
+ $textOne = '831jr091jr0921kr21kr0921kjr0921j09rj1';
+ $textOneLength = strlen( $textOne );
+ $textTwo = '831jr091jr092121j09rj1';
+ $textTwoLength = strlen( $textTwo );
+
+ $this->testPage->doEditContent( new WikitextContent( $textOne ), __METHOD__ );
+ $rev[1] = $this->testPage->getLatest();
+ $this->testPage->doEditContent( new WikitextContent( $textTwo ), __METHOD__ );
+ $rev[2] = $this->testPage->getLatest();
+
+ $this->assertSame(
+ [ $rev[1] => $textOneLength, $rev[2] => $textTwoLength ],
+ Revision::getParentLengths(
+ wfGetDB( DB_MASTER ),
+ [ $rev[1], $rev[2] ]
+ )
+ );
+ }
+
+ /**
+ * @covers Revision::getTitle
+ */
+ public function testGetTitle_fromExistingRevision() {
+ $this->assertTrue(
+ $this->testPage->getTitle()->equals(
+ $this->testPage->getRevision()->getTitle()
+ )
+ );
+ }
+
+ /**
+ * @covers Revision::getTitle
+ */
+ public function testGetTitle_fromRevisionWhichWillLoadTheTitle() {
+ $rev = new Revision( [ 'id' => $this->testPage->getLatest() ] );
+ $this->assertTrue(
+ $this->testPage->getTitle()->equals(
+ $rev->getTitle()
+ )
+ );
+ }
+
+ /**
+ * @covers Revision::isMinor
+ */
+ public function testIsMinor_true() {
+ // Use a sysop to ensure we can mark edits as minor
+ $sysop = $this->getTestSysop()->getUser();
+
+ $this->testPage->doEditContent(
+ new WikitextContent( __METHOD__ ),
+ __METHOD__,
+ EDIT_MINOR,
+ false,
+ $sysop
+ );
+ $rev = $this->testPage->getRevision();
+
+ $this->assertSame( true, $rev->isMinor() );
+ }
+
+ /**
+ * @covers Revision::isMinor
+ */
+ public function testIsMinor_false() {
+ $this->testPage->doEditContent(
+ new WikitextContent( __METHOD__ ),
+ __METHOD__,
+ 0
+ );
+ $rev = $this->testPage->getRevision();
+
+ $this->assertSame( false, $rev->isMinor() );
+ }
+
+ /**
+ * @covers Revision::getTimestamp
+ */
+ public function testGetTimestamp() {
+ $testTimestamp = wfTimestampNow();
+
+ $this->testPage->doEditContent(
+ new WikitextContent( __METHOD__ ),
+ __METHOD__
+ );
+ $rev = $this->testPage->getRevision();
+
+ $this->assertInternalType( 'string', $rev->getTimestamp() );
+ $this->assertTrue( strlen( $rev->getTimestamp() ) == strlen( 'YYYYMMDDHHMMSS' ) );
+ $this->assertContains( substr( $testTimestamp, 0, 10 ), $rev->getTimestamp() );
+ }
+
+ /**
+ * @covers Revision::getUser
+ * @covers Revision::getUserText
+ */
+ public function testGetUserAndText() {
+ $sysop = $this->getTestSysop()->getUser();
+
+ $this->testPage->doEditContent(
+ new WikitextContent( __METHOD__ ),
+ __METHOD__,
+ 0,
+ false,
+ $sysop
+ );
+ $rev = $this->testPage->getRevision();
+
+ $this->assertSame( $sysop->getId(), $rev->getUser() );
+ $this->assertSame( $sysop->getName(), $rev->getUserText() );
+ }
+
+ /**
+ * @covers Revision::isDeleted
+ */
+ public function testIsDeleted_nothingDeleted() {
+ $rev = $this->testPage->getRevision();
+
+ $this->assertSame( false, $rev->isDeleted( Revision::DELETED_TEXT ) );
+ $this->assertSame( false, $rev->isDeleted( Revision::DELETED_COMMENT ) );
+ $this->assertSame( false, $rev->isDeleted( Revision::DELETED_RESTRICTED ) );
+ $this->assertSame( false, $rev->isDeleted( Revision::DELETED_USER ) );
+ }
+
+ /**
+ * @covers Revision::getVisibility
+ */
+ public function testGetVisibility_nothingDeleted() {
+ $rev = $this->testPage->getRevision();
+
+ $this->assertSame( 0, $rev->getVisibility() );
+ }
+
+ /**
+ * @covers Revision::getComment
+ */
+ public function testGetComment_notDeleted() {
+ $expectedSummary = 'goatlicious summary';
+
+ $this->testPage->doEditContent(
+ new WikitextContent( __METHOD__ ),
+ $expectedSummary
+ );
+ $rev = $this->testPage->getRevision();
+
+ $this->assertSame( $expectedSummary, $rev->getComment() );
+ }
+
+ /**
+ * @covers Revision::isUnpatrolled
+ */
+ public function testIsUnpatrolled_returnsRecentChangesId() {
+ $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
+ $rev = $this->testPage->getRevision();
+
+ $this->assertGreaterThan( 0, $rev->isUnpatrolled() );
+ $this->assertSame( $rev->getRecentChange()->getAttribute( 'rc_id' ), $rev->isUnpatrolled() );
+ }
+
+ /**
+ * @covers Revision::isUnpatrolled
+ */
+ public function testIsUnpatrolled_returnsZeroIfPatrolled() {
+ // This assumes that sysops are auto patrolled
+ $sysop = $this->getTestSysop()->getUser();
+ $this->testPage->doEditContent(
+ new WikitextContent( __METHOD__ ),
+ __METHOD__,
+ 0,
+ false,
+ $sysop
+ );
+ $rev = $this->testPage->getRevision();
+
+ $this->assertSame( 0, $rev->isUnpatrolled() );
+ }
+
+ /**
+ * This is a simple blanket test for all simple content getters and is methods to provide some
+ * coverage before the split of Revision into multiple classes for MCR work.
+ * @covers Revision::getContent
+ * @covers Revision::getSerializedData
+ * @covers Revision::getContentModel
+ * @covers Revision::getContentFormat
+ * @covers Revision::getContentHandler
+ */
+ public function testSimpleContentGetters() {
+ $expectedText = 'testSimpleContentGetters in Revision. Goats love MCR...';
+ $expectedSummary = 'goatlicious testSimpleContentGetters summary';
+
+ $this->testPage->doEditContent(
+ new WikitextContent( $expectedText ),
+ $expectedSummary
+ );
+ $rev = $this->testPage->getRevision();
+
+ $this->assertSame( $expectedText, $rev->getContent()->getNativeData() );
+ $this->assertSame( $expectedText, $rev->getSerializedData() );
+ $this->assertSame( $this->testPage->getContentModel(), $rev->getContentModel() );
+ $this->assertSame( $this->testPage->getContent()->getDefaultFormat(), $rev->getContentFormat() );
+ $this->assertSame( $this->testPage->getContentHandler(), $rev->getContentHandler() );
+ }
+
+ /**
+ * @covers Revision::newKnownCurrent
+ */
+ public function testNewKnownCurrent() {
+ // Setup the services
+ $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
+ $this->setService( 'MainWANObjectCache', $cache );
+ $db = wfGetDB( DB_MASTER );
+
+ // Get a fresh revision to use during testing
+ $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
+ $rev = $this->testPage->getRevision();
+
+ // Clear any previous cache for the revision during creation
+ $key = $cache->makeGlobalKey( 'revision-row-1.29',
+ $db->getDomainID(),
+ $rev->getPage(),
+ $rev->getId()
+ );
+ $cache->delete( $key, WANObjectCache::HOLDOFF_NONE );
+ $this->assertFalse( $cache->get( $key ) );
+
+ // Get the new revision and make sure it is in the cache and correct
+ $newRev = Revision::newKnownCurrent( $db, $rev->getPage(), $rev->getId() );
+ $this->assertRevEquals( $rev, $newRev );
+
+ $cachedRow = $cache->get( $key );
+ $this->assertNotFalse( $cachedRow );
+ $this->assertEquals( $rev->getId(), $cachedRow->rev_id );
+ }
+
+ public function testNewKnownCurrent_withPageId() {
+ $db = wfGetDB( DB_MASTER );
+
+ $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
+ $rev = $this->testPage->getRevision();
+
+ $pageId = $this->testPage->getId();
+
+ $newRev = Revision::newKnownCurrent( $db, $pageId, $rev->getId() );
+ $this->assertRevEquals( $rev, $newRev );
+ }
+
+ public function testNewKnownCurrent_returnsFalseWhenTitleDoesntExist() {
+ $db = wfGetDB( DB_MASTER );
+
+ $this->assertFalse( Revision::newKnownCurrent( $db, 0 ) );
+ }
+
+ public function provideUserCanBitfield() {
+ yield [ 0, 0, [], null, true ];
+ // Bitfields match, user has no permissions
+ yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [], null, false ];
+ yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [], null, false ];
+ yield [ Revision::DELETED_USER, Revision::DELETED_USER, [], null, false ];
+ yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [], null, false ];
+ // Bitfields match, user (admin) does have permissions
+ yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [ 'sysop' ], null, true ];
+ yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [ 'sysop' ], null, true ];
+ yield [ Revision::DELETED_USER, Revision::DELETED_USER, [ 'sysop' ], null, true ];
+ // Bitfields match, user (admin) does not have permissions
+ yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'sysop' ], null, false ];
+ // Bitfields match, user (oversight) does have permissions
+ yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'oversight' ], null, true ];
+ // Check permissions using the title
+ yield [
+ Revision::DELETED_TEXT,
+ Revision::DELETED_TEXT,
+ [ 'sysop' ],
+ Title::newFromText( __METHOD__ ),
+ true,
+ ];
+ yield [
+ Revision::DELETED_TEXT,
+ Revision::DELETED_TEXT,
+ [],
+ Title::newFromText( __METHOD__ ),
+ false,
+ ];
+ }
+
+ /**
+ * @dataProvider provideUserCanBitfield
+ * @covers Revision::userCanBitfield
+ */
+ public function testUserCanBitfield( $bitField, $field, $userGroups, $title, $expected ) {
+ $this->setMwGlobals(
+ 'wgGroupPermissions',
+ [
+ 'sysop' => [
+ 'deletedtext' => true,
+ 'deletedhistory' => true,
+ ],
+ 'oversight' => [
+ 'viewsuppressed' => true,
+ 'suppressrevision' => true,
+ ],
+ ]
+ );
+ $user = $this->getTestUser( $userGroups )->getUser();
+
+ $this->assertSame(
+ $expected,
+ Revision::userCanBitfield( $bitField, $field, $user, $title )
+ );
+
+ // Fallback to $wgUser
+ $this->setMwGlobals(
+ 'wgUser',
+ $user
+ );
+ $this->assertSame(
+ $expected,
+ Revision::userCanBitfield( $bitField, $field, null, $title )
+ );
+ }
+
+ public function provideUserCan() {
+ yield [ 0, 0, [], true ];
+ // Bitfields match, user has no permissions
+ yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [], false ];
+ yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [], false ];
+ yield [ Revision::DELETED_USER, Revision::DELETED_USER, [], false ];
+ yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [], false ];
+ // Bitfields match, user (admin) does have permissions
+ yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [ 'sysop' ], true ];
+ yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [ 'sysop' ], true ];
+ yield [ Revision::DELETED_USER, Revision::DELETED_USER, [ 'sysop' ], true ];
+ // Bitfields match, user (admin) does not have permissions
+ yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'sysop' ], false ];
+ // Bitfields match, user (oversight) does have permissions
+ yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'oversight' ], true ];
+ }
+
+ /**
+ * @dataProvider provideUserCan
+ * @covers Revision::userCan
+ */
+ public function testUserCan( $bitField, $field, $userGroups, $expected ) {
+ $this->setMwGlobals(
+ 'wgGroupPermissions',
+ [
+ 'sysop' => [
+ 'deletedtext' => true,
+ 'deletedhistory' => true,
+ ],
+ 'oversight' => [
+ 'viewsuppressed' => true,
+ 'suppressrevision' => true,
+ ],
+ ]
+ );
+ $user = $this->getTestUser( $userGroups )->getUser();
+ $revision = new Revision( [ 'deleted' => $bitField ], 0, $this->testPage->getTitle() );
+
+ $this->assertSame(
+ $expected,
+ $revision->userCan( $field, $user )
+ );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/RevisionNoContentHandlerDbTest.php b/www/wiki/tests/phpunit/includes/RevisionNoContentHandlerDbTest.php
new file mode 100644
index 00000000..c980a487
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/RevisionNoContentHandlerDbTest.php
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * @group Database
+ * @group medium
+ * @group ContentHandler
+ */
+class RevisionNoContentHandlerDbTest extends RevisionDbTestBase {
+
+ protected function getContentHandlerUseDB() {
+ return false;
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/RevisionStorageTest.php b/www/wiki/tests/phpunit/includes/RevisionStorageTest.php
deleted file mode 100644
index e9f16dbd..00000000
--- a/www/wiki/tests/phpunit/includes/RevisionStorageTest.php
+++ /dev/null
@@ -1,574 +0,0 @@
-<?php
-
-/**
- * Test class for Revision storage.
- *
- * @group ContentHandler
- * @group Database
- * ^--- important, causes temporary tables to be used instead of the real database
- *
- * @group medium
- * ^--- important, causes tests not to fail with timeout
- */
-class RevisionStorageTest extends MediaWikiTestCase {
- /**
- * @var WikiPage $the_page
- */
- private $the_page;
-
- function __construct( $name = null, array $data = [], $dataName = '' ) {
- parent::__construct( $name, $data, $dataName );
-
- $this->tablesUsed = array_merge( $this->tablesUsed,
- [ 'page',
- 'revision',
- 'ip_changes',
- 'text',
-
- 'recentchanges',
- 'logging',
-
- 'page_props',
- 'pagelinks',
- 'categorylinks',
- 'langlinks',
- 'externallinks',
- 'imagelinks',
- 'templatelinks',
- 'iwlinks' ] );
- }
-
- protected function setUp() {
- global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
-
- parent::setUp();
-
- $wgExtraNamespaces[12312] = 'Dummy';
- $wgExtraNamespaces[12313] = 'Dummy_talk';
-
- $wgNamespaceContentModels[12312] = 'DUMMY';
- $wgContentHandlers['DUMMY'] = 'DummyContentHandlerForTesting';
-
- MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
- $wgContLang->resetNamespaces(); # reset namespace cache
- if ( !$this->the_page ) {
- $this->the_page = $this->createPage(
- 'RevisionStorageTest_the_page',
- "just a dummy page",
- CONTENT_MODEL_WIKITEXT
- );
- }
-
- $this->tablesUsed[] = 'archive';
- }
-
- protected function tearDown() {
- global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
-
- parent::tearDown();
-
- unset( $wgExtraNamespaces[12312] );
- unset( $wgExtraNamespaces[12313] );
-
- unset( $wgNamespaceContentModels[12312] );
- unset( $wgContentHandlers['DUMMY'] );
-
- MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
- $wgContLang->resetNamespaces(); # reset namespace cache
- }
-
- protected function makeRevision( $props = null ) {
- if ( $props === null ) {
- $props = [];
- }
-
- if ( !isset( $props['content'] ) && !isset( $props['text'] ) ) {
- $props['text'] = 'Lorem Ipsum';
- }
-
- if ( !isset( $props['comment'] ) ) {
- $props['comment'] = 'just a test';
- }
-
- if ( !isset( $props['page'] ) ) {
- $props['page'] = $this->the_page->getId();
- }
-
- $rev = new Revision( $props );
-
- $dbw = wfGetDB( DB_MASTER );
- $rev->insertOn( $dbw );
-
- return $rev;
- }
-
- protected function createPage( $page, $text, $model = null ) {
- if ( is_string( $page ) ) {
- if ( !preg_match( '/:/', $page ) &&
- ( $model === null || $model === CONTENT_MODEL_WIKITEXT )
- ) {
- $ns = $this->getDefaultWikitextNS();
- $page = MWNamespace::getCanonicalName( $ns ) . ':' . $page;
- }
-
- $page = Title::newFromText( $page );
- }
-
- if ( $page instanceof Title ) {
- $page = new WikiPage( $page );
- }
-
- if ( $page->exists() ) {
- $page->doDeleteArticle( "done" );
- }
-
- $content = ContentHandler::makeContent( $text, $page->getTitle(), $model );
- $page->doEditContent( $content, "testing", EDIT_NEW );
-
- return $page;
- }
-
- protected function assertRevEquals( Revision $orig, Revision $rev = null ) {
- $this->assertNotNull( $rev, 'missing revision' );
-
- $this->assertEquals( $orig->getId(), $rev->getId() );
- $this->assertEquals( $orig->getPage(), $rev->getPage() );
- $this->assertEquals( $orig->getTimestamp(), $rev->getTimestamp() );
- $this->assertEquals( $orig->getUser(), $rev->getUser() );
- $this->assertEquals( $orig->getContentModel(), $rev->getContentModel() );
- $this->assertEquals( $orig->getContentFormat(), $rev->getContentFormat() );
- $this->assertEquals( $orig->getSha1(), $rev->getSha1() );
- }
-
- /**
- * @covers Revision::__construct
- */
- public function testConstructFromRow() {
- $orig = $this->makeRevision();
-
- $dbr = wfGetDB( DB_REPLICA );
- $res = $dbr->select( 'revision', Revision::selectFields(), [ 'rev_id' => $orig->getId() ] );
- $this->assertTrue( is_object( $res ), 'query failed' );
-
- $row = $res->fetchObject();
- $res->free();
-
- $rev = new Revision( $row );
-
- $this->assertRevEquals( $orig, $rev );
- }
-
- /**
- * @covers Revision::newFromRow
- */
- public function testNewFromRow() {
- $orig = $this->makeRevision();
-
- $dbr = wfGetDB( DB_REPLICA );
- $res = $dbr->select( 'revision', Revision::selectFields(), [ 'rev_id' => $orig->getId() ] );
- $this->assertTrue( is_object( $res ), 'query failed' );
-
- $row = $res->fetchObject();
- $res->free();
-
- $rev = Revision::newFromRow( $row );
-
- $this->assertRevEquals( $orig, $rev );
- }
-
- /**
- * @covers Revision::newFromArchiveRow
- */
- public function testNewFromArchiveRow() {
- $page = $this->createPage(
- 'RevisionStorageTest_testNewFromArchiveRow',
- 'Lorem Ipsum',
- CONTENT_MODEL_WIKITEXT
- );
- $orig = $page->getRevision();
- $page->doDeleteArticle( 'test Revision::newFromArchiveRow' );
-
- $dbr = wfGetDB( DB_REPLICA );
- $res = $dbr->select(
- 'archive', Revision::selectArchiveFields(), [ 'ar_rev_id' => $orig->getId() ]
- );
- $this->assertTrue( is_object( $res ), 'query failed' );
-
- $row = $res->fetchObject();
- $res->free();
-
- $rev = Revision::newFromArchiveRow( $row );
-
- $this->assertRevEquals( $orig, $rev );
- }
-
- /**
- * @covers Revision::newFromId
- */
- public function testNewFromId() {
- $orig = $this->makeRevision();
-
- $rev = Revision::newFromId( $orig->getId() );
-
- $this->assertRevEquals( $orig, $rev );
- }
-
- /**
- * @covers Revision::fetchRevision
- */
- public function testFetchRevision() {
- $page = $this->createPage(
- 'RevisionStorageTest_testFetchRevision',
- 'one',
- CONTENT_MODEL_WIKITEXT
- );
-
- // Hidden process cache assertion below
- $page->getRevision()->getId();
-
- $page->doEditContent( new WikitextContent( 'two' ), 'second rev' );
- $id = $page->getRevision()->getId();
-
- $res = Revision::fetchRevision( $page->getTitle() );
-
- # note: order is unspecified
- $rows = [];
- while ( ( $row = $res->fetchObject() ) ) {
- $rows[$row->rev_id] = $row;
- }
-
- $this->assertEquals( 1, count( $rows ), 'expected exactly one revision' );
- $this->assertArrayHasKey( $id, $rows, 'missing revision with id ' . $id );
- }
-
- /**
- * @covers Revision::selectFields
- */
- public function testSelectFields() {
- global $wgContentHandlerUseDB;
-
- $fields = Revision::selectFields();
-
- $this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields' );
- $this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields' );
- $this->assertTrue(
- in_array( 'rev_timestamp', $fields ),
- 'missing rev_timestamp in list of fields'
- );
- $this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields' );
-
- if ( $wgContentHandlerUseDB ) {
- $this->assertTrue( in_array( 'rev_content_model', $fields ),
- 'missing rev_content_model in list of fields' );
- $this->assertTrue( in_array( 'rev_content_format', $fields ),
- 'missing rev_content_format in list of fields' );
- }
- }
-
- /**
- * @covers Revision::getPage
- */
- public function testGetPage() {
- $page = $this->the_page;
-
- $orig = $this->makeRevision( [ 'page' => $page->getId() ] );
- $rev = Revision::newFromId( $orig->getId() );
-
- $this->assertEquals( $page->getId(), $rev->getPage() );
- }
-
- /**
- * @covers Revision::getContent
- */
- public function testGetContent_failure() {
- $rev = new Revision( [
- 'page' => $this->the_page->getId(),
- 'content_model' => $this->the_page->getContentModel(),
- 'text_id' => 123456789, // not in the test DB
- ] );
-
- $this->assertNull( $rev->getContent(),
- "getContent() should return null if the revision's text blob could not be loaded." );
-
- // NOTE: check this twice, once for lazy initialization, and once with the cached value.
- $this->assertNull( $rev->getContent(),
- "getContent() should return null if the revision's text blob could not be loaded." );
- }
-
- /**
- * @covers Revision::getContent
- */
- public function testGetContent() {
- $orig = $this->makeRevision( [ 'text' => 'hello hello.' ] );
- $rev = Revision::newFromId( $orig->getId() );
-
- $this->assertEquals( 'hello hello.', $rev->getContent()->getNativeData() );
- }
-
- /**
- * @covers Revision::getContentModel
- */
- public function testGetContentModel() {
- global $wgContentHandlerUseDB;
-
- if ( !$wgContentHandlerUseDB ) {
- $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
- }
-
- $orig = $this->makeRevision( [ 'text' => 'hello hello.',
- 'content_model' => CONTENT_MODEL_JAVASCRIPT ] );
- $rev = Revision::newFromId( $orig->getId() );
-
- $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
- }
-
- /**
- * @covers Revision::getContentFormat
- */
- public function testGetContentFormat() {
- global $wgContentHandlerUseDB;
-
- if ( !$wgContentHandlerUseDB ) {
- $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
- }
-
- $orig = $this->makeRevision( [
- 'text' => 'hello hello.',
- 'content_model' => CONTENT_MODEL_JAVASCRIPT,
- 'content_format' => CONTENT_FORMAT_JAVASCRIPT
- ] );
- $rev = Revision::newFromId( $orig->getId() );
-
- $this->assertEquals( CONTENT_FORMAT_JAVASCRIPT, $rev->getContentFormat() );
- }
-
- /**
- * @covers Revision::isCurrent
- */
- public function testIsCurrent() {
- $page = $this->createPage(
- 'RevisionStorageTest_testIsCurrent',
- 'Lorem Ipsum',
- CONTENT_MODEL_WIKITEXT
- );
- $rev1 = $page->getRevision();
-
- # @todo find out if this should be true
- # $this->assertTrue( $rev1->isCurrent() );
-
- $rev1x = Revision::newFromId( $rev1->getId() );
- $this->assertTrue( $rev1x->isCurrent() );
-
- $page->doEditContent(
- ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
- 'second rev'
- );
- $rev2 = $page->getRevision();
-
- # @todo find out if this should be true
- # $this->assertTrue( $rev2->isCurrent() );
-
- $rev1x = Revision::newFromId( $rev1->getId() );
- $this->assertFalse( $rev1x->isCurrent() );
-
- $rev2x = Revision::newFromId( $rev2->getId() );
- $this->assertTrue( $rev2x->isCurrent() );
- }
-
- /**
- * @covers Revision::getPrevious
- */
- public function testGetPrevious() {
- $page = $this->createPage(
- 'RevisionStorageTest_testGetPrevious',
- 'Lorem Ipsum testGetPrevious',
- CONTENT_MODEL_WIKITEXT
- );
- $rev1 = $page->getRevision();
-
- $this->assertNull( $rev1->getPrevious() );
-
- $page->doEditContent(
- ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
- 'second rev testGetPrevious' );
- $rev2 = $page->getRevision();
-
- $this->assertNotNull( $rev2->getPrevious() );
- $this->assertEquals( $rev1->getId(), $rev2->getPrevious()->getId() );
- }
-
- /**
- * @covers Revision::getNext
- */
- public function testGetNext() {
- $page = $this->createPage(
- 'RevisionStorageTest_testGetNext',
- 'Lorem Ipsum testGetNext',
- CONTENT_MODEL_WIKITEXT
- );
- $rev1 = $page->getRevision();
-
- $this->assertNull( $rev1->getNext() );
-
- $page->doEditContent(
- ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
- 'second rev testGetNext'
- );
- $rev2 = $page->getRevision();
-
- $this->assertNotNull( $rev1->getNext() );
- $this->assertEquals( $rev2->getId(), $rev1->getNext()->getId() );
- }
-
- /**
- * @covers Revision::newNullRevision
- */
- public function testNewNullRevision() {
- $page = $this->createPage(
- 'RevisionStorageTest_testNewNullRevision',
- 'some testing text',
- CONTENT_MODEL_WIKITEXT
- );
- $orig = $page->getRevision();
-
- $dbw = wfGetDB( DB_MASTER );
- $rev = Revision::newNullRevision( $dbw, $page->getId(), 'a null revision', false );
-
- $this->assertNotEquals( $orig->getId(), $rev->getId(),
- 'new null revision shold have a different id from the original revision' );
- $this->assertEquals( $orig->getTextId(), $rev->getTextId(),
- 'new null revision shold have the same text id as the original revision' );
- $this->assertEquals( 'some testing text', $rev->getContent()->getNativeData() );
- }
-
- /**
- * @covers Revision::insertOn
- */
- public function testInsertOn() {
- $ip = '2600:387:ed7:947e:8c16:a1ad:dd34:1dd7';
-
- $orig = $this->makeRevision( [
- 'user_text' => $ip
- ] );
-
- // Make sure the revision was copied to ip_changes
- $dbr = wfGetDB( DB_REPLICA );
- $res = $dbr->select( 'ip_changes', '*', [ 'ipc_rev_id' => $orig->getId() ] );
- $row = $res->fetchObject();
-
- $this->assertEquals( IP::toHex( $ip ), $row->ipc_hex );
- $this->assertEquals( $orig->getTimestamp(), $row->ipc_rev_timestamp );
- }
-
- public static function provideUserWasLastToEdit() {
- return [
- [ # 0
- 3, true, # actually the last edit
- ],
- [ # 1
- 2, true, # not the current edit, but still by this user
- ],
- [ # 2
- 1, false, # edit by another user
- ],
- [ # 3
- 0, false, # first edit, by this user, but another user edited in the mean time
- ],
- ];
- }
-
- /**
- * @dataProvider provideUserWasLastToEdit
- */
- public function testUserWasLastToEdit( $sinceIdx, $expectedLast ) {
- $userA = User::newFromName( "RevisionStorageTest_userA" );
- $userB = User::newFromName( "RevisionStorageTest_userB" );
-
- if ( $userA->getId() === 0 ) {
- $userA = User::createNew( $userA->getName() );
- }
-
- if ( $userB->getId() === 0 ) {
- $userB = User::createNew( $userB->getName() );
- }
-
- $ns = $this->getDefaultWikitextNS();
-
- $dbw = wfGetDB( DB_MASTER );
- $revisions = [];
-
- // create revisions -----------------------------
- $page = WikiPage::factory( Title::newFromText(
- 'RevisionStorageTest_testUserWasLastToEdit', $ns ) );
- $page->insertOn( $dbw );
-
- # zero
- $revisions[0] = new Revision( [
- 'page' => $page->getId(),
- // we need the title to determine the page's default content model
- 'title' => $page->getTitle(),
- 'timestamp' => '20120101000000',
- 'user' => $userA->getId(),
- 'text' => 'zero',
- 'content_model' => CONTENT_MODEL_WIKITEXT,
- 'summary' => 'edit zero'
- ] );
- $revisions[0]->insertOn( $dbw );
-
- # one
- $revisions[1] = new Revision( [
- 'page' => $page->getId(),
- // still need the title, because $page->getId() is 0 (there's no entry in the page table)
- 'title' => $page->getTitle(),
- 'timestamp' => '20120101000100',
- 'user' => $userA->getId(),
- 'text' => 'one',
- 'content_model' => CONTENT_MODEL_WIKITEXT,
- 'summary' => 'edit one'
- ] );
- $revisions[1]->insertOn( $dbw );
-
- # two
- $revisions[2] = new Revision( [
- 'page' => $page->getId(),
- 'title' => $page->getTitle(),
- 'timestamp' => '20120101000200',
- 'user' => $userB->getId(),
- 'text' => 'two',
- 'content_model' => CONTENT_MODEL_WIKITEXT,
- 'summary' => 'edit two'
- ] );
- $revisions[2]->insertOn( $dbw );
-
- # three
- $revisions[3] = new Revision( [
- 'page' => $page->getId(),
- 'title' => $page->getTitle(),
- 'timestamp' => '20120101000300',
- 'user' => $userA->getId(),
- 'text' => 'three',
- 'content_model' => CONTENT_MODEL_WIKITEXT,
- 'summary' => 'edit three'
- ] );
- $revisions[3]->insertOn( $dbw );
-
- # four
- $revisions[4] = new Revision( [
- 'page' => $page->getId(),
- 'title' => $page->getTitle(),
- 'timestamp' => '20120101000200',
- 'user' => $userA->getId(),
- 'text' => 'zero',
- 'content_model' => CONTENT_MODEL_WIKITEXT,
- 'summary' => 'edit four'
- ] );
- $revisions[4]->insertOn( $dbw );
-
- // test it ---------------------------------
- $since = $revisions[$sinceIdx]->getTimestamp();
-
- $wasLast = Revision::userWasLastToEdit( $dbw, $page->getId(), $userA->getId(), $since );
-
- $this->assertEquals( $expectedLast, $wasLast );
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/RevisionStorageTestContentHandlerUseDB.php b/www/wiki/tests/phpunit/includes/RevisionStorageTestContentHandlerUseDB.php
deleted file mode 100644
index 9e667f21..00000000
--- a/www/wiki/tests/phpunit/includes/RevisionStorageTestContentHandlerUseDB.php
+++ /dev/null
@@ -1,89 +0,0 @@
-<?php
-
-/**
- * @group ContentHandler
- * @group Database
- * ^--- important, causes temporary tables to be used instead of the real database
- */
-class RevisionTestContentHandlerUseDB extends RevisionStorageTest {
-
- protected function setUp() {
- $this->setMwGlobals( 'wgContentHandlerUseDB', false );
-
- $dbw = wfGetDB( DB_MASTER );
-
- $page_table = $dbw->tableName( 'page' );
- $revision_table = $dbw->tableName( 'revision' );
- $archive_table = $dbw->tableName( 'archive' );
-
- if ( $dbw->fieldExists( $page_table, 'page_content_model' ) ) {
- $dbw->query( "alter table $page_table drop column page_content_model" );
- $dbw->query( "alter table $revision_table drop column rev_content_model" );
- $dbw->query( "alter table $revision_table drop column rev_content_format" );
- $dbw->query( "alter table $archive_table drop column ar_content_model" );
- $dbw->query( "alter table $archive_table drop column ar_content_format" );
- }
-
- parent::setUp();
- }
-
- /**
- * @covers Revision::selectFields
- */
- public function testSelectFields() {
- $fields = Revision::selectFields();
-
- $this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields' );
- $this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields' );
- $this->assertTrue(
- in_array( 'rev_timestamp', $fields ),
- 'missing rev_timestamp in list of fields'
- );
- $this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields' );
-
- $this->assertFalse(
- in_array( 'rev_content_model', $fields ),
- 'missing rev_content_model in list of fields'
- );
- $this->assertFalse(
- in_array( 'rev_content_format', $fields ),
- 'missing rev_content_format in list of fields'
- );
- }
-
- /**
- * @covers Revision::getContentModel
- */
- public function testGetContentModel() {
- try {
- $this->makeRevision( [ 'text' => 'hello hello.',
- 'content_model' => CONTENT_MODEL_JAVASCRIPT ] );
-
- $this->fail( "Creating JavaScript content on a wikitext page should fail with "
- . "\$wgContentHandlerUseDB disabled" );
- } catch ( MWException $ex ) {
- $this->assertTrue( true ); // ok
- }
- }
-
- /**
- * @covers Revision::getContentFormat
- */
- public function testGetContentFormat() {
- try {
- // @todo change this to test failure on using a non-standard (but supported) format
- // for a content model supported in the given location. As of 1.21, there are
- // no alternative formats for any of the standard content models that could be
- // used for this though.
-
- $this->makeRevision( [ 'text' => 'hello hello.',
- 'content_model' => CONTENT_MODEL_JAVASCRIPT,
- 'content_format' => 'text/javascript' ] );
-
- $this->fail( "Creating JavaScript content on a wikitext page should fail with "
- . "\$wgContentHandlerUseDB disabled" );
- } catch ( MWException $ex ) {
- $this->assertTrue( true ); // ok
- }
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/RevisionTest.php b/www/wiki/tests/phpunit/includes/RevisionTest.php
index c971a40c..ab067a47 100644
--- a/www/wiki/tests/phpunit/includes/RevisionTest.php
+++ b/www/wiki/tests/phpunit/includes/RevisionTest.php
@@ -1,139 +1,569 @@
<?php
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\BlobStoreFactory;
+use MediaWiki\Storage\MutableRevisionRecord;
+use MediaWiki\Storage\RevisionAccessException;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\RevisionStore;
+use MediaWiki\Storage\SlotRecord;
+use MediaWiki\Storage\SqlBlobStore;
+use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\LoadBalancer;
+
/**
- * @group ContentHandler
+ * Test cases in RevisionTest should not interact with the Database.
+ * For test cases that need Database interaction see RevisionDbTestBase.
*/
class RevisionTest extends MediaWikiTestCase {
- protected function setUp() {
- global $wgContLang;
- parent::setUp();
+ public function provideConstructFromArray() {
+ yield 'with text' => [
+ [
+ 'text' => 'hello world.',
+ 'content_model' => CONTENT_MODEL_JAVASCRIPT
+ ],
+ ];
+ yield 'with content' => [
+ [
+ 'content' => new JavaScriptContent( 'hellow world.' )
+ ],
+ ];
+ // FIXME: test with and without user ID, and with a user object.
+ // We can't prepare that here though, since we don't yet have a dummy DB
+ }
+
+ /**
+ * @param string $model
+ * @return Title
+ */
+ public function getMockTitle( $model = CONTENT_MODEL_WIKITEXT ) {
+ $mock = $this->getMockBuilder( Title::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mock->expects( $this->any() )
+ ->method( 'getNamespace' )
+ ->will( $this->returnValue( $this->getDefaultWikitextNS() ) );
+ $mock->expects( $this->any() )
+ ->method( 'getPrefixedText' )
+ ->will( $this->returnValue( 'RevisionTest' ) );
+ $mock->expects( $this->any() )
+ ->method( 'getDBkey' )
+ ->will( $this->returnValue( 'RevisionTest' ) );
+ $mock->expects( $this->any() )
+ ->method( 'getArticleID' )
+ ->will( $this->returnValue( 23 ) );
+ $mock->expects( $this->any() )
+ ->method( 'getContentModel' )
+ ->will( $this->returnValue( $model ) );
+
+ return $mock;
+ }
+
+ /**
+ * @dataProvider provideConstructFromArray
+ * @covers Revision::__construct
+ * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
+ */
+ public function testConstructFromArray( $rowArray ) {
+ $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
+ $this->assertNotNull( $rev->getContent(), 'no content object available' );
+ $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() );
+ $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
+ }
- $this->setMwGlobals( [
- 'wgContLang' => Language::factory( 'en' ),
- 'wgLanguageCode' => 'en',
- 'wgLegacyEncoding' => false,
- 'wgCompressRevisions' => false,
+ /**
+ * @covers Revision::__construct
+ * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
+ */
+ public function testConstructFromEmptyArray() {
+ $rev = new Revision( [], 0, $this->getMockTitle() );
+ $this->assertNull( $rev->getContent(), 'no content object should be available' );
+ }
- 'wgContentHandlerTextFallback' => 'ignore',
- ] );
+ /**
+ * @covers Revision::__construct
+ * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
+ */
+ public function testConstructFromArrayWithBadPageId() {
+ Wikimedia\suppressWarnings();
+ $rev = new Revision( [ 'page' => 77777777 ] );
+ $this->assertSame( 77777777, $rev->getPage() );
+ Wikimedia\restoreWarnings();
+ }
- $this->mergeMwGlobalArrayValue(
- 'wgExtraNamespaces',
+ public function provideConstructFromArray_userSetAsExpected() {
+ yield 'no user defaults to wgUser' => [
[
- 12312 => 'Dummy',
- 12313 => 'Dummy_talk',
- ]
- );
+ 'content' => new JavaScriptContent( 'hello world.' ),
+ ],
+ null,
+ null,
+ ];
+ yield 'user text and id' => [
+ [
+ 'content' => new JavaScriptContent( 'hello world.' ),
+ 'user_text' => 'SomeTextUserName',
+ 'user' => 99,
- $this->mergeMwGlobalArrayValue(
- 'wgNamespaceContentModels',
+ ],
+ 99,
+ 'SomeTextUserName',
+ ];
+ yield 'user text only' => [
[
- 12312 => 'testing',
- ]
- );
+ 'content' => new JavaScriptContent( 'hello world.' ),
+ 'user_text' => '111.111.111.111',
+ ],
+ 0,
+ '111.111.111.111',
+ ];
+ }
- $this->mergeMwGlobalArrayValue(
- 'wgContentHandlers',
+ /**
+ * @dataProvider provideConstructFromArray_userSetAsExpected
+ * @covers Revision::__construct
+ * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
+ *
+ * @param array $rowArray
+ * @param mixed $expectedUserId null to expect the current wgUser ID
+ * @param mixed $expectedUserName null to expect the current wgUser name
+ */
+ public function testConstructFromArray_userSetAsExpected(
+ array $rowArray,
+ $expectedUserId,
+ $expectedUserName
+ ) {
+ $testUser = $this->getTestUser()->getUser();
+ $this->setMwGlobals( 'wgUser', $testUser );
+ if ( $expectedUserId === null ) {
+ $expectedUserId = $testUser->getId();
+ }
+ if ( $expectedUserName === null ) {
+ $expectedUserName = $testUser->getName();
+ }
+
+ $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
+ $this->assertEquals( $expectedUserId, $rev->getUser() );
+ $this->assertEquals( $expectedUserName, $rev->getUserText() );
+ }
+
+ public function provideConstructFromArrayThrowsExceptions() {
+ yield 'content and text_id both not empty' => [
[
- 'testing' => 'DummyContentHandlerForTesting',
- 'RevisionTestModifyableContent' => 'RevisionTestModifyableContentHandler',
- ]
+ 'content' => new WikitextContent( 'GOAT' ),
+ 'text_id' => 'someid',
+ ],
+ new MWException( "Text already stored in external store (id someid), " .
+ "can't serialize content object" )
+ ];
+ yield 'with bad content object (class)' => [
+ [ 'content' => new stdClass() ],
+ new MWException( 'content field must contain a Content object.' )
+ ];
+ yield 'with bad content object (string)' => [
+ [ 'content' => 'ImAGoat' ],
+ new MWException( 'content field must contain a Content object.' )
+ ];
+ yield 'bad row format' => [
+ 'imastring, not a row',
+ new InvalidArgumentException(
+ '$row must be a row object, an associative array, or a RevisionRecord'
+ )
+ ];
+ }
+
+ /**
+ * @dataProvider provideConstructFromArrayThrowsExceptions
+ * @covers Revision::__construct
+ * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
+ */
+ public function testConstructFromArrayThrowsExceptions( $rowArray, Exception $expectedException ) {
+ $this->setExpectedException(
+ get_class( $expectedException ),
+ $expectedException->getMessage(),
+ $expectedException->getCode()
);
+ new Revision( $rowArray, 0, $this->getMockTitle() );
+ }
+
+ /**
+ * @covers Revision::__construct
+ * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
+ */
+ public function testConstructFromNothing() {
+ $this->setExpectedException(
+ InvalidArgumentException::class
+ );
+ new Revision( [] );
+ }
+
+ public function provideConstructFromRow() {
+ yield 'Full construction' => [
+ [
+ 'rev_id' => '42',
+ 'rev_page' => '23',
+ 'rev_text_id' => '2',
+ 'rev_timestamp' => '20171017114835',
+ 'rev_user_text' => '127.0.0.1',
+ 'rev_user' => '0',
+ 'rev_minor_edit' => '0',
+ 'rev_deleted' => '0',
+ 'rev_len' => '46',
+ 'rev_parent_id' => '1',
+ 'rev_sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
+ 'rev_comment_text' => 'Goat Comment!',
+ 'rev_comment_data' => null,
+ 'rev_comment_cid' => null,
+ 'rev_content_format' => 'GOATFORMAT',
+ 'rev_content_model' => 'GOATMODEL',
+ ],
+ function ( RevisionTest $testCase, Revision $rev ) {
+ $testCase->assertSame( 42, $rev->getId() );
+ $testCase->assertSame( 23, $rev->getPage() );
+ $testCase->assertSame( 2, $rev->getTextId() );
+ $testCase->assertSame( '20171017114835', $rev->getTimestamp() );
+ $testCase->assertSame( '127.0.0.1', $rev->getUserText() );
+ $testCase->assertSame( 0, $rev->getUser() );
+ $testCase->assertSame( false, $rev->isMinor() );
+ $testCase->assertSame( false, $rev->isDeleted( Revision::DELETED_TEXT ) );
+ $testCase->assertSame( 46, $rev->getSize() );
+ $testCase->assertSame( 1, $rev->getParentId() );
+ $testCase->assertSame( 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z', $rev->getSha1() );
+ $testCase->assertSame( 'Goat Comment!', $rev->getComment() );
+ $testCase->assertSame( 'GOATFORMAT', $rev->getContentFormat() );
+ $testCase->assertSame( 'GOATMODEL', $rev->getContentModel() );
+ }
+ ];
+ yield 'default field values' => [
+ [
+ 'rev_id' => '42',
+ 'rev_page' => '23',
+ 'rev_text_id' => '2',
+ 'rev_timestamp' => '20171017114835',
+ 'rev_user_text' => '127.0.0.1',
+ 'rev_user' => '0',
+ 'rev_minor_edit' => '0',
+ 'rev_deleted' => '0',
+ 'rev_comment_text' => 'Goat Comment!',
+ 'rev_comment_data' => null,
+ 'rev_comment_cid' => null,
+ ],
+ function ( RevisionTest $testCase, Revision $rev ) {
+ // parent ID may be null
+ $testCase->assertSame( null, $rev->getParentId(), 'revision id' );
+
+ // given fields
+ $testCase->assertSame( $rev->getTimestamp(), '20171017114835', 'timestamp' );
+ $testCase->assertSame( $rev->getUserText(), '127.0.0.1', 'user name' );
+ $testCase->assertSame( $rev->getUser(), 0, 'user id' );
+ $testCase->assertSame( $rev->getComment(), 'Goat Comment!' );
+ $testCase->assertSame( false, $rev->isMinor(), 'minor edit' );
+ $testCase->assertSame( 0, $rev->getVisibility(), 'visibility flags' );
+
+ // computed fields
+ $testCase->assertNotNull( $rev->getSize(), 'size' );
+ $testCase->assertNotNull( $rev->getSha1(), 'hash' );
+
+ // NOTE: model and format will be detected based on the namespace of the (mock) title
+ $testCase->assertSame( 'text/x-wiki', $rev->getContentFormat(), 'format' );
+ $testCase->assertSame( 'wikitext', $rev->getContentModel(), 'model' );
+ }
+ ];
+ }
+
+ /**
+ * @dataProvider provideConstructFromRow
+ * @covers Revision::__construct
+ * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
+ */
+ public function testConstructFromRow( array $arrayData, $assertions ) {
+ $data = 'Hello goat.'; // needs to match model and format
+
+ $blobStore = $this->getMockBuilder( SqlBlobStore::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $blobStore->method( 'getBlob' )
+ ->will( $this->returnValue( $data ) );
+
+ $blobStore->method( 'getTextIdFromAddress' )
+ ->will( $this->returnCallback(
+ function ( $address ) {
+ // Turn "tt:1234" into 12345.
+ // Note that this must be functional so we can test getTextId().
+ // Ideally, we'd un-mock getTextIdFromAddress and use its actual implementation.
+ $parts = explode( ':', $address );
+ return (int)array_pop( $parts );
+ }
+ ) );
+
+ // Note override internal service, so RevisionStore uses it as well.
+ $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
+
+ $row = (object)$arrayData;
+ $rev = new Revision( $row, 0, $this->getMockTitle() );
+ $assertions( $this, $rev );
+ }
+
+ /**
+ * @covers Revision::__construct
+ * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
+ */
+ public function testConstructFromRowWithBadPageId() {
+ $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->overrideMwServices();
+ Wikimedia\suppressWarnings();
+ $rev = new Revision( (object)[ 'rev_page' => 77777777 ] );
+ $this->assertSame( 77777777, $rev->getPage() );
+ Wikimedia\restoreWarnings();
+ }
- MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
- $wgContLang->resetNamespaces(); # reset namespace cache
+ public function provideGetRevisionText() {
+ yield 'Generic test' => [
+ 'This is a goat of revision text.',
+ [
+ 'old_flags' => '',
+ 'old_text' => 'This is a goat of revision text.',
+ ],
+ ];
+ }
+
+ public function provideGetId() {
+ yield [
+ [],
+ null
+ ];
+ yield [
+ [ 'id' => 998 ],
+ 998
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetId
+ * @covers Revision::getId
+ */
+ public function testGetId( $rowArray, $expectedId ) {
+ $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
+ $this->assertEquals( $expectedId, $rev->getId() );
+ }
+
+ public function provideSetId() {
+ yield [ '123', 123 ];
+ yield [ 456, 456 ];
+ }
+
+ /**
+ * @dataProvider provideSetId
+ * @covers Revision::setId
+ */
+ public function testSetId( $input, $expected ) {
+ $rev = new Revision( [], 0, $this->getMockTitle() );
+ $rev->setId( $input );
+ $this->assertSame( $expected, $rev->getId() );
+ }
+
+ public function provideSetUserIdAndName() {
+ yield [ '123', 123, 'GOaT' ];
+ yield [ 456, 456, 'GOaT' ];
+ }
+
+ /**
+ * @dataProvider provideSetUserIdAndName
+ * @covers Revision::setUserIdAndName
+ */
+ public function testSetUserIdAndName( $inputId, $expectedId, $name ) {
+ $rev = new Revision( [], 0, $this->getMockTitle() );
+ $rev->setUserIdAndName( $inputId, $name );
+ $this->assertSame( $expectedId, $rev->getUser( Revision::RAW ) );
+ $this->assertEquals( $name, $rev->getUserText( Revision::RAW ) );
}
- function tearDown() {
- global $wgContLang;
+ public function provideGetTextId() {
+ yield [ [], null ];
+ yield [ [ 'text_id' => '123' ], 123 ];
+ yield [ [ 'text_id' => 456 ], 456 ];
+ }
- MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
- $wgContLang->resetNamespaces(); # reset namespace cache
+ /**
+ * @dataProvider provideGetTextId
+ * @covers Revision::getTextId()
+ */
+ public function testGetTextId( $rowArray, $expected ) {
+ $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
+ $this->assertSame( $expected, $rev->getTextId() );
+ }
- parent::tearDown();
+ public function provideGetParentId() {
+ yield [ [], null ];
+ yield [ [ 'parent_id' => '123' ], 123 ];
+ yield [ [ 'parent_id' => 456 ], 456 ];
+ }
+
+ /**
+ * @dataProvider provideGetParentId
+ * @covers Revision::getParentId()
+ */
+ public function testGetParentId( $rowArray, $expected ) {
+ $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
+ $this->assertSame( $expected, $rev->getParentId() );
}
/**
* @covers Revision::getRevisionText
+ * @dataProvider provideGetRevisionText
*/
- public function testGetRevisionText() {
- $row = new stdClass;
- $row->old_flags = '';
- $row->old_text = 'This is a bunch of revision text.';
+ public function testGetRevisionText( $expected, $rowData, $prefix = 'old_', $wiki = false ) {
$this->assertEquals(
- 'This is a bunch of revision text.',
- Revision::getRevisionText( $row ) );
+ $expected,
+ Revision::getRevisionText( (object)$rowData, $prefix, $wiki ) );
+ }
+
+ public function provideGetRevisionTextWithZlibExtension() {
+ yield 'Generic gzip test' => [
+ 'This is a small goat of revision text.',
+ [
+ 'old_flags' => 'gzip',
+ 'old_text' => gzdeflate( 'This is a small goat of revision text.' ),
+ ],
+ ];
}
/**
* @covers Revision::getRevisionText
+ * @dataProvider provideGetRevisionTextWithZlibExtension
*/
- public function testGetRevisionTextGzip() {
+ public function testGetRevisionWithZlibExtension( $expected, $rowData ) {
$this->checkPHPExtension( 'zlib' );
+ $this->testGetRevisionText( $expected, $rowData );
+ }
- $row = new stdClass;
- $row->old_flags = 'gzip';
- $row->old_text = gzdeflate( 'This is a bunch of revision text.' );
- $this->assertEquals(
- 'This is a bunch of revision text.',
- Revision::getRevisionText( $row ) );
+ private function getWANObjectCache() {
+ return new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
}
/**
- * @covers Revision::getRevisionText
+ * @return SqlBlobStore
*/
- public function testGetRevisionTextUtf8Native() {
- $row = new stdClass;
- $row->old_flags = 'utf-8';
- $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
- $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1';
- $this->assertEquals(
- "Wiki est l'\xc3\xa9cole superieur !",
- Revision::getRevisionText( $row ) );
+ private function getBlobStore() {
+ /** @var LoadBalancer $lb */
+ $lb = $this->getMockBuilder( LoadBalancer::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $cache = $this->getWANObjectCache();
+
+ $blobStore = new SqlBlobStore( $lb, $cache );
+ return $blobStore;
+ }
+
+ private function mockBlobStoreFactory( $blobStore ) {
+ /** @var LoadBalancer $lb */
+ $factory = $this->getMockBuilder( BlobStoreFactory::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $factory->expects( $this->any() )
+ ->method( 'newBlobStore' )
+ ->willReturn( $blobStore );
+ $factory->expects( $this->any() )
+ ->method( 'newSqlBlobStore' )
+ ->willReturn( $blobStore );
+ return $factory;
}
/**
- * @covers Revision::getRevisionText
+ * @return RevisionStore
*/
- public function testGetRevisionTextUtf8Legacy() {
- $row = new stdClass;
- $row->old_flags = '';
- $row->old_text = "Wiki est l'\xe9cole superieur !";
- $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1';
- $this->assertEquals(
+ private function getRevisionStore() {
+ /** @var LoadBalancer $lb */
+ $lb = $this->getMockBuilder( LoadBalancer::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $cache = $this->getWANObjectCache();
+
+ $blobStore = new RevisionStore(
+ $lb,
+ $this->getBlobStore(),
+ $cache,
+ MediaWikiServices::getInstance()->getCommentStore(),
+ MediaWikiServices::getInstance()->getActorMigration()
+ );
+ return $blobStore;
+ }
+
+ public function provideGetRevisionTextWithLegacyEncoding() {
+ yield 'Utf8Native' => [
"Wiki est l'\xc3\xa9cole superieur !",
- Revision::getRevisionText( $row ) );
+ 'fr',
+ 'iso-8859-1',
+ [
+ 'old_flags' => 'utf-8',
+ 'old_text' => "Wiki est l'\xc3\xa9cole superieur !",
+ ]
+ ];
+ yield 'Utf8Legacy' => [
+ "Wiki est l'\xc3\xa9cole superieur !",
+ 'fr',
+ 'iso-8859-1',
+ [
+ 'old_flags' => '',
+ 'old_text' => "Wiki est l'\xe9cole superieur !",
+ ]
+ ];
}
/**
* @covers Revision::getRevisionText
+ * @dataProvider provideGetRevisionTextWithLegacyEncoding
*/
- public function testGetRevisionTextUtf8NativeGzip() {
- $this->checkPHPExtension( 'zlib' );
+ public function testGetRevisionWithLegacyEncoding( $expected, $lang, $encoding, $rowData ) {
+ $blobStore = $this->getBlobStore();
+ $blobStore->setLegacyEncoding( $encoding, Language::factory( $lang ) );
+ $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
- $row = new stdClass;
- $row->old_flags = 'gzip,utf-8';
- $row->old_text = gzdeflate( "Wiki est l'\xc3\xa9cole superieur !" );
- $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1';
- $this->assertEquals(
+ $this->testGetRevisionText( $expected, $rowData );
+ }
+
+ public function provideGetRevisionTextWithGzipAndLegacyEncoding() {
+ /**
+ * WARNING!
+ * Do not set the external flag!
+ * Otherwise, getRevisionText will hit the live database (if ExternalStore is enabled)!
+ */
+ yield 'Utf8NativeGzip' => [
+ "Wiki est l'\xc3\xa9cole superieur !",
+ 'fr',
+ 'iso-8859-1',
+ [
+ 'old_flags' => 'gzip,utf-8',
+ 'old_text' => gzdeflate( "Wiki est l'\xc3\xa9cole superieur !" ),
+ ]
+ ];
+ yield 'Utf8LegacyGzip' => [
"Wiki est l'\xc3\xa9cole superieur !",
- Revision::getRevisionText( $row ) );
+ 'fr',
+ 'iso-8859-1',
+ [
+ 'old_flags' => 'gzip',
+ 'old_text' => gzdeflate( "Wiki est l'\xe9cole superieur !" ),
+ ]
+ ];
}
/**
* @covers Revision::getRevisionText
+ * @dataProvider provideGetRevisionTextWithGzipAndLegacyEncoding
*/
- public function testGetRevisionTextUtf8LegacyGzip() {
+ public function testGetRevisionWithGzipAndLegacyEncoding( $expected, $lang, $encoding, $rowData ) {
$this->checkPHPExtension( 'zlib' );
- $row = new stdClass;
- $row->old_flags = 'gzip';
- $row->old_text = gzdeflate( "Wiki est l'\xe9cole superieur !" );
- $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1';
- $this->assertEquals(
- "Wiki est l'\xc3\xa9cole superieur !",
- Revision::getRevisionText( $row ) );
+ $blobStore = $this->getBlobStore();
+ $blobStore->setLegacyEncoding( $encoding, Language::factory( $lang ) );
+ $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
+
+ $this->testGetRevisionText( $expected, $rowData );
}
/**
@@ -158,7 +588,10 @@ class RevisionTest extends MediaWikiTestCase {
*/
public function testCompressRevisionTextUtf8Gzip() {
$this->checkPHPExtension( 'zlib' );
- $this->setMwGlobals( 'wgCompressRevisions', true );
+
+ $blobStore = $this->getBlobStore();
+ $blobStore->setCompressBlobs( true );
+ $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
$row = new stdClass;
$row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
@@ -173,293 +606,893 @@ class RevisionTest extends MediaWikiTestCase {
Revision::getRevisionText( $row ), "getRevisionText" );
}
- # =========================================================================
+ /**
+ * @covers Revision::loadFromTitle
+ */
+ public function testLoadFromTitle() {
+ $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->overrideMwServices();
+ $title = $this->getMockTitle();
+
+ $conditions = [
+ 'rev_id=page_latest',
+ 'page_namespace' => $title->getNamespace(),
+ 'page_title' => $title->getDBkey()
+ ];
+
+ $row = (object)[
+ 'rev_id' => '42',
+ 'rev_page' => $title->getArticleID(),
+ 'rev_text_id' => '2',
+ 'rev_timestamp' => '20171017114835',
+ 'rev_user_text' => '127.0.0.1',
+ 'rev_user' => '0',
+ 'rev_minor_edit' => '0',
+ 'rev_deleted' => '0',
+ 'rev_len' => '46',
+ 'rev_parent_id' => '1',
+ 'rev_sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
+ 'rev_comment_text' => 'Goat Comment!',
+ 'rev_comment_data' => null,
+ 'rev_comment_cid' => null,
+ 'rev_content_format' => 'GOATFORMAT',
+ 'rev_content_model' => 'GOATMODEL',
+ ];
+
+ $db = $this->getMock( IDatabase::class );
+ $db->expects( $this->any() )
+ ->method( 'getDomainId' )
+ ->will( $this->returnValue( wfWikiID() ) );
+ $db->expects( $this->once() )
+ ->method( 'selectRow' )
+ ->with(
+ $this->equalTo( [ 'revision', 'page', 'user' ] ),
+ // We don't really care about the fields are they come from the selectField methods
+ $this->isType( 'array' ),
+ $this->equalTo( $conditions ),
+ // Method name
+ $this->stringContains( 'fetchRevisionRowFromConds' ),
+ // We don't really care about the options here
+ $this->isType( 'array' ),
+ // We don't really care about the join conds are they come from the joinCond methods
+ $this->isType( 'array' )
+ )
+ ->willReturn( $row );
+
+ $revision = Revision::loadFromTitle( $db, $title );
+
+ $this->assertEquals( $title->getArticleID(), $revision->getTitle()->getArticleID() );
+ $this->assertEquals( $row->rev_id, $revision->getId() );
+ $this->assertEquals( $row->rev_len, $revision->getSize() );
+ $this->assertEquals( $row->rev_sha1, $revision->getSha1() );
+ $this->assertEquals( $row->rev_parent_id, $revision->getParentId() );
+ $this->assertEquals( $row->rev_timestamp, $revision->getTimestamp() );
+ $this->assertEquals( $row->rev_comment_text, $revision->getComment() );
+ $this->assertEquals( $row->rev_user_text, $revision->getUserText() );
+ }
+
+ public function provideDecompressRevisionText() {
+ yield '(no legacy encoding), false in false out' => [ false, false, [], false ];
+ yield '(no legacy encoding), empty in empty out' => [ false, '', [], '' ];
+ yield '(no legacy encoding), empty in empty out' => [ false, 'A', [], 'A' ];
+ yield '(no legacy encoding), string in with gzip flag returns string' => [
+ // gzip string below generated with gzdeflate( 'AAAABBAAA' )
+ false, "sttttr\002\022\000", [ 'gzip' ], 'AAAABBAAA',
+ ];
+ yield '(no legacy encoding), string in with object flag returns false' => [
+ // gzip string below generated with serialize( 'JOJO' )
+ false, "s:4:\"JOJO\";", [ 'object' ], false,
+ ];
+ yield '(no legacy encoding), serialized object in with object flag returns string' => [
+ false,
+ // Using a TitleValue object as it has a getText method (which is needed)
+ serialize( new TitleValue( 0, 'HHJJDDFF' ) ),
+ [ 'object' ],
+ 'HHJJDDFF',
+ ];
+ yield '(no legacy encoding), serialized object in with object & gzip flag returns string' => [
+ false,
+ // Using a TitleValue object as it has a getText method (which is needed)
+ gzdeflate( serialize( new TitleValue( 0, '8219JJJ840' ) ) ),
+ [ 'object', 'gzip' ],
+ '8219JJJ840',
+ ];
+ yield '(ISO-8859-1 encoding), string in string out' => [
+ 'ISO-8859-1',
+ iconv( 'utf-8', 'ISO-8859-1', "1®Àþ1" ),
+ [],
+ '1®Àþ1',
+ ];
+ yield '(ISO-8859-1 encoding), serialized object in with gzip flags returns string' => [
+ 'ISO-8859-1',
+ gzdeflate( iconv( 'utf-8', 'ISO-8859-1', "4®Àþ4" ) ),
+ [ 'gzip' ],
+ '4®Àþ4',
+ ];
+ yield '(ISO-8859-1 encoding), serialized object in with object flags returns string' => [
+ 'ISO-8859-1',
+ serialize( new TitleValue( 0, iconv( 'utf-8', 'ISO-8859-1', "3®Àþ3" ) ) ),
+ [ 'object' ],
+ '3®Àþ3',
+ ];
+ yield '(ISO-8859-1 encoding), serialized object in with object & gzip flags returns string' => [
+ 'ISO-8859-1',
+ gzdeflate( serialize( new TitleValue( 0, iconv( 'utf-8', 'ISO-8859-1', "2®Àþ2" ) ) ) ),
+ [ 'gzip', 'object' ],
+ '2®Àþ2',
+ ];
+ }
/**
- * @param string $text
- * @param string $title
- * @param string $model
- * @param string $format
+ * @dataProvider provideDecompressRevisionText
+ * @covers Revision::decompressRevisionText
*
- * @return Revision
+ * @param bool $legacyEncoding
+ * @param mixed $text
+ * @param array $flags
+ * @param mixed $expected
*/
- function newTestRevision( $text, $title = "Test",
- $model = CONTENT_MODEL_WIKITEXT, $format = null
- ) {
- if ( is_string( $title ) ) {
- $title = Title::newFromText( $title );
+ public function testDecompressRevisionText( $legacyEncoding, $text, $flags, $expected ) {
+ $blobStore = $this->getBlobStore();
+ if ( $legacyEncoding ) {
+ $blobStore->setLegacyEncoding( $legacyEncoding, Language::factory( 'en' ) );
}
- $content = ContentHandler::makeContent( $text, $title, $model, $format );
+ $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
+ $this->assertSame(
+ $expected,
+ Revision::decompressRevisionText( $text, $flags )
+ );
+ }
- $rev = new Revision(
- [
- 'id' => 42,
- 'page' => 23,
- 'title' => $title,
+ /**
+ * @covers Revision::getRevisionText
+ */
+ public function testGetRevisionText_returnsFalseWhenNoTextField() {
+ $this->assertFalse( Revision::getRevisionText( new stdClass() ) );
+ }
- 'content' => $content,
- 'length' => $content->getSize(),
- 'comment' => "testing",
- 'minor_edit' => false,
+ public function provideTestGetRevisionText_returnsDecompressedTextFieldWhenNotExternal() {
+ yield 'Just text' => [
+ (object)[ 'old_text' => 'SomeText' ],
+ 'old_',
+ 'SomeText'
+ ];
+ // gzip string below generated with gzdeflate( 'AAAABBAAA' )
+ yield 'gzip text' => [
+ (object)[
+ 'old_text' => "sttttr\002\022\000",
+ 'old_flags' => 'gzip'
+ ],
+ 'old_',
+ 'AAAABBAAA'
+ ];
+ yield 'gzip text and different prefix' => [
+ (object)[
+ 'jojo_text' => "sttttr\002\022\000",
+ 'jojo_flags' => 'gzip'
+ ],
+ 'jojo_',
+ 'AAAABBAAA'
+ ];
+ }
- 'content_format' => $format,
- ]
- );
+ /**
+ * @dataProvider provideTestGetRevisionText_returnsDecompressedTextFieldWhenNotExternal
+ * @covers Revision::getRevisionText
+ */
+ public function testGetRevisionText_returnsDecompressedTextFieldWhenNotExternal(
+ $row,
+ $prefix,
+ $expected
+ ) {
+ $this->assertSame( $expected, Revision::getRevisionText( $row, $prefix ) );
+ }
- return $rev;
+ public function provideTestGetRevisionText_external_returnsFalseWhenNotEnoughUrlParts() {
+ yield 'Just some text' => [ 'someNonUrlText' ];
+ yield 'No second URL part' => [ 'someProtocol://' ];
}
- function dataGetContentModel() {
- // NOTE: we expect the help namespace to always contain wikitext
- return [
- [ 'hello world', 'Help:Hello', null, null, CONTENT_MODEL_WIKITEXT ],
- [ 'hello world', 'User:hello/there.css', null, null, CONTENT_MODEL_CSS ],
- [ serialize( 'hello world' ), 'Dummy:Hello', null, null, "testing" ],
- ];
+ /**
+ * @dataProvider provideTestGetRevisionText_external_returnsFalseWhenNotEnoughUrlParts
+ * @covers Revision::getRevisionText
+ */
+ public function testGetRevisionText_external_returnsFalseWhenNotEnoughUrlParts(
+ $text
+ ) {
+ $this->assertFalse(
+ Revision::getRevisionText(
+ (object)[
+ 'old_text' => $text,
+ 'old_flags' => 'external',
+ ]
+ )
+ );
}
/**
- * @group Database
- * @dataProvider dataGetContentModel
- * @covers Revision::getContentModel
+ * @covers Revision::getRevisionText
*/
- public function testGetContentModel( $text, $title, $model, $format, $expectedModel ) {
- $rev = $this->newTestRevision( $text, $title, $model, $format );
+ public function testGetRevisionText_external_noOldId() {
+ $this->setService(
+ 'ExternalStoreFactory',
+ new ExternalStoreFactory( [ 'ForTesting' ] )
+ );
+ $this->assertSame(
+ 'AAAABBAAA',
+ Revision::getRevisionText(
+ (object)[
+ 'old_text' => 'ForTesting://cluster1/12345',
+ 'old_flags' => 'external,gzip',
+ ]
+ )
+ );
+ }
- $this->assertEquals( $expectedModel, $rev->getContentModel() );
+ /**
+ * @covers Revision::getRevisionText
+ */
+ public function testGetRevisionText_external_oldId() {
+ $cache = $this->getWANObjectCache();
+ $this->setService( 'MainWANObjectCache', $cache );
+
+ $this->setService(
+ 'ExternalStoreFactory',
+ new ExternalStoreFactory( [ 'ForTesting' ] )
+ );
+
+ $lb = $this->getMockBuilder( LoadBalancer::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $blobStore = new SqlBlobStore( $lb, $cache );
+ $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
+
+ $this->assertSame(
+ 'AAAABBAAA',
+ Revision::getRevisionText(
+ (object)[
+ 'old_text' => 'ForTesting://cluster1/12345',
+ 'old_flags' => 'external,gzip',
+ 'old_id' => '7777',
+ ]
+ )
+ );
+
+ $cacheKey = $cache->makeKey( 'revisiontext', 'textid', 'tt:7777' );
+ $this->assertSame( 'AAAABBAAA', $cache->get( $cacheKey ) );
}
- function dataGetContentFormat() {
- // NOTE: we expect the help namespace to always contain wikitext
- return [
- [ 'hello world', 'Help:Hello', null, null, CONTENT_FORMAT_WIKITEXT ],
- [ 'hello world', 'Help:Hello', CONTENT_MODEL_CSS, null, CONTENT_FORMAT_CSS ],
- [ 'hello world', 'User:hello/there.css', null, null, CONTENT_FORMAT_CSS ],
- [ serialize( 'hello world' ), 'Dummy:Hello', null, null, "testing" ],
- ];
+ /**
+ * @covers Revision::userJoinCond
+ */
+ public function testUserJoinCond() {
+ $this->hideDeprecated( 'Revision::userJoinCond' );
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->overrideMwServices();
+ $this->assertEquals(
+ [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
+ Revision::userJoinCond()
+ );
}
/**
- * @group Database
- * @dataProvider dataGetContentFormat
- * @covers Revision::getContentFormat
+ * @covers Revision::pageJoinCond
*/
- public function testGetContentFormat( $text, $title, $model, $format, $expectedFormat ) {
- $rev = $this->newTestRevision( $text, $title, $model, $format );
+ public function testPageJoinCond() {
+ $this->hideDeprecated( 'Revision::pageJoinCond' );
+ $this->assertEquals(
+ [ 'INNER JOIN', [ 'page_id = rev_page' ] ],
+ Revision::pageJoinCond()
+ );
+ }
- $this->assertEquals( $expectedFormat, $rev->getContentFormat() );
+ private function overrideCommentStoreAndActorMigration() {
+ $mockStore = $this->getMockBuilder( CommentStore::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mockStore->expects( $this->any() )
+ ->method( 'getFields' )
+ ->willReturn( [ 'commentstore' => 'fields' ] );
+ $mockStore->expects( $this->any() )
+ ->method( 'getJoin' )
+ ->willReturn( [
+ 'tables' => [ 'commentstore' => 'table' ],
+ 'fields' => [ 'commentstore' => 'field' ],
+ 'joins' => [ 'commentstore' => 'join' ],
+ ] );
+ $this->setService( 'CommentStore', $mockStore );
+
+ $mockStore = $this->getMockBuilder( ActorMigration::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mockStore->expects( $this->any() )
+ ->method( 'getJoin' )
+ ->willReturnCallback( function ( $key ) {
+ $p = strtok( $key, '_' );
+ return [
+ 'tables' => [ 'actormigration' => 'table' ],
+ 'fields' => [
+ $p . '_user' => 'actormigration_user',
+ $p . '_user_text' => 'actormigration_user_text',
+ $p . '_actor' => 'actormigration_actor',
+ ],
+ 'joins' => [ 'actormigration' => 'join' ],
+ ];
+ } );
+ $this->setService( 'ActorMigration', $mockStore );
}
- function dataGetContentHandler() {
- // NOTE: we expect the help namespace to always contain wikitext
- return [
- [ 'hello world', 'Help:Hello', null, null, 'WikitextContentHandler' ],
- [ 'hello world', 'User:hello/there.css', null, null, 'CssContentHandler' ],
- [ serialize( 'hello world' ), 'Dummy:Hello', null, null, 'DummyContentHandlerForTesting' ],
+ public function provideSelectFields() {
+ yield [
+ true,
+ [
+ 'rev_id',
+ 'rev_page',
+ 'rev_text_id',
+ 'rev_timestamp',
+ 'rev_user_text',
+ 'rev_user',
+ 'rev_actor' => 'NULL',
+ 'rev_minor_edit',
+ 'rev_deleted',
+ 'rev_len',
+ 'rev_parent_id',
+ 'rev_sha1',
+ 'commentstore' => 'fields',
+ 'rev_content_format',
+ 'rev_content_model',
+ ]
+ ];
+ yield [
+ false,
+ [
+ 'rev_id',
+ 'rev_page',
+ 'rev_text_id',
+ 'rev_timestamp',
+ 'rev_user_text',
+ 'rev_user',
+ 'rev_actor' => 'NULL',
+ 'rev_minor_edit',
+ 'rev_deleted',
+ 'rev_len',
+ 'rev_parent_id',
+ 'rev_sha1',
+ 'commentstore' => 'fields',
+ ]
];
}
/**
- * @group Database
- * @dataProvider dataGetContentHandler
- * @covers Revision::getContentHandler
+ * @dataProvider provideSelectFields
+ * @covers Revision::selectFields
*/
- public function testGetContentHandler( $text, $title, $model, $format, $expectedClass ) {
- $rev = $this->newTestRevision( $text, $title, $model, $format );
-
- $this->assertEquals( $expectedClass, get_class( $rev->getContentHandler() ) );
+ public function testSelectFields( $contentHandlerUseDB, $expected ) {
+ $this->hideDeprecated( 'Revision::selectFields' );
+ $this->setMwGlobals( 'wgContentHandlerUseDB', $contentHandlerUseDB );
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->overrideCommentStoreAndActorMigration();
+ $this->assertEquals( $expected, Revision::selectFields() );
}
- function dataGetContent() {
- // NOTE: we expect the help namespace to always contain wikitext
- return [
- [ 'hello world', 'Help:Hello', null, null, Revision::FOR_PUBLIC, 'hello world' ],
+ public function provideSelectArchiveFields() {
+ yield [
+ true,
[
- serialize( 'hello world' ),
- 'Hello',
- "testing",
- null,
- Revision::FOR_PUBLIC,
- serialize( 'hello world' )
- ],
+ 'ar_id',
+ 'ar_page_id',
+ 'ar_rev_id',
+ 'ar_text_id',
+ 'ar_timestamp',
+ 'ar_user_text',
+ 'ar_user',
+ 'ar_actor' => 'NULL',
+ 'ar_minor_edit',
+ 'ar_deleted',
+ 'ar_len',
+ 'ar_parent_id',
+ 'ar_sha1',
+ 'commentstore' => 'fields',
+ 'ar_content_format',
+ 'ar_content_model',
+ ]
+ ];
+ yield [
+ false,
[
- serialize( 'hello world' ),
- 'Dummy:Hello',
- null,
- null,
- Revision::FOR_PUBLIC,
- serialize( 'hello world' )
- ],
+ 'ar_id',
+ 'ar_page_id',
+ 'ar_rev_id',
+ 'ar_text_id',
+ 'ar_timestamp',
+ 'ar_user_text',
+ 'ar_user',
+ 'ar_actor' => 'NULL',
+ 'ar_minor_edit',
+ 'ar_deleted',
+ 'ar_len',
+ 'ar_parent_id',
+ 'ar_sha1',
+ 'commentstore' => 'fields',
+ ]
];
}
/**
- * @group Database
- * @dataProvider dataGetContent
- * @covers Revision::getContent
+ * @dataProvider provideSelectArchiveFields
+ * @covers Revision::selectArchiveFields
*/
- public function testGetContent( $text, $title, $model, $format,
- $audience, $expectedSerialization
- ) {
- $rev = $this->newTestRevision( $text, $title, $model, $format );
- $content = $rev->getContent( $audience );
+ public function testSelectArchiveFields( $contentHandlerUseDB, $expected ) {
+ $this->hideDeprecated( 'Revision::selectArchiveFields' );
+ $this->setMwGlobals( 'wgContentHandlerUseDB', $contentHandlerUseDB );
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->overrideCommentStoreAndActorMigration();
+ $this->assertEquals( $expected, Revision::selectArchiveFields() );
+ }
+ /**
+ * @covers Revision::selectTextFields
+ */
+ public function testSelectTextFields() {
+ $this->hideDeprecated( 'Revision::selectTextFields' );
$this->assertEquals(
- $expectedSerialization,
- is_null( $content ) ? null : $content->serialize( $format )
+ [
+ 'old_text',
+ 'old_flags',
+ ],
+ Revision::selectTextFields()
);
}
- public function dataGetSize() {
- return [
- [ "hello world.", CONTENT_MODEL_WIKITEXT, 12 ],
- [ serialize( "hello world." ), "testing", 12 ],
- ];
+ /**
+ * @covers Revision::selectPageFields
+ */
+ public function testSelectPageFields() {
+ $this->hideDeprecated( 'Revision::selectPageFields' );
+ $this->assertEquals(
+ [
+ 'page_namespace',
+ 'page_title',
+ 'page_id',
+ 'page_latest',
+ 'page_is_redirect',
+ 'page_len',
+ ],
+ Revision::selectPageFields()
+ );
}
/**
- * @covers Revision::getSize
- * @group Database
- * @dataProvider dataGetSize
+ * @covers Revision::selectUserFields
*/
- public function testGetSize( $text, $model, $expected_size ) {
- $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSize', $model );
- $this->assertEquals( $expected_size, $rev->getSize() );
+ public function testSelectUserFields() {
+ $this->hideDeprecated( 'Revision::selectUserFields' );
+ $this->assertEquals(
+ [
+ 'user_name',
+ ],
+ Revision::selectUserFields()
+ );
}
- public function dataGetSha1() {
- return [
- [ "hello world.", CONTENT_MODEL_WIKITEXT, Revision::base36Sha1( "hello world." ) ],
+ public function provideGetArchiveQueryInfo() {
+ yield 'wgContentHandlerUseDB false' => [
[
- serialize( "hello world." ),
- "testing",
- Revision::base36Sha1( serialize( "hello world." ) )
+ 'wgContentHandlerUseDB' => false,
],
+ [
+ 'tables' => [
+ 'archive',
+ 'commentstore' => 'table',
+ 'actormigration' => 'table',
+ ],
+ 'fields' => [
+ 'ar_id',
+ 'ar_page_id',
+ 'ar_namespace',
+ 'ar_title',
+ 'ar_rev_id',
+ 'ar_text_id',
+ 'ar_timestamp',
+ 'ar_minor_edit',
+ 'ar_deleted',
+ 'ar_len',
+ 'ar_parent_id',
+ 'ar_sha1',
+ 'commentstore' => 'field',
+ 'ar_user' => 'actormigration_user',
+ 'ar_user_text' => 'actormigration_user_text',
+ 'ar_actor' => 'actormigration_actor',
+ ],
+ 'joins' => [ 'commentstore' => 'join', 'actormigration' => 'join' ],
+ ]
+ ];
+ yield 'wgContentHandlerUseDB true' => [
+ [
+ 'wgContentHandlerUseDB' => true,
+ ],
+ [
+ 'tables' => [
+ 'archive',
+ 'commentstore' => 'table',
+ 'actormigration' => 'table',
+ ],
+ 'fields' => [
+ 'ar_id',
+ 'ar_page_id',
+ 'ar_namespace',
+ 'ar_title',
+ 'ar_rev_id',
+ 'ar_text_id',
+ 'ar_timestamp',
+ 'ar_minor_edit',
+ 'ar_deleted',
+ 'ar_len',
+ 'ar_parent_id',
+ 'ar_sha1',
+ 'commentstore' => 'field',
+ 'ar_user' => 'actormigration_user',
+ 'ar_user_text' => 'actormigration_user_text',
+ 'ar_actor' => 'actormigration_actor',
+ 'ar_content_format',
+ 'ar_content_model',
+ ],
+ 'joins' => [ 'commentstore' => 'join', 'actormigration' => 'join' ],
+ ]
];
}
/**
- * @covers Revision::getSha1
- * @group Database
- * @dataProvider dataGetSha1
+ * @covers Revision::getArchiveQueryInfo
+ * @dataProvider provideGetArchiveQueryInfo
*/
- public function testGetSha1( $text, $model, $expected_hash ) {
- $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSha1', $model );
- $this->assertEquals( $expected_hash, $rev->getSha1() );
+ public function testGetArchiveQueryInfo( $globals, $expected ) {
+ $this->setMwGlobals( $globals );
+ $this->overrideCommentStoreAndActorMigration();
+
+ $revisionStore = $this->getRevisionStore();
+ $revisionStore->setContentHandlerUseDB( $globals['wgContentHandlerUseDB'] );
+ $this->setService( 'RevisionStore', $revisionStore );
+ $this->assertEquals(
+ $expected,
+ Revision::getArchiveQueryInfo()
+ );
+ }
+
+ public function provideGetQueryInfo() {
+ yield 'wgContentHandlerUseDB false, opts none' => [
+ [
+ 'wgContentHandlerUseDB' => false,
+ ],
+ [],
+ [
+ 'tables' => [ 'revision', 'commentstore' => 'table', 'actormigration' => 'table' ],
+ 'fields' => [
+ 'rev_id',
+ 'rev_page',
+ 'rev_text_id',
+ 'rev_timestamp',
+ 'rev_minor_edit',
+ 'rev_deleted',
+ 'rev_len',
+ 'rev_parent_id',
+ 'rev_sha1',
+ 'commentstore' => 'field',
+ 'rev_user' => 'actormigration_user',
+ 'rev_user_text' => 'actormigration_user_text',
+ 'rev_actor' => 'actormigration_actor',
+ ],
+ 'joins' => [ 'commentstore' => 'join', 'actormigration' => 'join' ],
+ ],
+ ];
+ yield 'wgContentHandlerUseDB false, opts page' => [
+ [
+ 'wgContentHandlerUseDB' => false,
+ ],
+ [ 'page' ],
+ [
+ 'tables' => [ 'revision', 'commentstore' => 'table', 'actormigration' => 'table', 'page' ],
+ 'fields' => [
+ 'rev_id',
+ 'rev_page',
+ 'rev_text_id',
+ 'rev_timestamp',
+ 'rev_minor_edit',
+ 'rev_deleted',
+ 'rev_len',
+ 'rev_parent_id',
+ 'rev_sha1',
+ 'commentstore' => 'field',
+ 'rev_user' => 'actormigration_user',
+ 'rev_user_text' => 'actormigration_user_text',
+ 'rev_actor' => 'actormigration_actor',
+ 'page_namespace',
+ 'page_title',
+ 'page_id',
+ 'page_latest',
+ 'page_is_redirect',
+ 'page_len',
+ ],
+ 'joins' => [
+ 'page' => [
+ 'INNER JOIN',
+ [ 'page_id = rev_page' ],
+ ],
+ 'commentstore' => 'join',
+ 'actormigration' => 'join',
+ ],
+ ],
+ ];
+ yield 'wgContentHandlerUseDB false, opts user' => [
+ [
+ 'wgContentHandlerUseDB' => false,
+ ],
+ [ 'user' ],
+ [
+ 'tables' => [ 'revision', 'commentstore' => 'table', 'actormigration' => 'table', 'user' ],
+ 'fields' => [
+ 'rev_id',
+ 'rev_page',
+ 'rev_text_id',
+ 'rev_timestamp',
+ 'rev_minor_edit',
+ 'rev_deleted',
+ 'rev_len',
+ 'rev_parent_id',
+ 'rev_sha1',
+ 'commentstore' => 'field',
+ 'rev_user' => 'actormigration_user',
+ 'rev_user_text' => 'actormigration_user_text',
+ 'rev_actor' => 'actormigration_actor',
+ 'user_name',
+ ],
+ 'joins' => [
+ 'user' => [
+ 'LEFT JOIN',
+ [
+ 'actormigration_user != 0',
+ 'user_id = actormigration_user',
+ ],
+ ],
+ 'commentstore' => 'join',
+ 'actormigration' => 'join',
+ ],
+ ],
+ ];
+ yield 'wgContentHandlerUseDB false, opts text' => [
+ [
+ 'wgContentHandlerUseDB' => false,
+ ],
+ [ 'text' ],
+ [
+ 'tables' => [ 'revision', 'commentstore' => 'table', 'actormigration' => 'table', 'text' ],
+ 'fields' => [
+ 'rev_id',
+ 'rev_page',
+ 'rev_text_id',
+ 'rev_timestamp',
+ 'rev_minor_edit',
+ 'rev_deleted',
+ 'rev_len',
+ 'rev_parent_id',
+ 'rev_sha1',
+ 'commentstore' => 'field',
+ 'rev_user' => 'actormigration_user',
+ 'rev_user_text' => 'actormigration_user_text',
+ 'rev_actor' => 'actormigration_actor',
+ 'old_text',
+ 'old_flags',
+ ],
+ 'joins' => [
+ 'text' => [
+ 'INNER JOIN',
+ [ 'rev_text_id=old_id' ],
+ ],
+ 'commentstore' => 'join',
+ 'actormigration' => 'join',
+ ],
+ ],
+ ];
+ yield 'wgContentHandlerUseDB false, opts 3' => [
+ [
+ 'wgContentHandlerUseDB' => false,
+ ],
+ [ 'text', 'page', 'user' ],
+ [
+ 'tables' => [
+ 'revision', 'commentstore' => 'table', 'actormigration' => 'table', 'page', 'user', 'text'
+ ],
+ 'fields' => [
+ 'rev_id',
+ 'rev_page',
+ 'rev_text_id',
+ 'rev_timestamp',
+ 'rev_minor_edit',
+ 'rev_deleted',
+ 'rev_len',
+ 'rev_parent_id',
+ 'rev_sha1',
+ 'commentstore' => 'field',
+ 'rev_user' => 'actormigration_user',
+ 'rev_user_text' => 'actormigration_user_text',
+ 'rev_actor' => 'actormigration_actor',
+ 'page_namespace',
+ 'page_title',
+ 'page_id',
+ 'page_latest',
+ 'page_is_redirect',
+ 'page_len',
+ 'user_name',
+ 'old_text',
+ 'old_flags',
+ ],
+ 'joins' => [
+ 'page' => [
+ 'INNER JOIN',
+ [ 'page_id = rev_page' ],
+ ],
+ 'user' => [
+ 'LEFT JOIN',
+ [
+ 'actormigration_user != 0',
+ 'user_id = actormigration_user',
+ ],
+ ],
+ 'text' => [
+ 'INNER JOIN',
+ [ 'rev_text_id=old_id' ],
+ ],
+ 'commentstore' => 'join',
+ 'actormigration' => 'join',
+ ],
+ ],
+ ];
+ yield 'wgContentHandlerUseDB true, opts none' => [
+ [
+ 'wgContentHandlerUseDB' => true,
+ ],
+ [],
+ [
+ 'tables' => [ 'revision', 'commentstore' => 'table', 'actormigration' => 'table' ],
+ 'fields' => [
+ 'rev_id',
+ 'rev_page',
+ 'rev_text_id',
+ 'rev_timestamp',
+ 'rev_minor_edit',
+ 'rev_deleted',
+ 'rev_len',
+ 'rev_parent_id',
+ 'rev_sha1',
+ 'commentstore' => 'field',
+ 'rev_user' => 'actormigration_user',
+ 'rev_user_text' => 'actormigration_user_text',
+ 'rev_actor' => 'actormigration_actor',
+ 'rev_content_format',
+ 'rev_content_model',
+ ],
+ 'joins' => [ 'commentstore' => 'join', 'actormigration' => 'join' ],
+ ],
+ ];
}
/**
- * @covers Revision::__construct
+ * @covers Revision::getQueryInfo
+ * @dataProvider provideGetQueryInfo
*/
- public function testConstructWithText() {
- $rev = new Revision( [
- 'text' => 'hello world.',
- 'content_model' => CONTENT_MODEL_JAVASCRIPT
- ] );
+ public function testGetQueryInfo( $globals, $options, $expected ) {
+ $this->setMwGlobals( $globals );
+ $this->overrideCommentStoreAndActorMigration();
- $this->assertNotNull( $rev->getContent(), 'no content object available' );
- $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() );
- $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
+ $revisionStore = $this->getRevisionStore();
+ $revisionStore->setContentHandlerUseDB( $globals['wgContentHandlerUseDB'] );
+ $this->setService( 'RevisionStore', $revisionStore );
+
+ $this->assertEquals(
+ $expected,
+ Revision::getQueryInfo( $options )
+ );
}
/**
- * @covers Revision::__construct
+ * @covers Revision::getSize
*/
- public function testConstructWithContent() {
- $title = Title::newFromText( 'RevisionTest_testConstructWithContent' );
+ public function testGetSize() {
+ $title = $this->getMockTitle();
- $rev = new Revision( [
- 'content' => ContentHandler::makeContent( 'hello world.', $title, CONTENT_MODEL_JAVASCRIPT ),
- ] );
+ $rec = new MutableRevisionRecord( $title );
+ $rev = new Revision( $rec, 0, $title );
- $this->assertNotNull( $rev->getContent(), 'no content object available' );
- $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() );
- $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
+ $this->assertSame( 0, $rev->getSize(), 'Size of no slots is 0' );
+
+ $rec->setSize( 13 );
+ $this->assertSame( 13, $rev->getSize() );
}
/**
- * Tests whether $rev->getContent() returns a clone when needed.
- *
- * @group Database
- * @covers Revision::getContent
+ * @covers Revision::getSize
*/
- public function testGetContentClone() {
- $content = new RevisionTestModifyableContent( "foo" );
+ public function testGetSize_failure() {
+ $title = $this->getMockTitle();
- $rev = new Revision(
- [
- 'id' => 42,
- 'page' => 23,
- 'title' => Title::newFromText( "testGetContentClone_dummy" ),
+ $rec = $this->getMockBuilder( RevisionRecord::class )
+ ->disableOriginalConstructor()
+ ->getMock();
- 'content' => $content,
- 'length' => $content->getSize(),
- 'comment' => "testing",
- 'minor_edit' => false,
- ]
- );
+ $rec->method( 'getSize' )
+ ->willThrowException( new RevisionAccessException( 'Oops!' ) );
- $content = $rev->getContent( Revision::RAW );
- $content->setText( "bar" );
+ $rev = new Revision( $rec, 0, $title );
+ $this->assertNull( $rev->getSize() );
+ }
- $content2 = $rev->getContent( Revision::RAW );
- // content is mutable, expect clone
- $this->assertNotSame( $content, $content2, "expected a clone" );
- // clone should contain the original text
- $this->assertEquals( "foo", $content2->getText() );
+ /**
+ * @covers Revision::getSha1
+ */
+ public function testGetSha1() {
+ $title = $this->getMockTitle();
+
+ $rec = new MutableRevisionRecord( $title );
+ $rev = new Revision( $rec, 0, $title );
+
+ $emptyHash = SlotRecord::base36Sha1( '' );
+ $this->assertSame( $emptyHash, $rev->getSha1(), 'Sha1 of no slots is hash of empty string' );
- $content2->setText( "bla bla" );
- $this->assertEquals( "bar", $content->getText() ); // clones should be independent
+ $rec->setSha1( 'deadbeef' );
+ $this->assertSame( 'deadbeef', $rev->getSha1() );
}
/**
- * Tests whether $rev->getContent() returns the same object repeatedly if appropriate.
- *
- * @group Database
- * @covers Revision::getContent
+ * @covers Revision::getSha1
*/
- public function testGetContentUncloned() {
- $rev = $this->newTestRevision( "hello", "testGetContentUncloned_dummy", CONTENT_MODEL_WIKITEXT );
- $content = $rev->getContent( Revision::RAW );
- $content2 = $rev->getContent( Revision::RAW );
+ public function testGetSha1_failure() {
+ $title = $this->getMockTitle();
- // for immutable content like wikitext, this should be the same object
- $this->assertSame( $content, $content2 );
- }
-}
+ $rec = $this->getMockBuilder( RevisionRecord::class )
+ ->disableOriginalConstructor()
+ ->getMock();
-class RevisionTestModifyableContent extends TextContent {
- public function __construct( $text ) {
- parent::__construct( $text, "RevisionTestModifyableContent" );
- }
+ $rec->method( 'getSha1' )
+ ->willThrowException( new RevisionAccessException( 'Oops!' ) );
- public function copy() {
- return new RevisionTestModifyableContent( $this->mText );
+ $rev = new Revision( $rec, 0, $title );
+ $this->assertNull( $rev->getSha1() );
}
- public function getText() {
- return $this->mText;
- }
+ /**
+ * @covers Revision::getContent
+ */
+ public function testGetContent() {
+ $title = $this->getMockTitle();
- public function setText( $text ) {
- $this->mText = $text;
- }
-}
+ $rec = new MutableRevisionRecord( $title );
+ $rev = new Revision( $rec, 0, $title );
-class RevisionTestModifyableContentHandler extends TextContentHandler {
+ $this->assertNull( $rev->getContent(), 'Content of no slots is null' );
- public function __construct() {
- parent::__construct( "RevisionTestModifyableContent", [ CONTENT_FORMAT_TEXT ] );
+ $content = new TextContent( 'Hello Kittens!' );
+ $rec->setContent( 'main', $content );
+ $this->assertSame( $content, $rev->getContent() );
}
- public function unserializeContent( $text, $format = null ) {
- $this->checkFormat( $format );
+ /**
+ * @covers Revision::getContent
+ */
+ public function testGetContent_failure() {
+ $title = $this->getMockTitle();
+
+ $rec = $this->getMockBuilder( RevisionRecord::class )
+ ->disableOriginalConstructor()
+ ->getMock();
- return new RevisionTestModifyableContent( $text );
- }
+ $rec->method( 'getContent' )
+ ->willThrowException( new RevisionAccessException( 'Oops!' ) );
- public function makeEmptyContent() {
- return new RevisionTestModifyableContent( '' );
+ $rev = new Revision( $rec, 0, $title );
+ $this->assertNull( $rev->getContent() );
}
+
}
diff --git a/www/wiki/tests/phpunit/includes/RevisionTestModifyableContent.php b/www/wiki/tests/phpunit/includes/RevisionTestModifyableContent.php
new file mode 100644
index 00000000..6dcba53c
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/RevisionTestModifyableContent.php
@@ -0,0 +1,23 @@
+<?php
+
+class RevisionTestModifyableContent extends TextContent {
+
+ const MODEL_ID = "RevisionTestModifyableContent";
+
+ public function __construct( $text ) {
+ parent::__construct( $text, self::MODEL_ID );
+ }
+
+ public function copy() {
+ return new RevisionTestModifyableContent( $this->mText );
+ }
+
+ public function getText() {
+ return $this->mText;
+ }
+
+ public function setText( $text ) {
+ $this->mText = $text;
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/RevisionTestModifyableContentHandler.php b/www/wiki/tests/phpunit/includes/RevisionTestModifyableContentHandler.php
new file mode 100644
index 00000000..bc4e40a4
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/RevisionTestModifyableContentHandler.php
@@ -0,0 +1,19 @@
+<?php
+
+class RevisionTestModifyableContentHandler extends TextContentHandler {
+
+ public function __construct() {
+ parent::__construct( RevisionTestModifyableContent::MODEL_ID, [ CONTENT_FORMAT_TEXT ] );
+ }
+
+ public function unserializeContent( $text, $format = null ) {
+ $this->checkFormat( $format );
+
+ return new RevisionTestModifyableContent( $text );
+ }
+
+ public function makeEmptyContent() {
+ return new RevisionTestModifyableContent( '' );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/SampleTest.php b/www/wiki/tests/phpunit/includes/SampleTest.php
index 02935a53..3d74ae3e 100644
--- a/www/wiki/tests/phpunit/includes/SampleTest.php
+++ b/www/wiki/tests/phpunit/includes/SampleTest.php
@@ -1,6 +1,6 @@
<?php
-class TestSample extends MediaWikiLangTestCase {
+class SampleTest extends MediaWikiLangTestCase {
/**
* Anything that needs to happen before your tests should go here.
@@ -36,7 +36,7 @@ class TestSample extends MediaWikiLangTestCase {
*/
public function testTitleObjectStringConversion() {
$title = Title::newFromText( "text" );
- $this->assertInstanceOf( 'Title', $title, "Title creation" );
+ $this->assertInstanceOf( Title::class, $title, "Title creation" );
$this->assertEquals( "Text", $title, "Automatic string conversion" );
$title = Title::newFromText( "text", NS_MEDIA );
@@ -57,12 +57,12 @@ class TestSample extends MediaWikiLangTestCase {
];
}
- // @codingStandardsIgnoreStart Generic.Files.LineLength
/**
+ * phpcs:disable Generic.Files.LineLength
* @dataProvider provideTitles
* See https://phpunit.de/manual/3.7/en/appendixes.annotations.html#appendixes.annotations.dataProvider
+ * phpcs:enable
*/
- // @codingStandardsIgnoreEnd
public function testCreateBasicListOfTitles( $titleName, $ns, $text ) {
$title = Title::newFromText( $titleName, $ns );
$this->assertEquals( $text, "$title", "see if '$titleName' matches '$text'" );
@@ -95,12 +95,10 @@ class TestSample extends MediaWikiLangTestCase {
$this->assertTrue( $title->isLocal() );
}
- // @codingStandardsIgnoreStart Generic.Files.LineLength
/**
* @expectedException InvalidArgumentException
* See https://phpunit.de/manual/3.7/en/appendixes.annotations.html#appendixes.annotations.expectedException
*/
- // @codingStandardsIgnoreEnd
public function testTitleObjectFromObject() {
$title = Title::newFromText( Title::newFromText( "test" ) );
$this->assertEquals( "Test", $title->isLocal() );
diff --git a/www/wiki/tests/phpunit/includes/SanitizerValidateEmailTest.php b/www/wiki/tests/phpunit/includes/SanitizerValidateEmailTest.php
index 24485133..c4e43084 100644
--- a/www/wiki/tests/phpunit/includes/SanitizerValidateEmailTest.php
+++ b/www/wiki/tests/phpunit/includes/SanitizerValidateEmailTest.php
@@ -5,7 +5,9 @@
* @todo all test methods in this class should be refactored and...
* use a single test method and a single data provider...
*/
-class SanitizerValidateEmailTest extends PHPUnit_Framework_TestCase {
+class SanitizerValidateEmailTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
private function checkEmail( $addr, $expected = true, $msg = '' ) {
if ( $msg == '' ) {
diff --git a/www/wiki/tests/phpunit/includes/SiteStatsTest.php b/www/wiki/tests/phpunit/includes/SiteStatsTest.php
index cdbf9fd9..56bde5da 100644
--- a/www/wiki/tests/phpunit/includes/SiteStatsTest.php
+++ b/www/wiki/tests/phpunit/includes/SiteStatsTest.php
@@ -11,9 +11,10 @@ class SiteStatsTest extends MediaWikiTestCase {
$cache = \MediaWiki\MediaWikiServices::getInstance()->getMainWANObjectCache();
$jobq = JobQueueGroup::singleton();
- // Delete EditPage jobs that might have been left behind by other tests
+ // Delete jobs that might have been left behind by other tests
$jobq->get( 'htmlCacheUpdate' )->delete();
$jobq->get( 'recentChangesUpdate' )->delete();
+ $jobq->get( 'userGroupExpiry' )->delete();
$cache->delete( $cache->makeKey( 'SiteStats', 'jobscount' ) );
$jobq->push( new NullJob( Title::newMainPage(), [] ) );
diff --git a/www/wiki/tests/phpunit/includes/StatusTest.php b/www/wiki/tests/phpunit/includes/StatusTest.php
index 7e56ebf2..6e62afdd 100644
--- a/www/wiki/tests/phpunit/includes/StatusTest.php
+++ b/www/wiki/tests/phpunit/includes/StatusTest.php
@@ -5,11 +5,6 @@
*/
class StatusTest extends MediaWikiLangTestCase {
- public function testCanConstruct() {
- new Status();
- $this->assertTrue( true );
- }
-
/**
* @dataProvider provideValues
* @covers Status::newGood
@@ -35,7 +30,7 @@ class StatusTest extends MediaWikiLangTestCase {
* @covers Status::newFatal
*/
public function testNewFatalWithMessage() {
- $message = $this->getMockBuilder( 'Message' )
+ $message = $this->getMockBuilder( Message::class )
->disableOriginalConstructor()
->getMock();
@@ -229,7 +224,7 @@ class StatusTest extends MediaWikiLangTestCase {
}
protected function getMockMessage( $key = 'key', $params = [] ) {
- $message = $this->getMockBuilder( 'Message' )
+ $message = $this->getMockBuilder( Message::class )
->disableOriginalConstructor()
->getMock();
$message->expects( $this->atLeastOnce() )
@@ -316,7 +311,7 @@ class StatusTest extends MediaWikiLangTestCase {
* @covers Status::cleanParams
*/
public function testCleanParams( $cleanCallback, $params, $expected ) {
- $method = new ReflectionMethod( 'Status', 'cleanParams' );
+ $method = new ReflectionMethod( Status::class, 'cleanParams' );
$method->setAccessible( true );
$status = new Status();
$status->cleanCallback = $cleanCallback;
@@ -406,8 +401,8 @@ class StatusTest extends MediaWikiLangTestCase {
$status,
"* ⧼fooBar!⧽\n* ⧼fooBar2!⧽\n",
"(wrap-long: * (fooBar!)\n* (fooBar2!)\n)",
- "<ul><li> ⧼fooBar!⧽</li>\n<li> ⧼fooBar2!⧽</li></ul>\n",
- "<p>(wrap-long: * (fooBar!)\n</p>\n<ul><li> (fooBar2!)</li></ul>\n<p>)\n</p>",
+ "<ul><li>⧼fooBar!⧽</li>\n<li>⧼fooBar2!⧽</li></ul>\n",
+ "<p>(wrap-long: * (fooBar!)\n</p>\n<ul><li>(fooBar2!)</li></ul>\n<p>)\n</p>",
];
$status = new Status();
@@ -427,8 +422,8 @@ class StatusTest extends MediaWikiLangTestCase {
$status,
"* ⧼fooBar!⧽\n* ⧼fooBar2!⧽\n",
"(wrap-long: * (fooBar!: foo, bar)\n* (fooBar2!)\n)",
- "<ul><li> ⧼fooBar!⧽</li>\n<li> ⧼fooBar2!⧽</li></ul>\n",
- "<p>(wrap-long: * (fooBar!: foo, bar)\n</p>\n<ul><li> (fooBar2!)</li></ul>\n<p>)\n</p>",
+ "<ul><li>⧼fooBar!⧽</li>\n<li>⧼fooBar2!⧽</li></ul>\n",
+ "<p>(wrap-long: * (fooBar!: foo, bar)\n</p>\n<ul><li>(fooBar2!)</li></ul>\n<p>)\n</p>",
];
return $testCases;
@@ -454,23 +449,23 @@ class StatusTest extends MediaWikiLangTestCase {
Status $status, $expectedParams = [], $expectedKey, $expectedWrapper
) {
$message = $status->getMessage( null, null, 'qqx' );
- $this->assertInstanceOf( 'Message', $message );
+ $this->assertInstanceOf( Message::class, $message );
$this->assertEquals( $expectedParams, self::sanitizedMessageParams( $message ),
'Message::getParams' );
$this->assertEquals( $expectedKey, $message->getKey(), 'Message::getKey' );
$message = $status->getMessage( 'wrapper-short', 'wrapper-long' );
- $this->assertInstanceOf( 'Message', $message );
+ $this->assertInstanceOf( Message::class, $message );
$this->assertEquals( $expectedWrapper, $message->getKey(), 'Message::getKey with wrappers' );
$this->assertCount( 1, $message->getParams(), 'Message::getParams with wrappers' );
$message = $status->getMessage( 'wrapper' );
- $this->assertInstanceOf( 'Message', $message );
+ $this->assertInstanceOf( Message::class, $message );
$this->assertEquals( 'wrapper', $message->getKey(), 'Message::getKey with wrappers' );
$this->assertCount( 1, $message->getParams(), 'Message::getParams with wrappers' );
$message = $status->getMessage( false, 'wrapper' );
- $this->assertInstanceOf( 'Message', $message );
+ $this->assertInstanceOf( Message::class, $message );
$this->assertEquals( 'wrapper', $message->getKey(), 'Message::getKey with wrappers' );
$this->assertCount( 1, $message->getParams(), 'Message::getParams with wrappers' );
}
@@ -565,7 +560,7 @@ class StatusTest extends MediaWikiLangTestCase {
* @covers Status::getErrorMessage
*/
public function testGetErrorMessage() {
- $method = new ReflectionMethod( 'Status', 'getErrorMessage' );
+ $method = new ReflectionMethod( Status::class, 'getErrorMessage' );
$method->setAccessible( true );
$status = new Status();
$key = 'foo';
@@ -573,7 +568,7 @@ class StatusTest extends MediaWikiLangTestCase {
/** @var Message $message */
$message = $method->invoke( $status, array_merge( [ $key ], $params ) );
- $this->assertInstanceOf( 'Message', $message );
+ $this->assertInstanceOf( Message::class, $message );
$this->assertEquals( $key, $message->getKey() );
$this->assertEquals( $params, $message->getParams() );
}
@@ -582,7 +577,7 @@ class StatusTest extends MediaWikiLangTestCase {
* @covers Status::getErrorMessageArray
*/
public function testGetErrorMessageArray() {
- $method = new ReflectionMethod( 'Status', 'getErrorMessageArray' );
+ $method = new ReflectionMethod( Status::class, 'getErrorMessageArray' );
$method->setAccessible( true );
$status = new Status();
$key = 'foo';
@@ -600,7 +595,7 @@ class StatusTest extends MediaWikiLangTestCase {
$this->assertInternalType( 'array', $messageArray );
$this->assertCount( 2, $messageArray );
foreach ( $messageArray as $message ) {
- $this->assertInstanceOf( 'Message', $message );
+ $this->assertInstanceOf( Message::class, $message );
$this->assertEquals( $key, $message->getKey() );
$this->assertEquals( $params, $message->getParams() );
}
diff --git a/www/wiki/tests/phpunit/includes/Storage/BlobStoreFactoryTest.php b/www/wiki/tests/phpunit/includes/Storage/BlobStoreFactoryTest.php
new file mode 100644
index 00000000..252c6578
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/Storage/BlobStoreFactoryTest.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace MediaWiki\Tests\Storage;
+
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\BlobStore;
+use MediaWiki\Storage\SqlBlobStore;
+use MediaWikiTestCase;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @covers \MediaWiki\Storage\BlobStoreFactory
+ */
+class BlobStoreFactoryTest extends MediaWikiTestCase {
+
+ public function provideWikiIds() {
+ yield [ false ];
+ yield [ 'someWiki' ];
+ }
+
+ /**
+ * @dataProvider provideWikiIds
+ */
+ public function testNewBlobStore( $wikiId ) {
+ $factory = MediaWikiServices::getInstance()->getBlobStoreFactory();
+ $store = $factory->newBlobStore( $wikiId );
+ $this->assertInstanceOf( BlobStore::class, $store );
+
+ // This only works as we currently know this is a SqlBlobStore object
+ $wrapper = TestingAccessWrapper::newFromObject( $store );
+ $this->assertEquals( $wikiId, $wrapper->wikiId );
+ }
+
+ /**
+ * @dataProvider provideWikiIds
+ */
+ public function testNewSqlBlobStore( $wikiId ) {
+ $factory = MediaWikiServices::getInstance()->getBlobStoreFactory();
+ $store = $factory->newSqlBlobStore( $wikiId );
+ $this->assertInstanceOf( SqlBlobStore::class, $store );
+
+ $wrapper = TestingAccessWrapper::newFromObject( $store );
+ $this->assertEquals( $wikiId, $wrapper->wikiId );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/Storage/MutableRevisionRecordTest.php b/www/wiki/tests/phpunit/includes/Storage/MutableRevisionRecordTest.php
new file mode 100644
index 00000000..dd2c4b68
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/Storage/MutableRevisionRecordTest.php
@@ -0,0 +1,212 @@
+<?php
+
+namespace MediaWiki\Tests\Storage;
+
+use CommentStoreComment;
+use InvalidArgumentException;
+use MediaWiki\Storage\MutableRevisionRecord;
+use MediaWiki\Storage\RevisionAccessException;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\SlotRecord;
+use MediaWiki\User\UserIdentityValue;
+use MediaWikiTestCase;
+use TextContent;
+use Title;
+use WikitextContent;
+
+/**
+ * @covers \MediaWiki\Storage\MutableRevisionRecord
+ * @covers \MediaWiki\Storage\RevisionRecord
+ */
+class MutableRevisionRecordTest extends MediaWikiTestCase {
+
+ use RevisionRecordTests;
+
+ /**
+ * @param array $rowOverrides
+ *
+ * @return MutableRevisionRecord
+ */
+ protected function newRevision( array $rowOverrides = [] ) {
+ $title = Title::newFromText( 'Dummy' );
+ $title->resetArticleID( 17 );
+
+ $user = new UserIdentityValue( 11, 'Tester', 0 );
+ $comment = CommentStoreComment::newUnsavedComment( 'Hello World' );
+
+ $record = new MutableRevisionRecord( $title );
+
+ if ( isset( $rowOverrides['rev_deleted'] ) ) {
+ $record->setVisibility( $rowOverrides['rev_deleted'] );
+ }
+
+ if ( isset( $rowOverrides['rev_id'] ) ) {
+ $record->setId( $rowOverrides['rev_id'] );
+ }
+
+ if ( isset( $rowOverrides['rev_page'] ) ) {
+ $record->setPageId( $rowOverrides['rev_page'] );
+ }
+
+ $record->setContent( 'main', new TextContent( 'Lorem Ipsum' ) );
+ $record->setComment( $comment );
+ $record->setUser( $user );
+
+ return $record;
+ }
+
+ public function provideConstructor() {
+ $title = Title::newFromText( 'Dummy' );
+ $title->resetArticleID( 17 );
+
+ yield [
+ $title,
+ 'acmewiki'
+ ];
+ }
+
+ /**
+ * @dataProvider provideConstructor
+ *
+ * @param Title $title
+ * @param bool $wikiId
+ */
+ public function testConstructorAndGetters(
+ Title $title,
+ $wikiId = false
+ ) {
+ $rec = new MutableRevisionRecord( $title, $wikiId );
+
+ $this->assertSame( $title, $rec->getPageAsLinkTarget(), 'getPageAsLinkTarget' );
+ $this->assertSame( $wikiId, $rec->getWikiId(), 'getWikiId' );
+ }
+
+ public function provideConstructorFailure() {
+ $title = Title::newFromText( 'Dummy' );
+ $title->resetArticleID( 17 );
+
+ yield 'not a wiki id' => [
+ $title,
+ null
+ ];
+ }
+
+ /**
+ * @dataProvider provideConstructorFailure
+ *
+ * @param Title $title
+ * @param bool $wikiId
+ */
+ public function testConstructorFailure(
+ Title $title,
+ $wikiId = false
+ ) {
+ $this->setExpectedException( InvalidArgumentException::class );
+ new MutableRevisionRecord( $title, $wikiId );
+ }
+
+ public function testSetGetId() {
+ $record = new MutableRevisionRecord( Title::newFromText( 'Foo' ) );
+ $this->assertNull( $record->getId() );
+ $record->setId( 888 );
+ $this->assertSame( 888, $record->getId() );
+ }
+
+ public function testSetGetUser() {
+ $record = new MutableRevisionRecord( Title::newFromText( 'Foo' ) );
+ $user = $this->getTestSysop()->getUser();
+ $this->assertNull( $record->getUser() );
+ $record->setUser( $user );
+ $this->assertSame( $user, $record->getUser() );
+ }
+
+ public function testSetGetPageId() {
+ $record = new MutableRevisionRecord( Title::newFromText( 'Foo' ) );
+ $this->assertSame( 0, $record->getPageId() );
+ $record->setPageId( 999 );
+ $this->assertSame( 999, $record->getPageId() );
+ }
+
+ public function testSetGetParentId() {
+ $record = new MutableRevisionRecord( Title::newFromText( 'Foo' ) );
+ $this->assertNull( $record->getParentId() );
+ $record->setParentId( 100 );
+ $this->assertSame( 100, $record->getParentId() );
+ }
+
+ public function testGetMainContentWhenEmpty() {
+ $record = new MutableRevisionRecord( Title::newFromText( 'Foo' ) );
+ $this->setExpectedException( RevisionAccessException::class );
+ $this->assertNull( $record->getContent( 'main' ) );
+ }
+
+ public function testSetGetMainContent() {
+ $record = new MutableRevisionRecord( Title::newFromText( 'Foo' ) );
+ $content = new WikitextContent( 'Badger' );
+ $record->setContent( 'main', $content );
+ $this->assertSame( $content, $record->getContent( 'main' ) );
+ }
+
+ public function testGetSlotWhenEmpty() {
+ $record = new MutableRevisionRecord( Title::newFromText( 'Foo' ) );
+ $this->assertFalse( $record->hasSlot( 'main' ) );
+
+ $this->setExpectedException( RevisionAccessException::class );
+ $record->getSlot( 'main' );
+ }
+
+ public function testSetGetSlot() {
+ $record = new MutableRevisionRecord( Title::newFromText( 'Foo' ) );
+ $slot = SlotRecord::newUnsaved(
+ 'main',
+ new WikitextContent( 'x' )
+ );
+ $record->setSlot( $slot );
+ $this->assertTrue( $record->hasSlot( 'main' ) );
+ $this->assertSame( $slot, $record->getSlot( 'main' ) );
+ }
+
+ public function testSetGetMinor() {
+ $record = new MutableRevisionRecord( Title::newFromText( 'Foo' ) );
+ $this->assertFalse( $record->isMinor() );
+ $record->setMinorEdit( true );
+ $this->assertSame( true, $record->isMinor() );
+ }
+
+ public function testSetGetTimestamp() {
+ $record = new MutableRevisionRecord( Title::newFromText( 'Foo' ) );
+ $this->assertNull( $record->getTimestamp() );
+ $record->setTimestamp( '20180101010101' );
+ $this->assertSame( '20180101010101', $record->getTimestamp() );
+ }
+
+ public function testSetGetVisibility() {
+ $record = new MutableRevisionRecord( Title::newFromText( 'Foo' ) );
+ $this->assertSame( 0, $record->getVisibility() );
+ $record->setVisibility( RevisionRecord::DELETED_USER );
+ $this->assertSame( RevisionRecord::DELETED_USER, $record->getVisibility() );
+ }
+
+ public function testSetGetSha1() {
+ $record = new MutableRevisionRecord( Title::newFromText( 'Foo' ) );
+ $this->assertSame( 'phoiac9h4m842xq45sp7s6u21eteeq1', $record->getSha1() );
+ $record->setSha1( 'someHash' );
+ $this->assertSame( 'someHash', $record->getSha1() );
+ }
+
+ public function testSetGetSize() {
+ $record = new MutableRevisionRecord( Title::newFromText( 'Foo' ) );
+ $this->assertSame( 0, $record->getSize() );
+ $record->setSize( 775 );
+ $this->assertSame( 775, $record->getSize() );
+ }
+
+ public function testSetGetComment() {
+ $record = new MutableRevisionRecord( Title::newFromText( 'Foo' ) );
+ $comment = new CommentStoreComment( 1, 'foo' );
+ $this->assertNull( $record->getComment() );
+ $record->setComment( $comment );
+ $this->assertSame( $comment, $record->getComment() );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/Storage/MutableRevisionSlotsTest.php b/www/wiki/tests/phpunit/includes/Storage/MutableRevisionSlotsTest.php
new file mode 100644
index 00000000..0416bcfa
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/Storage/MutableRevisionSlotsTest.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace MediaWiki\Tests\Storage;
+
+use MediaWiki\Storage\MutableRevisionSlots;
+use MediaWiki\Storage\RevisionAccessException;
+use MediaWiki\Storage\SlotRecord;
+use WikitextContent;
+
+/**
+ * @covers \MediaWiki\Storage\MutableRevisionSlots
+ */
+class MutableRevisionSlotsTest extends RevisionSlotsTest {
+
+ public function testSetMultipleSlots() {
+ $slots = new MutableRevisionSlots();
+
+ $this->assertSame( [], $slots->getSlots() );
+
+ $slotA = SlotRecord::newUnsaved( 'some', new WikitextContent( 'A' ) );
+ $slots->setSlot( $slotA );
+ $this->assertTrue( $slots->hasSlot( 'some' ) );
+ $this->assertSame( $slotA, $slots->getSlot( 'some' ) );
+ $this->assertSame( [ 'some' => $slotA ], $slots->getSlots() );
+
+ $slotB = SlotRecord::newUnsaved( 'other', new WikitextContent( 'B' ) );
+ $slots->setSlot( $slotB );
+ $this->assertTrue( $slots->hasSlot( 'other' ) );
+ $this->assertSame( $slotB, $slots->getSlot( 'other' ) );
+ $this->assertSame( [ 'some' => $slotA, 'other' => $slotB ], $slots->getSlots() );
+ }
+
+ public function testSetExistingSlotOverwritesSlot() {
+ $slots = new MutableRevisionSlots();
+
+ $this->assertSame( [], $slots->getSlots() );
+
+ $slotA = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ $slots->setSlot( $slotA );
+ $this->assertSame( $slotA, $slots->getSlot( 'main' ) );
+ $this->assertSame( [ 'main' => $slotA ], $slots->getSlots() );
+
+ $slotB = SlotRecord::newUnsaved( 'main', new WikitextContent( 'B' ) );
+ $slots->setSlot( $slotB );
+ $this->assertSame( $slotB, $slots->getSlot( 'main' ) );
+ $this->assertSame( [ 'main' => $slotB ], $slots->getSlots() );
+ }
+
+ public function testSetContentOfExistingSlotOverwritesContent() {
+ $slots = new MutableRevisionSlots();
+
+ $this->assertSame( [], $slots->getSlots() );
+
+ $slotA = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ $slots->setSlot( $slotA );
+ $this->assertSame( $slotA, $slots->getSlot( 'main' ) );
+ $this->assertSame( [ 'main' => $slotA ], $slots->getSlots() );
+
+ $newContent = new WikitextContent( 'B' );
+ $slots->setContent( 'main', $newContent );
+ $this->assertSame( $newContent, $slots->getContent( 'main' ) );
+ }
+
+ public function testRemoveExistingSlot() {
+ $slotA = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ $slots = new MutableRevisionSlots( [ $slotA ] );
+
+ $this->assertSame( [ 'main' => $slotA ], $slots->getSlots() );
+
+ $slots->removeSlot( 'main' );
+ $this->assertSame( [], $slots->getSlots() );
+ $this->setExpectedException( RevisionAccessException::class );
+ $slots->getSlot( 'main' );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/Storage/NameTableStoreTest.php b/www/wiki/tests/phpunit/includes/Storage/NameTableStoreTest.php
new file mode 100644
index 00000000..0cd164b7
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/Storage/NameTableStoreTest.php
@@ -0,0 +1,298 @@
+<?php
+
+namespace MediaWiki\Tests\Storage;
+
+use BagOStuff;
+use EmptyBagOStuff;
+use HashBagOStuff;
+use MediaWiki\Storage\NameTableAccessException;
+use MediaWiki\Storage\NameTableStore;
+use MediaWikiTestCase;
+use Psr\Log\NullLogger;
+use WANObjectCache;
+use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @author Addshore
+ * @group Database
+ * @covers \MediaWiki\Storage\NameTableStore
+ */
+class NameTableStoreTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ $this->tablesUsed[] = 'slot_roles';
+ parent::setUp();
+ }
+
+ private function populateTable( $values ) {
+ $insertValues = [];
+ foreach ( $values as $name ) {
+ $insertValues[] = [ 'role_name' => $name ];
+ }
+ $this->db->insert( 'slot_roles', $insertValues );
+ }
+
+ private function getHashWANObjectCache( $cacheBag ) {
+ return new WANObjectCache( [ 'cache' => $cacheBag ] );
+ }
+
+ /**
+ * @param $db
+ * @return \PHPUnit_Framework_MockObject_MockObject|LoadBalancer
+ */
+ private function getMockLoadBalancer( $db ) {
+ $mock = $this->getMockBuilder( LoadBalancer::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mock->expects( $this->any() )
+ ->method( 'getConnection' )
+ ->willReturn( $db );
+ return $mock;
+ }
+
+ private function getCallCheckingDb( $insertCalls, $selectCalls ) {
+ $mock = $this->getMockBuilder( Database::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mock->expects( $this->exactly( $insertCalls ) )
+ ->method( 'insert' )
+ ->willReturnCallback( function () {
+ return call_user_func_array( [ $this->db, 'insert' ], func_get_args() );
+ } );
+ $mock->expects( $this->exactly( $selectCalls ) )
+ ->method( 'select' )
+ ->willReturnCallback( function () {
+ return call_user_func_array( [ $this->db, 'select' ], func_get_args() );
+ } );
+ $mock->expects( $this->exactly( $insertCalls ) )
+ ->method( 'affectedRows' )
+ ->willReturnCallback( function () {
+ return call_user_func_array( [ $this->db, 'affectedRows' ], func_get_args() );
+ } );
+ $mock->expects( $this->any() )
+ ->method( 'insertId' )
+ ->willReturnCallback( function () {
+ return call_user_func_array( [ $this->db, 'insertId' ], func_get_args() );
+ } );
+ return $mock;
+ }
+
+ private function getNameTableSqlStore(
+ BagOStuff $cacheBag,
+ $insertCalls,
+ $selectCalls,
+ $normalizationCallback = null
+ ) {
+ return new NameTableStore(
+ $this->getMockLoadBalancer( $this->getCallCheckingDb( $insertCalls, $selectCalls ) ),
+ $this->getHashWANObjectCache( $cacheBag ),
+ new NullLogger(),
+ 'slot_roles', 'role_id', 'role_name',
+ $normalizationCallback
+ );
+ }
+
+ public function provideGetAndAcquireId() {
+ return [
+ 'no wancache, empty table' =>
+ [ new EmptyBagOStuff(), true, 1, [], 'foo', 1 ],
+ 'no wancache, one matching value' =>
+ [ new EmptyBagOStuff(), false, 1, [ 'foo' ], 'foo', 1 ],
+ 'no wancache, one not matching value' =>
+ [ new EmptyBagOStuff(), true, 1, [ 'bar' ], 'foo', 2 ],
+ 'no wancache, multiple, one matching value' =>
+ [ new EmptyBagOStuff(), false, 1, [ 'foo', 'bar' ], 'bar', 2 ],
+ 'no wancache, multiple, no matching value' =>
+ [ new EmptyBagOStuff(), true, 1, [ 'foo', 'bar' ], 'baz', 3 ],
+ 'wancache, empty table' =>
+ [ new HashBagOStuff(), true, 1, [], 'foo', 1 ],
+ 'wancache, one matching value' =>
+ [ new HashBagOStuff(), false, 1, [ 'foo' ], 'foo', 1 ],
+ 'wancache, one not matching value' =>
+ [ new HashBagOStuff(), true, 1, [ 'bar' ], 'foo', 2 ],
+ 'wancache, multiple, one matching value' =>
+ [ new HashBagOStuff(), false, 1, [ 'foo', 'bar' ], 'bar', 2 ],
+ 'wancache, multiple, no matching value' =>
+ [ new HashBagOStuff(), true, 1, [ 'foo', 'bar' ], 'baz', 3 ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetAndAcquireId
+ * @param BagOStuff $cacheBag to use in the WANObjectCache service
+ * @param bool $needsInsert Does the value we are testing need to be inserted?
+ * @param int $selectCalls Number of times the select DB method will be called
+ * @param string[] $existingValues to be added to the db table
+ * @param string $name name to acquire
+ * @param int $expectedId the id we expect the name to have
+ */
+ public function testGetAndAcquireId(
+ $cacheBag,
+ $needsInsert,
+ $selectCalls,
+ $existingValues,
+ $name,
+ $expectedId
+ ) {
+ $this->populateTable( $existingValues );
+ $store = $this->getNameTableSqlStore( $cacheBag, (int)$needsInsert, $selectCalls );
+
+ // Some names will not initially exist
+ try {
+ $result = $store->getId( $name );
+ $this->assertSame( $expectedId, $result );
+ } catch ( NameTableAccessException $e ) {
+ if ( $needsInsert ) {
+ $this->assertTrue( true ); // Expected exception
+ } else {
+ $this->fail( 'Did not expect an exception, but got one: ' . $e->getMessage() );
+ }
+ }
+
+ // All names should return their id here
+ $this->assertSame( $expectedId, $store->acquireId( $name ) );
+
+ // acquireId inserted these names, so now everything should exist with getId
+ $this->assertSame( $expectedId, $store->getId( $name ) );
+
+ // calling getId again will also still work, and not result in more selects
+ $this->assertSame( $expectedId, $store->getId( $name ) );
+ }
+
+ public function provideTestGetAndAcquireIdNameNormalization() {
+ yield [ 'A', 'a', 'strtolower' ];
+ yield [ 'b', 'B', 'strtoupper' ];
+ yield [
+ 'X',
+ 'X',
+ function ( $name ) {
+ return $name;
+ }
+ ];
+ yield [ 'ZZ', 'ZZ-a', __CLASS__ . '::appendDashAToString' ];
+ }
+
+ public static function appendDashAToString( $string ) {
+ return $string . '-a';
+ }
+
+ /**
+ * @dataProvider provideTestGetAndAcquireIdNameNormalization
+ */
+ public function testGetAndAcquireIdNameNormalization(
+ $nameIn,
+ $nameOut,
+ $normalizationCallback
+ ) {
+ $store = $this->getNameTableSqlStore(
+ new EmptyBagOStuff(),
+ 1,
+ 1,
+ $normalizationCallback
+ );
+ $acquiredId = $store->acquireId( $nameIn );
+ $this->assertSame( $nameOut, $store->getName( $acquiredId ) );
+ }
+
+ public function provideGetName() {
+ return [
+ [ new HashBagOStuff(), 3, 3 ],
+ [ new EmptyBagOStuff(), 3, 3 ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetName
+ */
+ public function testGetName( $cacheBag, $insertCalls, $selectCalls ) {
+ $store = $this->getNameTableSqlStore( $cacheBag, $insertCalls, $selectCalls );
+
+ // Get 1 ID and make sure getName returns correctly
+ $fooId = $store->acquireId( 'foo' );
+ $this->assertSame( 'foo', $store->getName( $fooId ) );
+
+ // Get another ID and make sure getName returns correctly
+ $barId = $store->acquireId( 'bar' );
+ $this->assertSame( 'bar', $store->getName( $barId ) );
+
+ // Blitz the cache and make sure it still returns
+ TestingAccessWrapper::newFromObject( $store )->tableCache = null;
+ $this->assertSame( 'foo', $store->getName( $fooId ) );
+ $this->assertSame( 'bar', $store->getName( $barId ) );
+
+ // Blitz the cache again and get another ID and make sure getName returns correctly
+ TestingAccessWrapper::newFromObject( $store )->tableCache = null;
+ $bazId = $store->acquireId( 'baz' );
+ $this->assertSame( 'baz', $store->getName( $bazId ) );
+ $this->assertSame( 'baz', $store->getName( $bazId ) );
+ }
+
+ public function testGetName_masterFallback() {
+ $store = $this->getNameTableSqlStore( new EmptyBagOStuff(), 1, 2 );
+
+ // Insert a new name
+ $fooId = $store->acquireId( 'foo' );
+
+ // Empty the process cache, getCachedTable() will now return this empty array
+ TestingAccessWrapper::newFromObject( $store )->tableCache = [];
+
+ // getName should fallback to master, which is why we assert 2 selectCalls above
+ $this->assertSame( 'foo', $store->getName( $fooId ) );
+ }
+
+ public function testGetMap_empty() {
+ $this->populateTable( [] );
+ $store = $this->getNameTableSqlStore( new HashBagOStuff(), 0, 1 );
+ $table = $store->getMap();
+ $this->assertSame( [], $table );
+ }
+
+ public function testGetMap_twoValues() {
+ $this->populateTable( [ 'foo', 'bar' ] );
+ $store = $this->getNameTableSqlStore( new HashBagOStuff(), 0, 1 );
+
+ // We are using a cache, so 2 calls should only result in 1 select on the db
+ $store->getMap();
+ $table = $store->getMap();
+
+ $expected = [ 1 => 'foo', 2 => 'bar' ];
+ $this->assertSame( $expected, $table );
+ // Make sure the table returned is the same as the cached table
+ $this->assertSame( $expected, TestingAccessWrapper::newFromObject( $store )->tableCache );
+ }
+
+ public function testCacheRaceCondition() {
+ $wanHashBag = new HashBagOStuff();
+ $store1 = $this->getNameTableSqlStore( $wanHashBag, 1, 1 );
+ $store2 = $this->getNameTableSqlStore( $wanHashBag, 1, 0 );
+ $store3 = $this->getNameTableSqlStore( $wanHashBag, 1, 1 );
+
+ // Cache the current table in the instances we will use
+ // This simulates multiple requests running simultaneously
+ $store1->getMap();
+ $store2->getMap();
+ $store3->getMap();
+
+ // Store 2 separate names using different instances
+ $fooId = $store1->acquireId( 'foo' );
+ $barId = $store2->acquireId( 'bar' );
+
+ // Each of these instances should be aware of what they have inserted
+ $this->assertSame( $fooId, $store1->acquireId( 'foo' ) );
+ $this->assertSame( $barId, $store2->acquireId( 'bar' ) );
+
+ // A new store should be able to get both of these new Ids
+ // Note: before there was a race condition here where acquireId( 'bar' ) would update the
+ // cache with data missing the 'foo' key that it was not aware of
+ $store4 = $this->getNameTableSqlStore( $wanHashBag, 0, 1 );
+ $this->assertSame( $fooId, $store4->getId( 'foo' ) );
+ $this->assertSame( $barId, $store4->getId( 'bar' ) );
+
+ // If a store with old cached data tries to acquire these we will get the same ids.
+ $this->assertSame( $fooId, $store3->acquireId( 'foo' ) );
+ $this->assertSame( $barId, $store3->acquireId( 'bar' ) );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/Storage/RevisionArchiveRecordTest.php b/www/wiki/tests/phpunit/includes/Storage/RevisionArchiveRecordTest.php
new file mode 100644
index 00000000..f959d680
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/Storage/RevisionArchiveRecordTest.php
@@ -0,0 +1,272 @@
+<?php
+
+namespace MediaWiki\Tests\Storage;
+
+use CommentStoreComment;
+use InvalidArgumentException;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\RevisionSlots;
+use MediaWiki\Storage\RevisionArchiveRecord;
+use MediaWiki\Storage\SlotRecord;
+use MediaWiki\User\UserIdentity;
+use MediaWiki\User\UserIdentityValue;
+use MediaWikiTestCase;
+use TextContent;
+use Title;
+
+/**
+ * @covers \MediaWiki\Storage\RevisionArchiveRecord
+ * @covers \MediaWiki\Storage\RevisionRecord
+ */
+class RevisionArchiveRecordTest extends MediaWikiTestCase {
+
+ use RevisionRecordTests;
+
+ /**
+ * @param array $rowOverrides
+ *
+ * @return RevisionArchiveRecord
+ */
+ protected function newRevision( array $rowOverrides = [] ) {
+ $title = Title::newFromText( 'Dummy' );
+ $title->resetArticleID( 17 );
+
+ $user = new UserIdentityValue( 11, 'Tester', 0 );
+ $comment = CommentStoreComment::newUnsavedComment( 'Hello World' );
+
+ $main = SlotRecord::newUnsaved( 'main', new TextContent( 'Lorem Ipsum' ) );
+ $aux = SlotRecord::newUnsaved( 'aux', new TextContent( 'Frumious Bandersnatch' ) );
+ $slots = new RevisionSlots( [ $main, $aux ] );
+
+ $row = [
+ 'ar_id' => '5',
+ 'ar_rev_id' => '7',
+ 'ar_page_id' => strval( $title->getArticleID() ),
+ 'ar_timestamp' => '20200101000000',
+ 'ar_deleted' => 0,
+ 'ar_minor_edit' => 0,
+ 'ar_parent_id' => '5',
+ 'ar_len' => $slots->computeSize(),
+ 'ar_sha1' => $slots->computeSha1(),
+ ];
+
+ foreach ( $rowOverrides as $field => $value ) {
+ $field = preg_replace( '/^rev_/', 'ar_', $field );
+ $row[$field] = $value;
+ }
+
+ return new RevisionArchiveRecord( $title, $user, $comment, (object)$row, $slots );
+ }
+
+ public function provideConstructor() {
+ $title = Title::newFromText( 'Dummy' );
+ $title->resetArticleID( 17 );
+
+ $user = new UserIdentityValue( 11, 'Tester', 0 );
+ $comment = CommentStoreComment::newUnsavedComment( 'Hello World' );
+
+ $main = SlotRecord::newUnsaved( 'main', new TextContent( 'Lorem Ipsum' ) );
+ $aux = SlotRecord::newUnsaved( 'aux', new TextContent( 'Frumious Bandersnatch' ) );
+ $slots = new RevisionSlots( [ $main, $aux ] );
+
+ $protoRow = [
+ 'ar_id' => '5',
+ 'ar_rev_id' => '7',
+ 'ar_page_id' => strval( $title->getArticleID() ),
+ 'ar_timestamp' => '20200101000000',
+ 'ar_deleted' => 0,
+ 'ar_minor_edit' => 0,
+ 'ar_parent_id' => '5',
+ 'ar_len' => $slots->computeSize(),
+ 'ar_sha1' => $slots->computeSha1(),
+ ];
+
+ $row = $protoRow;
+ yield 'all info' => [
+ $title,
+ $user,
+ $comment,
+ (object)$row,
+ $slots,
+ 'acmewiki'
+ ];
+
+ $row = $protoRow;
+ $row['ar_minor_edit'] = '1';
+ $row['ar_deleted'] = strval( RevisionRecord::DELETED_USER );
+
+ yield 'minor deleted' => [
+ $title,
+ $user,
+ $comment,
+ (object)$row,
+ $slots
+ ];
+
+ $row = $protoRow;
+ unset( $row['ar_parent'] );
+
+ yield 'no parent' => [
+ $title,
+ $user,
+ $comment,
+ (object)$row,
+ $slots
+ ];
+
+ $row = $protoRow;
+ $row['ar_len'] = null;
+ $row['ar_sha1'] = '';
+
+ yield 'ar_len is null, ar_sha1 is ""' => [
+ $title,
+ $user,
+ $comment,
+ (object)$row,
+ $slots
+ ];
+
+ $row = $protoRow;
+ yield 'no length, no hash' => [
+ Title::newFromText( 'DummyDoesNotExist' ),
+ $user,
+ $comment,
+ (object)$row,
+ $slots
+ ];
+ }
+
+ /**
+ * @dataProvider provideConstructor
+ *
+ * @param Title $title
+ * @param UserIdentity $user
+ * @param CommentStoreComment $comment
+ * @param object $row
+ * @param RevisionSlots $slots
+ * @param bool $wikiId
+ */
+ public function testConstructorAndGetters(
+ Title $title,
+ UserIdentity $user,
+ CommentStoreComment $comment,
+ $row,
+ RevisionSlots $slots,
+ $wikiId = false
+ ) {
+ $rec = new RevisionArchiveRecord( $title, $user, $comment, $row, $slots, $wikiId );
+
+ $this->assertSame( $title, $rec->getPageAsLinkTarget(), 'getPageAsLinkTarget' );
+ $this->assertSame( $user, $rec->getUser( RevisionRecord::RAW ), 'getUser' );
+ $this->assertSame( $comment, $rec->getComment(), 'getComment' );
+
+ $this->assertSame( $slots->getSlotRoles(), $rec->getSlotRoles(), 'getSlotRoles' );
+ $this->assertSame( $wikiId, $rec->getWikiId(), 'getWikiId' );
+
+ $this->assertSame( (int)$row->ar_id, $rec->getArchiveId(), 'getArchiveId' );
+ $this->assertSame( (int)$row->ar_rev_id, $rec->getId(), 'getId' );
+ $this->assertSame( (int)$row->ar_page_id, $rec->getPageId(), 'getId' );
+ $this->assertSame( $row->ar_timestamp, $rec->getTimestamp(), 'getTimestamp' );
+ $this->assertSame( (int)$row->ar_deleted, $rec->getVisibility(), 'getVisibility' );
+ $this->assertSame( (bool)$row->ar_minor_edit, $rec->isMinor(), 'getIsMinor' );
+
+ if ( isset( $row->ar_parent_id ) ) {
+ $this->assertSame( (int)$row->ar_parent_id, $rec->getParentId(), 'getParentId' );
+ } else {
+ $this->assertSame( 0, $rec->getParentId(), 'getParentId' );
+ }
+
+ if ( isset( $row->ar_len ) ) {
+ $this->assertSame( (int)$row->ar_len, $rec->getSize(), 'getSize' );
+ } else {
+ $this->assertSame( $slots->computeSize(), $rec->getSize(), 'getSize' );
+ }
+
+ if ( !empty( $row->ar_sha1 ) ) {
+ $this->assertSame( $row->ar_sha1, $rec->getSha1(), 'getSha1' );
+ } else {
+ $this->assertSame( $slots->computeSha1(), $rec->getSha1(), 'getSha1' );
+ }
+ }
+
+ public function provideConstructorFailure() {
+ $title = Title::newFromText( 'Dummy' );
+ $title->resetArticleID( 17 );
+
+ $user = new UserIdentityValue( 11, 'Tester', 0 );
+
+ $comment = CommentStoreComment::newUnsavedComment( 'Hello World' );
+
+ $main = SlotRecord::newUnsaved( 'main', new TextContent( 'Lorem Ipsum' ) );
+ $aux = SlotRecord::newUnsaved( 'aux', new TextContent( 'Frumious Bandersnatch' ) );
+ $slots = new RevisionSlots( [ $main, $aux ] );
+
+ $protoRow = [
+ 'ar_id' => '5',
+ 'ar_rev_id' => '7',
+ 'ar_page_id' => strval( $title->getArticleID() ),
+ 'ar_timestamp' => '20200101000000',
+ 'ar_deleted' => 0,
+ 'ar_minor_edit' => 0,
+ 'ar_parent_id' => '5',
+ 'ar_len' => $slots->computeSize(),
+ 'ar_sha1' => $slots->computeSha1(),
+ ];
+
+ yield 'not a row' => [
+ $title,
+ $user,
+ $comment,
+ 'not a row',
+ $slots,
+ 'acmewiki'
+ ];
+
+ $row = $protoRow;
+ $row['ar_timestamp'] = 'kittens';
+
+ yield 'bad timestamp' => [
+ $title,
+ $user,
+ $comment,
+ (object)$row,
+ $slots
+ ];
+
+ $row = $protoRow;
+
+ yield 'bad wiki' => [
+ $title,
+ $user,
+ $comment,
+ (object)$row,
+ $slots,
+ 12345
+ ];
+
+ // NOTE: $title->getArticleID does *not* have to match ar_page_id in all cases!
+ }
+
+ /**
+ * @dataProvider provideConstructorFailure
+ *
+ * @param Title $title
+ * @param UserIdentity $user
+ * @param CommentStoreComment $comment
+ * @param object $row
+ * @param RevisionSlots $slots
+ * @param bool $wikiId
+ */
+ public function testConstructorFailure(
+ Title $title,
+ UserIdentity $user,
+ CommentStoreComment $comment,
+ $row,
+ RevisionSlots $slots,
+ $wikiId = false
+ ) {
+ $this->setExpectedException( InvalidArgumentException::class );
+ new RevisionArchiveRecord( $title, $user, $comment, $row, $slots, $wikiId );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/Storage/RevisionRecordTests.php b/www/wiki/tests/phpunit/includes/Storage/RevisionRecordTests.php
new file mode 100644
index 00000000..607f7829
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/Storage/RevisionRecordTests.php
@@ -0,0 +1,512 @@
+<?php
+
+namespace MediaWiki\Tests\Storage;
+
+use CommentStoreComment;
+use LogicException;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\RevisionSlots;
+use MediaWiki\Storage\RevisionStoreRecord;
+use MediaWiki\Storage\SlotRecord;
+use MediaWiki\Storage\SuppressedDataException;
+use MediaWiki\User\UserIdentityValue;
+use TextContent;
+use Title;
+
+// PHPCS should not complain about @covers and @dataProvider being used in traits, see T192384
+// phpcs:disable MediaWiki.Commenting.PhpunitAnnotations.NotTestClass
+
+/**
+ * @covers \MediaWiki\Storage\RevisionRecord
+ *
+ * @note Expects to be used in classes that extend MediaWikiTestCase.
+ */
+trait RevisionRecordTests {
+
+ /**
+ * @param array $rowOverrides
+ *
+ * @return RevisionRecord
+ */
+ protected abstract function newRevision( array $rowOverrides = [] );
+
+ private function provideAudienceCheckData( $field ) {
+ yield 'field accessible for oversighter (ALL)' => [
+ RevisionRecord::SUPPRESSED_ALL,
+ [ 'oversight' ],
+ true,
+ false
+ ];
+
+ yield 'field accessible for oversighter' => [
+ RevisionRecord::DELETED_RESTRICTED | $field,
+ [ 'oversight' ],
+ true,
+ false
+ ];
+
+ yield 'field not accessible for sysops (ALL)' => [
+ RevisionRecord::SUPPRESSED_ALL,
+ [ 'sysop' ],
+ false,
+ false
+ ];
+
+ yield 'field not accessible for sysops' => [
+ RevisionRecord::DELETED_RESTRICTED | $field,
+ [ 'sysop' ],
+ false,
+ false
+ ];
+
+ yield 'field accessible for sysops' => [
+ $field,
+ [ 'sysop' ],
+ true,
+ false
+ ];
+
+ yield 'field suppressed for logged in users' => [
+ $field,
+ [ 'user' ],
+ false,
+ false
+ ];
+
+ yield 'unrelated field suppressed' => [
+ $field === RevisionRecord::DELETED_COMMENT
+ ? RevisionRecord::DELETED_USER
+ : RevisionRecord::DELETED_COMMENT,
+ [ 'user' ],
+ true,
+ true
+ ];
+
+ yield 'nothing suppressed' => [
+ 0,
+ [ 'user' ],
+ true,
+ true
+ ];
+ }
+
+ public function testSerialization_fails() {
+ $this->setExpectedException( LogicException::class );
+ $rev = $this->newRevision();
+ serialize( $rev );
+ }
+
+ public function provideGetComment_audience() {
+ return $this->provideAudienceCheckData( RevisionRecord::DELETED_COMMENT );
+ }
+
+ private function forceStandardPermissions() {
+ $this->setMwGlobals(
+ 'wgGroupPermissions',
+ [
+ 'user' => [
+ 'viewsuppressed' => false,
+ 'suppressrevision' => false,
+ 'deletedtext' => false,
+ 'deletedhistory' => false,
+ ],
+ 'sysop' => [
+ 'viewsuppressed' => false,
+ 'suppressrevision' => false,
+ 'deletedtext' => true,
+ 'deletedhistory' => true,
+ ],
+ 'oversight' => [
+ 'deletedtext' => true,
+ 'deletedhistory' => true,
+ 'viewsuppressed' => true,
+ 'suppressrevision' => true,
+ ],
+ ]
+ );
+ }
+
+ /**
+ * @dataProvider provideGetComment_audience
+ */
+ public function testGetComment_audience( $visibility, $groups, $userCan, $publicCan ) {
+ $this->forceStandardPermissions();
+
+ $user = $this->getTestUser( $groups )->getUser();
+ $rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
+
+ $this->assertNotNull( $rev->getComment( RevisionRecord::RAW ), 'raw can' );
+
+ $this->assertSame(
+ $publicCan,
+ $rev->getComment( RevisionRecord::FOR_PUBLIC ) !== null,
+ 'public can'
+ );
+ $this->assertSame(
+ $userCan,
+ $rev->getComment( RevisionRecord::FOR_THIS_USER, $user ) !== null,
+ 'user can'
+ );
+ }
+
+ public function provideGetUser_audience() {
+ return $this->provideAudienceCheckData( RevisionRecord::DELETED_USER );
+ }
+
+ /**
+ * @dataProvider provideGetUser_audience
+ */
+ public function testGetUser_audience( $visibility, $groups, $userCan, $publicCan ) {
+ $this->forceStandardPermissions();
+
+ $user = $this->getTestUser( $groups )->getUser();
+ $rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
+
+ $this->assertNotNull( $rev->getUser( RevisionRecord::RAW ), 'raw can' );
+
+ $this->assertSame(
+ $publicCan,
+ $rev->getUser( RevisionRecord::FOR_PUBLIC ) !== null,
+ 'public can'
+ );
+ $this->assertSame(
+ $userCan,
+ $rev->getUser( RevisionRecord::FOR_THIS_USER, $user ) !== null,
+ 'user can'
+ );
+ }
+
+ public function provideGetSlot_audience() {
+ return $this->provideAudienceCheckData( RevisionRecord::DELETED_TEXT );
+ }
+
+ /**
+ * @dataProvider provideGetSlot_audience
+ */
+ public function testGetSlot_audience( $visibility, $groups, $userCan, $publicCan ) {
+ $this->forceStandardPermissions();
+
+ $user = $this->getTestUser( $groups )->getUser();
+ $rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
+
+ // NOTE: slot meta-data is never suppressed, just the content is!
+ $this->assertTrue( $rev->hasSlot( 'main' ), 'hasSlot is never suppressed' );
+ $this->assertNotNull( $rev->getSlot( 'main', RevisionRecord::RAW ), 'raw meta' );
+ $this->assertNotNull( $rev->getSlot( 'main', RevisionRecord::FOR_PUBLIC ), 'public meta' );
+
+ $this->assertNotNull(
+ $rev->getSlot( 'main', RevisionRecord::FOR_THIS_USER, $user ),
+ 'user can'
+ );
+
+ try {
+ $rev->getSlot( 'main', RevisionRecord::FOR_PUBLIC )->getContent();
+ $exception = null;
+ } catch ( SuppressedDataException $ex ) {
+ $exception = $ex;
+ }
+
+ $this->assertSame(
+ $publicCan,
+ $exception === null,
+ 'public can'
+ );
+
+ try {
+ $rev->getSlot( 'main', RevisionRecord::FOR_THIS_USER, $user )->getContent();
+ $exception = null;
+ } catch ( SuppressedDataException $ex ) {
+ $exception = $ex;
+ }
+
+ $this->assertSame(
+ $userCan,
+ $exception === null,
+ 'user can'
+ );
+ }
+
+ /**
+ * @dataProvider provideGetSlot_audience
+ */
+ public function testGetContent_audience( $visibility, $groups, $userCan, $publicCan ) {
+ $this->forceStandardPermissions();
+
+ $user = $this->getTestUser( $groups )->getUser();
+ $rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
+
+ $this->assertNotNull( $rev->getContent( 'main', RevisionRecord::RAW ), 'raw can' );
+
+ $this->assertSame(
+ $publicCan,
+ $rev->getContent( 'main', RevisionRecord::FOR_PUBLIC ) !== null,
+ 'public can'
+ );
+ $this->assertSame(
+ $userCan,
+ $rev->getContent( 'main', RevisionRecord::FOR_THIS_USER, $user ) !== null,
+ 'user can'
+ );
+ }
+
+ public function testGetSlot() {
+ $rev = $this->newRevision();
+
+ $slot = $rev->getSlot( 'main' );
+ $this->assertNotNull( $slot, 'getSlot()' );
+ $this->assertSame( 'main', $slot->getRole(), 'getRole()' );
+ }
+
+ public function testHasSlot() {
+ $rev = $this->newRevision();
+
+ $this->assertTrue( $rev->hasSlot( 'main' ) );
+ $this->assertFalse( $rev->hasSlot( 'xyz' ) );
+ }
+
+ public function testGetContent() {
+ $rev = $this->newRevision();
+
+ $content = $rev->getSlot( 'main' );
+ $this->assertNotNull( $content, 'getContent()' );
+ $this->assertSame( CONTENT_MODEL_TEXT, $content->getModel(), 'getModel()' );
+ }
+
+ public function provideUserCanBitfield() {
+ yield [ 0, 0, [], null, true ];
+ // Bitfields match, user has no permissions
+ yield [
+ RevisionRecord::DELETED_TEXT,
+ RevisionRecord::DELETED_TEXT,
+ [],
+ null,
+ false
+ ];
+ yield [
+ RevisionRecord::DELETED_COMMENT,
+ RevisionRecord::DELETED_COMMENT,
+ [],
+ null,
+ false,
+ ];
+ yield [
+ RevisionRecord::DELETED_USER,
+ RevisionRecord::DELETED_USER,
+ [],
+ null,
+ false
+ ];
+ yield [
+ RevisionRecord::DELETED_RESTRICTED,
+ RevisionRecord::DELETED_RESTRICTED,
+ [],
+ null,
+ false,
+ ];
+ // Bitfields match, user (admin) does have permissions
+ yield [
+ RevisionRecord::DELETED_TEXT,
+ RevisionRecord::DELETED_TEXT,
+ [ 'sysop' ],
+ null,
+ true,
+ ];
+ yield [
+ RevisionRecord::DELETED_COMMENT,
+ RevisionRecord::DELETED_COMMENT,
+ [ 'sysop' ],
+ null,
+ true,
+ ];
+ yield [
+ RevisionRecord::DELETED_USER,
+ RevisionRecord::DELETED_USER,
+ [ 'sysop' ],
+ null,
+ true,
+ ];
+ // Bitfields match, user (admin) does not have permissions
+ yield [
+ RevisionRecord::DELETED_RESTRICTED,
+ RevisionRecord::DELETED_RESTRICTED,
+ [ 'sysop' ],
+ null,
+ false,
+ ];
+ // Bitfields match, user (oversight) does have permissions
+ yield [
+ RevisionRecord::DELETED_RESTRICTED,
+ RevisionRecord::DELETED_RESTRICTED,
+ [ 'oversight' ],
+ null,
+ true,
+ ];
+ // Check permissions using the title
+ yield [
+ RevisionRecord::DELETED_TEXT,
+ RevisionRecord::DELETED_TEXT,
+ [ 'sysop' ],
+ Title::newFromText( __METHOD__ ),
+ true,
+ ];
+ yield [
+ RevisionRecord::DELETED_TEXT,
+ RevisionRecord::DELETED_TEXT,
+ [],
+ Title::newFromText( __METHOD__ ),
+ false,
+ ];
+ }
+
+ /**
+ * @dataProvider provideUserCanBitfield
+ * @covers \MediaWiki\Storage\RevisionRecord::userCanBitfield
+ */
+ public function testUserCanBitfield( $bitField, $field, $userGroups, $title, $expected ) {
+ $this->forceStandardPermissions();
+
+ $user = $this->getTestUser( $userGroups )->getUser();
+
+ $this->assertSame(
+ $expected,
+ RevisionRecord::userCanBitfield( $bitField, $field, $user, $title )
+ );
+ }
+
+ public function provideHasSameContent() {
+ /**
+ * @param SlotRecord[] $slots
+ * @param int $revId
+ * @return RevisionStoreRecord
+ */
+ $recordCreator = function ( array $slots, $revId ) {
+ $title = Title::newFromText( 'provideHasSameContent' );
+ $title->resetArticleID( 19 );
+ $slots = new RevisionSlots( $slots );
+
+ return new RevisionStoreRecord(
+ $title,
+ new UserIdentityValue( 11, __METHOD__, 0 ),
+ CommentStoreComment::newUnsavedComment( __METHOD__ ),
+ (object)[
+ 'rev_id' => strval( $revId ),
+ 'rev_page' => strval( $title->getArticleID() ),
+ 'rev_timestamp' => '20200101000000',
+ 'rev_deleted' => 0,
+ 'rev_minor_edit' => 0,
+ 'rev_parent_id' => '5',
+ 'rev_len' => $slots->computeSize(),
+ 'rev_sha1' => $slots->computeSha1(),
+ 'page_latest' => '18',
+ ],
+ $slots
+ );
+ };
+
+ // Create some slots with content
+ $mainA = SlotRecord::newUnsaved( 'main', new TextContent( 'A' ) );
+ $mainB = SlotRecord::newUnsaved( 'main', new TextContent( 'B' ) );
+ $auxA = SlotRecord::newUnsaved( 'aux', new TextContent( 'A' ) );
+ $auxB = SlotRecord::newUnsaved( 'aux', new TextContent( 'A' ) );
+
+ $initialRecord = $recordCreator( [ $mainA ], 12 );
+
+ return [
+ 'same record object' => [
+ true,
+ $initialRecord,
+ $initialRecord,
+ ],
+ 'same record content, different object' => [
+ true,
+ $recordCreator( [ $mainA ], 12 ),
+ $recordCreator( [ $mainA ], 13 ),
+ ],
+ 'same record content, aux slot, different object' => [
+ true,
+ $recordCreator( [ $auxA ], 12 ),
+ $recordCreator( [ $auxB ], 13 ),
+ ],
+ 'different content' => [
+ false,
+ $recordCreator( [ $mainA ], 12 ),
+ $recordCreator( [ $mainB ], 13 ),
+ ],
+ 'different content and number of slots' => [
+ false,
+ $recordCreator( [ $mainA ], 12 ),
+ $recordCreator( [ $mainA, $mainB ], 13 ),
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideHasSameContent
+ * @covers \MediaWiki\Storage\RevisionRecord::hasSameContent
+ * @group Database
+ */
+ public function testHasSameContent(
+ $expected,
+ RevisionRecord $record1,
+ RevisionRecord $record2
+ ) {
+ $this->assertSame(
+ $expected,
+ $record1->hasSameContent( $record2 )
+ );
+ }
+
+ public function provideIsDeleted() {
+ yield 'no deletion' => [
+ 0,
+ [
+ RevisionRecord::DELETED_TEXT => false,
+ RevisionRecord::DELETED_COMMENT => false,
+ RevisionRecord::DELETED_USER => false,
+ RevisionRecord::DELETED_RESTRICTED => false,
+ ]
+ ];
+ yield 'text deleted' => [
+ RevisionRecord::DELETED_TEXT,
+ [
+ RevisionRecord::DELETED_TEXT => true,
+ RevisionRecord::DELETED_COMMENT => false,
+ RevisionRecord::DELETED_USER => false,
+ RevisionRecord::DELETED_RESTRICTED => false,
+ ]
+ ];
+ yield 'text and comment deleted' => [
+ RevisionRecord::DELETED_TEXT + RevisionRecord::DELETED_COMMENT,
+ [
+ RevisionRecord::DELETED_TEXT => true,
+ RevisionRecord::DELETED_COMMENT => true,
+ RevisionRecord::DELETED_USER => false,
+ RevisionRecord::DELETED_RESTRICTED => false,
+ ]
+ ];
+ yield 'all 4 deleted' => [
+ RevisionRecord::DELETED_TEXT +
+ RevisionRecord::DELETED_COMMENT +
+ RevisionRecord::DELETED_RESTRICTED +
+ RevisionRecord::DELETED_USER,
+ [
+ RevisionRecord::DELETED_TEXT => true,
+ RevisionRecord::DELETED_COMMENT => true,
+ RevisionRecord::DELETED_USER => true,
+ RevisionRecord::DELETED_RESTRICTED => true,
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider provideIsDeleted
+ * @covers \MediaWiki\Storage\RevisionRecord::isDeleted
+ */
+ public function testIsDeleted( $revDeleted, $assertionMap ) {
+ $rev = $this->newRevision( [ 'rev_deleted' => $revDeleted ] );
+ foreach ( $assertionMap as $deletionLevel => $expected ) {
+ $this->assertSame( $expected, $rev->isDeleted( $deletionLevel ) );
+ }
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/Storage/RevisionSlotsTest.php b/www/wiki/tests/phpunit/includes/Storage/RevisionSlotsTest.php
new file mode 100644
index 00000000..b9f833ca
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/Storage/RevisionSlotsTest.php
@@ -0,0 +1,139 @@
+<?php
+
+namespace MediaWiki\Tests\Storage;
+
+use MediaWiki\Storage\RevisionAccessException;
+use MediaWiki\Storage\RevisionSlots;
+use MediaWiki\Storage\SlotRecord;
+use MediaWikiTestCase;
+use WikitextContent;
+
+class RevisionSlotsTest extends MediaWikiTestCase {
+
+ /**
+ * @param SlotRecord[] $slots
+ * @return RevisionSlots
+ */
+ protected function newRevisionSlots( $slots = [] ) {
+ return new RevisionSlots( $slots );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionSlots::getSlot
+ */
+ public function testGetSlot() {
+ $mainSlot = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ $auxSlot = SlotRecord::newUnsaved( 'aux', new WikitextContent( 'B' ) );
+ $slots = $this->newRevisionSlots( [ $mainSlot, $auxSlot ] );
+
+ $this->assertSame( $mainSlot, $slots->getSlot( 'main' ) );
+ $this->assertSame( $auxSlot, $slots->getSlot( 'aux' ) );
+ $this->setExpectedException( RevisionAccessException::class );
+ $slots->getSlot( 'nothere' );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionSlots::hasSlot
+ */
+ public function testHasSlot() {
+ $mainSlot = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ $auxSlot = SlotRecord::newUnsaved( 'aux', new WikitextContent( 'B' ) );
+ $slots = $this->newRevisionSlots( [ $mainSlot, $auxSlot ] );
+
+ $this->assertTrue( $slots->hasSlot( 'main' ) );
+ $this->assertTrue( $slots->hasSlot( 'aux' ) );
+ $this->assertFalse( $slots->hasSlot( 'AUX' ) );
+ $this->assertFalse( $slots->hasSlot( 'xyz' ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionSlots::getContent
+ */
+ public function testGetContent() {
+ $mainContent = new WikitextContent( 'A' );
+ $auxContent = new WikitextContent( 'B' );
+ $mainSlot = SlotRecord::newUnsaved( 'main', $mainContent );
+ $auxSlot = SlotRecord::newUnsaved( 'aux', $auxContent );
+ $slots = $this->newRevisionSlots( [ $mainSlot, $auxSlot ] );
+
+ $this->assertSame( $mainContent, $slots->getContent( 'main' ) );
+ $this->assertSame( $auxContent, $slots->getContent( 'aux' ) );
+ $this->setExpectedException( RevisionAccessException::class );
+ $slots->getContent( 'nothere' );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionSlots::getSlotRoles
+ */
+ public function testGetSlotRoles_someSlots() {
+ $mainSlot = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ $auxSlot = SlotRecord::newUnsaved( 'aux', new WikitextContent( 'B' ) );
+ $slots = $this->newRevisionSlots( [ $mainSlot, $auxSlot ] );
+
+ $this->assertSame( [ 'main', 'aux' ], $slots->getSlotRoles() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionSlots::getSlotRoles
+ */
+ public function testGetSlotRoles_noSlots() {
+ $slots = $this->newRevisionSlots( [] );
+
+ $this->assertSame( [], $slots->getSlotRoles() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionSlots::getSlots
+ */
+ public function testGetSlots() {
+ $mainSlot = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ $auxSlot = SlotRecord::newUnsaved( 'aux', new WikitextContent( 'B' ) );
+ $slotsArray = [ $mainSlot, $auxSlot ];
+ $slots = $this->newRevisionSlots( $slotsArray );
+
+ $this->assertEquals( [ 'main' => $mainSlot, 'aux' => $auxSlot ], $slots->getSlots() );
+ }
+
+ public function provideComputeSize() {
+ yield [ 1, [ 'A' ] ];
+ yield [ 2, [ 'AA' ] ];
+ yield [ 4, [ 'AA', 'X', 'H' ] ];
+ }
+
+ /**
+ * @dataProvider provideComputeSize
+ * @covers \MediaWiki\Storage\RevisionSlots::computeSize
+ */
+ public function testComputeSize( $expected, $contentStrings ) {
+ $slotsArray = [];
+ foreach ( $contentStrings as $key => $contentString ) {
+ $slotsArray[] = SlotRecord::newUnsaved( strval( $key ), new WikitextContent( $contentString ) );
+ }
+ $slots = $this->newRevisionSlots( $slotsArray );
+
+ $this->assertSame( $expected, $slots->computeSize() );
+ }
+
+ public function provideComputeSha1() {
+ yield [ 'ctqm7794fr2dp1taki8a88ovwnvmnmj', [ 'A' ] ];
+ yield [ 'eyq8wiwlcofnaiy4eid97gyfy60uw51', [ 'AA' ] ];
+ yield [ 'lavctqfpxartyjr31f853drgfl4kj1g', [ 'AA', 'X', 'H' ] ];
+ }
+
+ /**
+ * @dataProvider provideComputeSha1
+ * @covers \MediaWiki\Storage\RevisionSlots::computeSha1
+ * @note this test is a bit brittle as the hashes are hardcoded, perhaps just check that strings
+ * are returned and different Slots objects return different strings?
+ */
+ public function testComputeSha1( $expected, $contentStrings ) {
+ $slotsArray = [];
+ foreach ( $contentStrings as $key => $contentString ) {
+ $slotsArray[] = SlotRecord::newUnsaved( strval( $key ), new WikitextContent( $contentString ) );
+ }
+ $slots = $this->newRevisionSlots( $slotsArray );
+
+ $this->assertSame( $expected, $slots->computeSha1() );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/Storage/RevisionStoreDbTest.php b/www/wiki/tests/phpunit/includes/Storage/RevisionStoreDbTest.php
new file mode 100644
index 00000000..7d6906c1
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/Storage/RevisionStoreDbTest.php
@@ -0,0 +1,1281 @@
+<?php
+
+namespace MediaWiki\Tests\Storage;
+
+use CommentStoreComment;
+use Exception;
+use HashBagOStuff;
+use InvalidArgumentException;
+use Language;
+use MediaWiki\Linker\LinkTarget;
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\BlobStoreFactory;
+use MediaWiki\Storage\IncompleteRevisionException;
+use MediaWiki\Storage\MutableRevisionRecord;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\RevisionStore;
+use MediaWiki\Storage\SlotRecord;
+use MediaWiki\Storage\SqlBlobStore;
+use MediaWikiTestCase;
+use Revision;
+use TestUserRegistry;
+use Title;
+use WANObjectCache;
+use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\DatabaseSqlite;
+use Wikimedia\Rdbms\FakeResultWrapper;
+use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\Rdbms\TransactionProfiler;
+use WikiPage;
+use WikitextContent;
+
+/**
+ * @group Database
+ */
+class RevisionStoreDbTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ parent::setUp();
+ $this->tablesUsed[] = 'archive';
+ $this->tablesUsed[] = 'page';
+ $this->tablesUsed[] = 'revision';
+ $this->tablesUsed[] = 'comment';
+ }
+
+ /**
+ * @return LoadBalancer
+ */
+ private function getLoadBalancerMock( array $server ) {
+ $lb = $this->getMockBuilder( LoadBalancer::class )
+ ->setMethods( [ 'reallyOpenConnection' ] )
+ ->setConstructorArgs( [ [ 'servers' => [ $server ] ] ] )
+ ->getMock();
+
+ $lb->method( 'reallyOpenConnection' )->willReturnCallback(
+ function ( array $server, $dbNameOverride ) {
+ return $this->getDatabaseMock( $server );
+ }
+ );
+
+ return $lb;
+ }
+
+ /**
+ * @return Database
+ */
+ private function getDatabaseMock( array $params ) {
+ $db = $this->getMockBuilder( DatabaseSqlite::class )
+ ->setMethods( [ 'select', 'doQuery', 'open', 'closeConnection', 'isOpen' ] )
+ ->setConstructorArgs( [ $params ] )
+ ->getMock();
+
+ $db->method( 'select' )->willReturn( new FakeResultWrapper( [] ) );
+ $db->method( 'isOpen' )->willReturn( true );
+
+ return $db;
+ }
+
+ public function provideDomainCheck() {
+ yield [ false, 'test', '' ];
+ yield [ 'test', 'test', '' ];
+
+ yield [ false, 'test', 'foo_' ];
+ yield [ 'test-foo_', 'test', 'foo_' ];
+
+ yield [ false, 'dash-test', '' ];
+ yield [ 'dash-test', 'dash-test', '' ];
+
+ yield [ false, 'underscore_test', 'foo_' ];
+ yield [ 'underscore_test-foo_', 'underscore_test', 'foo_' ];
+ }
+
+ /**
+ * @dataProvider provideDomainCheck
+ * @covers \MediaWiki\Storage\RevisionStore::checkDatabaseWikiId
+ */
+ public function testDomainCheck( $wikiId, $dbName, $dbPrefix ) {
+ $this->setMwGlobals(
+ [
+ 'wgDBname' => $dbName,
+ 'wgDBprefix' => $dbPrefix,
+ ]
+ );
+
+ $loadBalancer = $this->getLoadBalancerMock(
+ [
+ 'host' => '*dummy*',
+ 'dbDirectory' => '*dummy*',
+ 'user' => 'test',
+ 'password' => 'test',
+ 'flags' => 0,
+ 'variables' => [],
+ 'schema' => '',
+ 'cliMode' => true,
+ 'agent' => '',
+ 'load' => 100,
+ 'profiler' => null,
+ 'trxProfiler' => new TransactionProfiler(),
+ 'connLogger' => new \Psr\Log\NullLogger(),
+ 'queryLogger' => new \Psr\Log\NullLogger(),
+ 'errorLogger' => function () {
+ },
+ 'deprecationLogger' => function () {
+ },
+ 'type' => 'test',
+ 'dbname' => $dbName,
+ 'tablePrefix' => $dbPrefix,
+ ]
+ );
+ $db = $loadBalancer->getConnection( DB_REPLICA );
+
+ $blobStore = $this->getMockBuilder( SqlBlobStore::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $store = new RevisionStore(
+ $loadBalancer,
+ $blobStore,
+ new WANObjectCache( [ 'cache' => new HashBagOStuff() ] ),
+ MediaWikiServices::getInstance()->getCommentStore(),
+ MediaWikiServices::getInstance()->getActorMigration(),
+ $wikiId
+ );
+
+ $count = $store->countRevisionsByPageId( $db, 0 );
+
+ // Dummy check to make PhpUnit happy. We are really only interested in
+ // countRevisionsByPageId not failing due to the DB domain check.
+ $this->assertSame( 0, $count );
+ }
+
+ private function assertLinkTargetsEqual( LinkTarget $l1, LinkTarget $l2 ) {
+ $this->assertEquals( $l1->getDBkey(), $l2->getDBkey() );
+ $this->assertEquals( $l1->getNamespace(), $l2->getNamespace() );
+ $this->assertEquals( $l1->getFragment(), $l2->getFragment() );
+ $this->assertEquals( $l1->getInterwiki(), $l2->getInterwiki() );
+ }
+
+ private function assertRevisionRecordsEqual( RevisionRecord $r1, RevisionRecord $r2 ) {
+ $this->assertEquals( $r1->getUser()->getName(), $r2->getUser()->getName() );
+ $this->assertEquals( $r1->getUser()->getId(), $r2->getUser()->getId() );
+ $this->assertEquals( $r1->getComment(), $r2->getComment() );
+ $this->assertEquals( $r1->getPageAsLinkTarget(), $r2->getPageAsLinkTarget() );
+ $this->assertEquals( $r1->getTimestamp(), $r2->getTimestamp() );
+ $this->assertEquals( $r1->getVisibility(), $r2->getVisibility() );
+ $this->assertEquals( $r1->getSha1(), $r2->getSha1() );
+ $this->assertEquals( $r1->getParentId(), $r2->getParentId() );
+ $this->assertEquals( $r1->getSize(), $r2->getSize() );
+ $this->assertEquals( $r1->getPageId(), $r2->getPageId() );
+ $this->assertEquals( $r1->getSlotRoles(), $r2->getSlotRoles() );
+ $this->assertEquals( $r1->getWikiId(), $r2->getWikiId() );
+ $this->assertEquals( $r1->isMinor(), $r2->isMinor() );
+ foreach ( $r1->getSlotRoles() as $role ) {
+ $this->assertSlotRecordsEqual( $r1->getSlot( $role ), $r2->getSlot( $role ) );
+ $this->assertTrue( $r1->getContent( $role )->equals( $r2->getContent( $role ) ) );
+ }
+ foreach ( [
+ RevisionRecord::DELETED_TEXT,
+ RevisionRecord::DELETED_COMMENT,
+ RevisionRecord::DELETED_USER,
+ RevisionRecord::DELETED_RESTRICTED,
+ ] as $field ) {
+ $this->assertEquals( $r1->isDeleted( $field ), $r2->isDeleted( $field ) );
+ }
+ }
+
+ private function assertSlotRecordsEqual( SlotRecord $s1, SlotRecord $s2 ) {
+ $this->assertSame( $s1->getRole(), $s2->getRole() );
+ $this->assertSame( $s1->getModel(), $s2->getModel() );
+ $this->assertSame( $s1->getFormat(), $s2->getFormat() );
+ $this->assertSame( $s1->getSha1(), $s2->getSha1() );
+ $this->assertSame( $s1->getSize(), $s2->getSize() );
+ $this->assertTrue( $s1->getContent()->equals( $s2->getContent() ) );
+
+ $s1->hasRevision() ? $this->assertSame( $s1->getRevision(), $s2->getRevision() ) : null;
+ $s1->hasAddress() ? $this->assertSame( $s1->hasAddress(), $s2->hasAddress() ) : null;
+ }
+
+ private function assertRevisionCompleteness( RevisionRecord $r ) {
+ foreach ( $r->getSlotRoles() as $role ) {
+ $this->assertSlotCompleteness( $r, $r->getSlot( $role ) );
+ }
+ }
+
+ private function assertSlotCompleteness( RevisionRecord $r, SlotRecord $slot ) {
+ $this->assertTrue( $slot->hasAddress() );
+ $this->assertSame( $r->getId(), $slot->getRevision() );
+ }
+
+ /**
+ * @param mixed[] $details
+ *
+ * @return RevisionRecord
+ */
+ private function getRevisionRecordFromDetailsArray( $title, $details = [] ) {
+ // Convert some values that can't be provided by dataProviders
+ $page = WikiPage::factory( $title );
+ if ( isset( $details['user'] ) && $details['user'] === true ) {
+ $details['user'] = $this->getTestUser()->getUser();
+ }
+ if ( isset( $details['page'] ) && $details['page'] === true ) {
+ $details['page'] = $page->getId();
+ }
+ if ( isset( $details['parent'] ) && $details['parent'] === true ) {
+ $details['parent'] = $page->getLatest();
+ }
+
+ // Create the RevisionRecord with any available data
+ $rev = new MutableRevisionRecord( $title );
+ isset( $details['slot'] ) ? $rev->setSlot( $details['slot'] ) : null;
+ isset( $details['parent'] ) ? $rev->setParentId( $details['parent'] ) : null;
+ isset( $details['page'] ) ? $rev->setPageId( $details['page'] ) : null;
+ isset( $details['size'] ) ? $rev->setSize( $details['size'] ) : null;
+ isset( $details['sha1'] ) ? $rev->setSha1( $details['sha1'] ) : null;
+ isset( $details['comment'] ) ? $rev->setComment( $details['comment'] ) : null;
+ isset( $details['timestamp'] ) ? $rev->setTimestamp( $details['timestamp'] ) : null;
+ isset( $details['minor'] ) ? $rev->setMinorEdit( $details['minor'] ) : null;
+ isset( $details['user'] ) ? $rev->setUser( $details['user'] ) : null;
+ isset( $details['visibility'] ) ? $rev->setVisibility( $details['visibility'] ) : null;
+ isset( $details['id'] ) ? $rev->setId( $details['id'] ) : null;
+
+ return $rev;
+ }
+
+ private function getRandomCommentStoreComment() {
+ return CommentStoreComment::newUnsavedComment( __METHOD__ . '.' . rand( 0, 1000 ) );
+ }
+
+ public function provideInsertRevisionOn_successes() {
+ yield 'Bare minimum revision insertion' => [
+ Title::newFromText( 'UTPage' ),
+ [
+ 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'parent' => true,
+ 'comment' => $this->getRandomCommentStoreComment(),
+ 'timestamp' => '20171117010101',
+ 'user' => true,
+ ],
+ ];
+ yield 'Detailed revision insertion' => [
+ Title::newFromText( 'UTPage' ),
+ [
+ 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'parent' => true,
+ 'page' => true,
+ 'comment' => $this->getRandomCommentStoreComment(),
+ 'timestamp' => '20171117010101',
+ 'user' => true,
+ 'minor' => true,
+ 'visibility' => RevisionRecord::DELETED_RESTRICTED,
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideInsertRevisionOn_successes
+ * @covers \MediaWiki\Storage\RevisionStore::insertRevisionOn
+ */
+ public function testInsertRevisionOn_successes( Title $title, array $revDetails = [] ) {
+ $rev = $this->getRevisionRecordFromDetailsArray( $title, $revDetails );
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $return = $store->insertRevisionOn( $rev, wfGetDB( DB_MASTER ) );
+
+ $this->assertLinkTargetsEqual( $title, $return->getPageAsLinkTarget() );
+ $this->assertRevisionRecordsEqual( $rev, $return );
+ $this->assertRevisionCompleteness( $return );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::insertRevisionOn
+ */
+ public function testInsertRevisionOn_blobAddressExists() {
+ $title = Title::newFromText( 'UTPage' );
+ $revDetails = [
+ 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'parent' => true,
+ 'comment' => $this->getRandomCommentStoreComment(),
+ 'timestamp' => '20171117010101',
+ 'user' => true,
+ ];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+
+ // Insert the first revision
+ $revOne = $this->getRevisionRecordFromDetailsArray( $title, $revDetails );
+ $firstReturn = $store->insertRevisionOn( $revOne, wfGetDB( DB_MASTER ) );
+ $this->assertLinkTargetsEqual( $title, $firstReturn->getPageAsLinkTarget() );
+ $this->assertRevisionRecordsEqual( $revOne, $firstReturn );
+
+ // Insert a second revision inheriting the same blob address
+ $revDetails['slot'] = SlotRecord::newInherited( $firstReturn->getSlot( 'main' ) );
+ $revTwo = $this->getRevisionRecordFromDetailsArray( $title, $revDetails );
+ $secondReturn = $store->insertRevisionOn( $revTwo, wfGetDB( DB_MASTER ) );
+ $this->assertLinkTargetsEqual( $title, $secondReturn->getPageAsLinkTarget() );
+ $this->assertRevisionRecordsEqual( $revTwo, $secondReturn );
+
+ // Assert that the same blob address has been used.
+ $this->assertEquals(
+ $firstReturn->getSlot( 'main' )->getAddress(),
+ $secondReturn->getSlot( 'main' )->getAddress()
+ );
+ // And that different revisions have been created.
+ $this->assertNotSame(
+ $firstReturn->getId(),
+ $secondReturn->getId()
+ );
+ }
+
+ public function provideInsertRevisionOn_failures() {
+ yield 'no slot' => [
+ Title::newFromText( 'UTPage' ),
+ [
+ 'comment' => $this->getRandomCommentStoreComment(),
+ 'timestamp' => '20171117010101',
+ 'user' => true,
+ ],
+ new InvalidArgumentException( 'At least one slot needs to be defined!' )
+ ];
+ yield 'slot that is not main slot' => [
+ Title::newFromText( 'UTPage' ),
+ [
+ 'slot' => SlotRecord::newUnsaved( 'lalala', new WikitextContent( 'Chicken' ) ),
+ 'comment' => $this->getRandomCommentStoreComment(),
+ 'timestamp' => '20171117010101',
+ 'user' => true,
+ ],
+ new InvalidArgumentException( 'Only the main slot is supported for now!' )
+ ];
+ yield 'no timestamp' => [
+ Title::newFromText( 'UTPage' ),
+ [
+ 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'comment' => $this->getRandomCommentStoreComment(),
+ 'user' => true,
+ ],
+ new IncompleteRevisionException( 'timestamp field must not be NULL!' )
+ ];
+ yield 'no comment' => [
+ Title::newFromText( 'UTPage' ),
+ [
+ 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'timestamp' => '20171117010101',
+ 'user' => true,
+ ],
+ new IncompleteRevisionException( 'comment must not be NULL!' )
+ ];
+ yield 'no user' => [
+ Title::newFromText( 'UTPage' ),
+ [
+ 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'comment' => $this->getRandomCommentStoreComment(),
+ 'timestamp' => '20171117010101',
+ ],
+ new IncompleteRevisionException( 'user must not be NULL!' )
+ ];
+ }
+
+ /**
+ * @dataProvider provideInsertRevisionOn_failures
+ * @covers \MediaWiki\Storage\RevisionStore::insertRevisionOn
+ */
+ public function testInsertRevisionOn_failures(
+ Title $title,
+ array $revDetails = [],
+ Exception $exception ) {
+ $rev = $this->getRevisionRecordFromDetailsArray( $title, $revDetails );
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+
+ $this->setExpectedException(
+ get_class( $exception ),
+ $exception->getMessage(),
+ $exception->getCode()
+ );
+ $store->insertRevisionOn( $rev, wfGetDB( DB_MASTER ) );
+ }
+
+ public function provideNewNullRevision() {
+ yield [
+ Title::newFromText( 'UTPage' ),
+ CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment1' ),
+ true,
+ ];
+ yield [
+ Title::newFromText( 'UTPage' ),
+ CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment2', [ 'a' => 1 ] ),
+ false,
+ ];
+ }
+
+ /**
+ * @dataProvider provideNewNullRevision
+ * @covers \MediaWiki\Storage\RevisionStore::newNullRevision
+ */
+ public function testNewNullRevision( Title $title, $comment, $minor ) {
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $user = TestUserRegistry::getMutableTestUser( __METHOD__ )->getUser();
+
+ $parent = $store->getRevisionByTitle( $title );
+ $record = $store->newNullRevision(
+ wfGetDB( DB_MASTER ),
+ $title,
+ $comment,
+ $minor,
+ $user
+ );
+
+ $this->assertEquals( $title->getNamespace(), $record->getPageAsLinkTarget()->getNamespace() );
+ $this->assertEquals( $title->getDBkey(), $record->getPageAsLinkTarget()->getDBkey() );
+ $this->assertEquals( $comment, $record->getComment() );
+ $this->assertEquals( $minor, $record->isMinor() );
+ $this->assertEquals( $user->getName(), $record->getUser()->getName() );
+ $this->assertEquals( $parent->getId(), $record->getParentId() );
+
+ $parentSlot = $parent->getSlot( 'main' );
+ $slot = $record->getSlot( 'main' );
+
+ $this->assertTrue( $slot->isInherited(), 'isInherited' );
+ $this->assertSame( $parentSlot->getOrigin(), $slot->getOrigin(), 'getOrigin' );
+ $this->assertSame( $parentSlot->getAddress(), $slot->getAddress(), 'getAddress' );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::newNullRevision
+ */
+ public function testNewNullRevision_nonExistingTitle() {
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $record = $store->newNullRevision(
+ wfGetDB( DB_MASTER ),
+ Title::newFromText( __METHOD__ . '.iDontExist!' ),
+ CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment' ),
+ false,
+ TestUserRegistry::getMutableTestUser( __METHOD__ )->getUser()
+ );
+ $this->assertNull( $record );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getRcIdIfUnpatrolled
+ */
+ public function testGetRcIdIfUnpatrolled_returnsRecentChangesId() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $status = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
+ /** @var Revision $rev */
+ $rev = $status->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $revisionRecord = $store->getRevisionById( $rev->getId() );
+ $result = $store->getRcIdIfUnpatrolled( $revisionRecord );
+
+ $this->assertGreaterThan( 0, $result );
+ $this->assertSame(
+ $page->getRevision()->getRecentChange()->getAttribute( 'rc_id' ),
+ $result
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getRcIdIfUnpatrolled
+ */
+ public function testGetRcIdIfUnpatrolled_returnsZeroIfPatrolled() {
+ // This assumes that sysops are auto patrolled
+ $sysop = $this->getTestSysop()->getUser();
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $status = $page->doEditContent(
+ new WikitextContent( __METHOD__ ),
+ __METHOD__,
+ 0,
+ false,
+ $sysop
+ );
+ /** @var Revision $rev */
+ $rev = $status->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $revisionRecord = $store->getRevisionById( $rev->getId() );
+ $result = $store->getRcIdIfUnpatrolled( $revisionRecord );
+
+ $this->assertSame( 0, $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getRecentChange
+ */
+ public function testGetRecentChange() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $content = new WikitextContent( __METHOD__ );
+ $status = $page->doEditContent( $content, __METHOD__ );
+ /** @var Revision $rev */
+ $rev = $status->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $revRecord = $store->getRevisionById( $rev->getId() );
+ $recentChange = $store->getRecentChange( $revRecord );
+
+ $this->assertEquals( $rev->getId(), $recentChange->getAttribute( 'rc_this_oldid' ) );
+ $this->assertEquals( $rev->getRecentChange(), $recentChange );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getRevisionById
+ */
+ public function testGetRevisionById() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $content = new WikitextContent( __METHOD__ );
+ $status = $page->doEditContent( $content, __METHOD__ );
+ /** @var Revision $rev */
+ $rev = $status->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $revRecord = $store->getRevisionById( $rev->getId() );
+
+ $this->assertSame( $rev->getId(), $revRecord->getId() );
+ $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
+ $this->assertSame( __METHOD__, $revRecord->getComment()->text );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getRevisionByTitle
+ */
+ public function testGetRevisionByTitle() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $content = new WikitextContent( __METHOD__ );
+ $status = $page->doEditContent( $content, __METHOD__ );
+ /** @var Revision $rev */
+ $rev = $status->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $revRecord = $store->getRevisionByTitle( $page->getTitle() );
+
+ $this->assertSame( $rev->getId(), $revRecord->getId() );
+ $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
+ $this->assertSame( __METHOD__, $revRecord->getComment()->text );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getRevisionByPageId
+ */
+ public function testGetRevisionByPageId() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $content = new WikitextContent( __METHOD__ );
+ $status = $page->doEditContent( $content, __METHOD__ );
+ /** @var Revision $rev */
+ $rev = $status->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $revRecord = $store->getRevisionByPageId( $page->getId() );
+
+ $this->assertSame( $rev->getId(), $revRecord->getId() );
+ $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
+ $this->assertSame( __METHOD__, $revRecord->getComment()->text );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getRevisionByTimestamp
+ */
+ public function testGetRevisionByTimestamp() {
+ // Make sure there is 1 second between the last revision and the rev we create...
+ // Otherwise we might not get the correct revision and the test may fail...
+ // :(
+ sleep( 1 );
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $content = new WikitextContent( __METHOD__ );
+ $status = $page->doEditContent( $content, __METHOD__ );
+ /** @var Revision $rev */
+ $rev = $status->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $revRecord = $store->getRevisionByTimestamp(
+ $page->getTitle(),
+ $rev->getTimestamp()
+ );
+
+ $this->assertSame( $rev->getId(), $revRecord->getId() );
+ $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
+ $this->assertSame( __METHOD__, $revRecord->getComment()->text );
+ }
+
+ private function revisionToRow( Revision $rev ) {
+ $page = WikiPage::factory( $rev->getTitle() );
+
+ return (object)[
+ 'rev_id' => (string)$rev->getId(),
+ 'rev_page' => (string)$rev->getPage(),
+ 'rev_text_id' => (string)$rev->getTextId(),
+ 'rev_timestamp' => (string)$rev->getTimestamp(),
+ 'rev_user_text' => (string)$rev->getUserText(),
+ 'rev_user' => (string)$rev->getUser(),
+ 'rev_minor_edit' => $rev->isMinor() ? '1' : '0',
+ 'rev_deleted' => (string)$rev->getVisibility(),
+ 'rev_len' => (string)$rev->getSize(),
+ 'rev_parent_id' => (string)$rev->getParentId(),
+ 'rev_sha1' => (string)$rev->getSha1(),
+ 'rev_comment_text' => $rev->getComment(),
+ 'rev_comment_data' => null,
+ 'rev_comment_cid' => null,
+ 'rev_content_format' => $rev->getContentFormat(),
+ 'rev_content_model' => $rev->getContentModel(),
+ 'page_namespace' => (string)$page->getTitle()->getNamespace(),
+ 'page_title' => $page->getTitle()->getDBkey(),
+ 'page_id' => (string)$page->getId(),
+ 'page_latest' => (string)$page->getLatest(),
+ 'page_is_redirect' => $page->isRedirect() ? '1' : '0',
+ 'page_len' => (string)$page->getContent()->getSize(),
+ 'user_name' => (string)$rev->getUserText(),
+ ];
+ }
+
+ private function assertRevisionRecordMatchesRevision(
+ Revision $rev,
+ RevisionRecord $record
+ ) {
+ $this->assertSame( $rev->getId(), $record->getId() );
+ $this->assertSame( $rev->getPage(), $record->getPageId() );
+ $this->assertSame( $rev->getTimestamp(), $record->getTimestamp() );
+ $this->assertSame( $rev->getUserText(), $record->getUser()->getName() );
+ $this->assertSame( $rev->getUser(), $record->getUser()->getId() );
+ $this->assertSame( $rev->isMinor(), $record->isMinor() );
+ $this->assertSame( $rev->getVisibility(), $record->getVisibility() );
+ $this->assertSame( $rev->getSize(), $record->getSize() );
+ /**
+ * @note As of MW 1.31, the database schema allows the parent ID to be
+ * NULL to indicate that it is unknown.
+ */
+ $expectedParent = $rev->getParentId();
+ if ( $expectedParent === null ) {
+ $expectedParent = 0;
+ }
+ $this->assertSame( $expectedParent, $record->getParentId() );
+ $this->assertSame( $rev->getSha1(), $record->getSha1() );
+ $this->assertSame( $rev->getComment(), $record->getComment()->text );
+ $this->assertSame( $rev->getContentFormat(), $record->getContent( 'main' )->getDefaultFormat() );
+ $this->assertSame( $rev->getContentModel(), $record->getContent( 'main' )->getModel() );
+ $this->assertLinkTargetsEqual( $rev->getTitle(), $record->getPageAsLinkTarget() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29
+ */
+ public function testNewRevisionFromRow_anonEdit() {
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->overrideMwServices();
+
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $text = __METHOD__ . 'a-ä';
+ /** @var Revision $rev */
+ $rev = $page->doEditContent(
+ new WikitextContent( $text ),
+ __METHOD__ . 'a'
+ )->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $record = $store->newRevisionFromRow(
+ $this->revisionToRow( $rev ),
+ [],
+ $page->getTitle()
+ );
+ $this->assertRevisionRecordMatchesRevision( $rev, $record );
+ $this->assertSame( $text, $rev->getContent()->serialize() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29
+ */
+ public function testNewRevisionFromRow_anonEdit_legacyEncoding() {
+ $this->setMwGlobals( 'wgLegacyEncoding', 'windows-1252' );
+ $this->overrideMwServices();
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $text = __METHOD__ . 'a-ä';
+ /** @var Revision $rev */
+ $rev = $page->doEditContent(
+ new WikitextContent( $text ),
+ __METHOD__. 'a'
+ )->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $record = $store->newRevisionFromRow(
+ $this->revisionToRow( $rev ),
+ [],
+ $page->getTitle()
+ );
+ $this->assertRevisionRecordMatchesRevision( $rev, $record );
+ $this->assertSame( $text, $rev->getContent()->serialize() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29
+ */
+ public function testNewRevisionFromRow_userEdit() {
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->overrideMwServices();
+
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $text = __METHOD__ . 'b-ä';
+ /** @var Revision $rev */
+ $rev = $page->doEditContent(
+ new WikitextContent( $text ),
+ __METHOD__ . 'b',
+ 0,
+ false,
+ $this->getTestUser()->getUser()
+ )->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $record = $store->newRevisionFromRow(
+ $this->revisionToRow( $rev ),
+ [],
+ $page->getTitle()
+ );
+ $this->assertRevisionRecordMatchesRevision( $rev, $record );
+ $this->assertSame( $text, $rev->getContent()->serialize() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromArchiveRow
+ */
+ public function testNewRevisionFromArchiveRow() {
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $title = Title::newFromText( __METHOD__ );
+ $text = __METHOD__ . '-bä';
+ $page = WikiPage::factory( $title );
+ /** @var Revision $orig */
+ $orig = $page->doEditContent( new WikitextContent( $text ), __METHOD__ )
+ ->value['revision'];
+ $page->doDeleteArticle( __METHOD__ );
+
+ $db = wfGetDB( DB_MASTER );
+ $arQuery = $store->getArchiveQueryInfo();
+ $res = $db->select(
+ $arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
+ __METHOD__, [], $arQuery['joins']
+ );
+ $this->assertTrue( is_object( $res ), 'query failed' );
+
+ $row = $res->fetchObject();
+ $res->free();
+ $record = $store->newRevisionFromArchiveRow( $row );
+
+ $this->assertRevisionRecordMatchesRevision( $orig, $record );
+ $this->assertSame( $text, $record->getContent( 'main' )->serialize() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromArchiveRow
+ */
+ public function testNewRevisionFromArchiveRow_legacyEncoding() {
+ $this->setMwGlobals( 'wgLegacyEncoding', 'windows-1252' );
+ $this->overrideMwServices();
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $title = Title::newFromText( __METHOD__ );
+ $text = __METHOD__ . '-bä';
+ $page = WikiPage::factory( $title );
+ /** @var Revision $orig */
+ $orig = $page->doEditContent( new WikitextContent( $text ), __METHOD__ )
+ ->value['revision'];
+ $page->doDeleteArticle( __METHOD__ );
+
+ $db = wfGetDB( DB_MASTER );
+ $arQuery = $store->getArchiveQueryInfo();
+ $res = $db->select(
+ $arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
+ __METHOD__, [], $arQuery['joins']
+ );
+ $this->assertTrue( is_object( $res ), 'query failed' );
+
+ $row = $res->fetchObject();
+ $res->free();
+ $record = $store->newRevisionFromArchiveRow( $row );
+
+ $this->assertRevisionRecordMatchesRevision( $orig, $record );
+ $this->assertSame( $text, $record->getContent( 'main' )->serialize() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromId
+ */
+ public function testLoadRevisionFromId() {
+ $title = Title::newFromText( __METHOD__ );
+ $page = WikiPage::factory( $title );
+ /** @var Revision $rev */
+ $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
+ ->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $result = $store->loadRevisionFromId( wfGetDB( DB_MASTER ), $rev->getId() );
+ $this->assertRevisionRecordMatchesRevision( $rev, $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromPageId
+ */
+ public function testLoadRevisionFromPageId() {
+ $title = Title::newFromText( __METHOD__ );
+ $page = WikiPage::factory( $title );
+ /** @var Revision $rev */
+ $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
+ ->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $result = $store->loadRevisionFromPageId( wfGetDB( DB_MASTER ), $page->getId() );
+ $this->assertRevisionRecordMatchesRevision( $rev, $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromTitle
+ */
+ public function testLoadRevisionFromTitle() {
+ $title = Title::newFromText( __METHOD__ );
+ $page = WikiPage::factory( $title );
+ /** @var Revision $rev */
+ $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
+ ->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $result = $store->loadRevisionFromTitle( wfGetDB( DB_MASTER ), $title );
+ $this->assertRevisionRecordMatchesRevision( $rev, $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromTimestamp
+ */
+ public function testLoadRevisionFromTimestamp() {
+ $title = Title::newFromText( __METHOD__ );
+ $page = WikiPage::factory( $title );
+ /** @var Revision $revOne */
+ $revOne = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
+ ->value['revision'];
+ // Sleep to ensure different timestamps... )(evil)
+ sleep( 1 );
+ /** @var Revision $revTwo */
+ $revTwo = $page->doEditContent( new WikitextContent( __METHOD__ . 'a' ), '' )
+ ->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $this->assertNull(
+ $store->loadRevisionFromTimestamp( wfGetDB( DB_MASTER ), $title, '20150101010101' )
+ );
+ $this->assertSame(
+ $revOne->getId(),
+ $store->loadRevisionFromTimestamp(
+ wfGetDB( DB_MASTER ),
+ $title,
+ $revOne->getTimestamp()
+ )->getId()
+ );
+ $this->assertSame(
+ $revTwo->getId(),
+ $store->loadRevisionFromTimestamp(
+ wfGetDB( DB_MASTER ),
+ $title,
+ $revTwo->getTimestamp()
+ )->getId()
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::listRevisionSizes
+ */
+ public function testGetParentLengths() {
+ $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
+ /** @var Revision $revOne */
+ $revOne = $page->doEditContent(
+ new WikitextContent( __METHOD__ ), __METHOD__
+ )->value['revision'];
+ /** @var Revision $revTwo */
+ $revTwo = $page->doEditContent(
+ new WikitextContent( __METHOD__ . '2' ), __METHOD__
+ )->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $this->assertSame(
+ [
+ $revOne->getId() => strlen( __METHOD__ ),
+ ],
+ $store->listRevisionSizes(
+ wfGetDB( DB_MASTER ),
+ [ $revOne->getId() ]
+ )
+ );
+ $this->assertSame(
+ [
+ $revOne->getId() => strlen( __METHOD__ ),
+ $revTwo->getId() => strlen( __METHOD__ ) + 1,
+ ],
+ $store->listRevisionSizes(
+ wfGetDB( DB_MASTER ),
+ [ $revOne->getId(), $revTwo->getId() ]
+ )
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getPreviousRevision
+ */
+ public function testGetPreviousRevision() {
+ $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
+ /** @var Revision $revOne */
+ $revOne = $page->doEditContent(
+ new WikitextContent( __METHOD__ ), __METHOD__
+ )->value['revision'];
+ /** @var Revision $revTwo */
+ $revTwo = $page->doEditContent(
+ new WikitextContent( __METHOD__ . '2' ), __METHOD__
+ )->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $this->assertNull(
+ $store->getPreviousRevision( $store->getRevisionById( $revOne->getId() ) )
+ );
+ $this->assertSame(
+ $revOne->getId(),
+ $store->getPreviousRevision( $store->getRevisionById( $revTwo->getId() ) )->getId()
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getNextRevision
+ */
+ public function testGetNextRevision() {
+ $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
+ /** @var Revision $revOne */
+ $revOne = $page->doEditContent(
+ new WikitextContent( __METHOD__ ), __METHOD__
+ )->value['revision'];
+ /** @var Revision $revTwo */
+ $revTwo = $page->doEditContent(
+ new WikitextContent( __METHOD__ . '2' ), __METHOD__
+ )->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $this->assertSame(
+ $revTwo->getId(),
+ $store->getNextRevision( $store->getRevisionById( $revOne->getId() ) )->getId()
+ );
+ $this->assertNull(
+ $store->getNextRevision( $store->getRevisionById( $revTwo->getId() ) )
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getTimestampFromId
+ */
+ public function testGetTimestampFromId_found() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ /** @var Revision $rev */
+ $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
+ ->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $result = $store->getTimestampFromId(
+ $page->getTitle(),
+ $rev->getId()
+ );
+
+ $this->assertSame( $rev->getTimestamp(), $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getTimestampFromId
+ */
+ public function testGetTimestampFromId_notFound() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ /** @var Revision $rev */
+ $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
+ ->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $result = $store->getTimestampFromId(
+ $page->getTitle(),
+ $rev->getId() + 1
+ );
+
+ $this->assertFalse( $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::countRevisionsByPageId
+ */
+ public function testCountRevisionsByPageId() {
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
+
+ $this->assertSame(
+ 0,
+ $store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
+ );
+ $page->doEditContent( new WikitextContent( 'a' ), 'a' );
+ $this->assertSame(
+ 1,
+ $store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
+ );
+ $page->doEditContent( new WikitextContent( 'b' ), 'b' );
+ $this->assertSame(
+ 2,
+ $store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::countRevisionsByTitle
+ */
+ public function testCountRevisionsByTitle() {
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
+
+ $this->assertSame(
+ 0,
+ $store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
+ );
+ $page->doEditContent( new WikitextContent( 'a' ), 'a' );
+ $this->assertSame(
+ 1,
+ $store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
+ );
+ $page->doEditContent( new WikitextContent( 'b' ), 'b' );
+ $this->assertSame(
+ 2,
+ $store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::userWasLastToEdit
+ */
+ public function testUserWasLastToEdit_false() {
+ $sysop = $this->getTestSysop()->getUser();
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $result = $store->userWasLastToEdit(
+ wfGetDB( DB_MASTER ),
+ $page->getId(),
+ $sysop->getId(),
+ '20160101010101'
+ );
+ $this->assertFalse( $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::userWasLastToEdit
+ */
+ public function testUserWasLastToEdit_true() {
+ $startTime = wfTimestampNow();
+ $sysop = $this->getTestSysop()->getUser();
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $page->doEditContent(
+ new WikitextContent( __METHOD__ ),
+ __METHOD__,
+ 0,
+ false,
+ $sysop
+ );
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $result = $store->userWasLastToEdit(
+ wfGetDB( DB_MASTER ),
+ $page->getId(),
+ $sysop->getId(),
+ $startTime
+ );
+ $this->assertTrue( $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getKnownCurrentRevision
+ */
+ public function testGetKnownCurrentRevision() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ /** @var Revision $rev */
+ $rev = $page->doEditContent(
+ new WikitextContent( __METHOD__ . 'b' ),
+ __METHOD__ . 'b',
+ 0,
+ false,
+ $this->getTestUser()->getUser()
+ )->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $record = $store->getKnownCurrentRevision(
+ $page->getTitle(),
+ $rev->getId()
+ );
+
+ $this->assertRevisionRecordMatchesRevision( $rev, $record );
+ }
+
+ public function provideNewMutableRevisionFromArray() {
+ yield 'Basic array, with page & id' => [
+ [
+ 'id' => 2,
+ 'page' => 1,
+ 'text_id' => 2,
+ 'timestamp' => '20171017114835',
+ 'user_text' => '111.0.1.2',
+ 'user' => 0,
+ 'minor_edit' => false,
+ 'deleted' => 0,
+ 'len' => 46,
+ 'parent_id' => 1,
+ 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
+ 'comment' => 'Goat Comment!',
+ 'content_format' => 'text/x-wiki',
+ 'content_model' => 'wikitext',
+ ]
+ ];
+ yield 'Basic array, content object' => [
+ [
+ 'id' => 2,
+ 'page' => 1,
+ 'timestamp' => '20171017114835',
+ 'user_text' => '111.0.1.2',
+ 'user' => 0,
+ 'minor_edit' => false,
+ 'deleted' => 0,
+ 'len' => 46,
+ 'parent_id' => 1,
+ 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
+ 'comment' => 'Goat Comment!',
+ 'content' => new WikitextContent( 'Some Content' ),
+ ]
+ ];
+ yield 'Basic array, serialized text' => [
+ [
+ 'id' => 2,
+ 'page' => 1,
+ 'timestamp' => '20171017114835',
+ 'user_text' => '111.0.1.2',
+ 'user' => 0,
+ 'minor_edit' => false,
+ 'deleted' => 0,
+ 'len' => 46,
+ 'parent_id' => 1,
+ 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
+ 'comment' => 'Goat Comment!',
+ 'text' => ( new WikitextContent( 'Söme Content' ) )->serialize(),
+ ]
+ ];
+ yield 'Basic array, serialized text, utf-8 flags' => [
+ [
+ 'id' => 2,
+ 'page' => 1,
+ 'timestamp' => '20171017114835',
+ 'user_text' => '111.0.1.2',
+ 'user' => 0,
+ 'minor_edit' => false,
+ 'deleted' => 0,
+ 'len' => 46,
+ 'parent_id' => 1,
+ 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
+ 'comment' => 'Goat Comment!',
+ 'text' => ( new WikitextContent( 'Söme Content' ) )->serialize(),
+ 'flags' => 'utf-8',
+ ]
+ ];
+ yield 'Basic array, with title' => [
+ [
+ 'title' => Title::newFromText( 'SomeText' ),
+ 'text_id' => 2,
+ 'timestamp' => '20171017114835',
+ 'user_text' => '111.0.1.2',
+ 'user' => 0,
+ 'minor_edit' => false,
+ 'deleted' => 0,
+ 'len' => 46,
+ 'parent_id' => 1,
+ 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
+ 'comment' => 'Goat Comment!',
+ 'content_format' => 'text/x-wiki',
+ 'content_model' => 'wikitext',
+ ]
+ ];
+ yield 'Basic array, no user field' => [
+ [
+ 'id' => 2,
+ 'page' => 1,
+ 'text_id' => 2,
+ 'timestamp' => '20171017114835',
+ 'user_text' => '111.0.1.3',
+ 'minor_edit' => false,
+ 'deleted' => 0,
+ 'len' => 46,
+ 'parent_id' => 1,
+ 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
+ 'comment' => 'Goat Comment!',
+ 'content_format' => 'text/x-wiki',
+ 'content_model' => 'wikitext',
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider provideNewMutableRevisionFromArray
+ * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
+ */
+ public function testNewMutableRevisionFromArray( array $array ) {
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+
+ $result = $store->newMutableRevisionFromArray( $array );
+
+ if ( isset( $array['id'] ) ) {
+ $this->assertSame( $array['id'], $result->getId() );
+ }
+ if ( isset( $array['page'] ) ) {
+ $this->assertSame( $array['page'], $result->getPageId() );
+ }
+ $this->assertSame( $array['timestamp'], $result->getTimestamp() );
+ $this->assertSame( $array['user_text'], $result->getUser()->getName() );
+ if ( isset( $array['user'] ) ) {
+ $this->assertSame( $array['user'], $result->getUser()->getId() );
+ }
+ $this->assertSame( (bool)$array['minor_edit'], $result->isMinor() );
+ $this->assertSame( $array['deleted'], $result->getVisibility() );
+ $this->assertSame( $array['len'], $result->getSize() );
+ $this->assertSame( $array['parent_id'], $result->getParentId() );
+ $this->assertSame( $array['sha1'], $result->getSha1() );
+ $this->assertSame( $array['comment'], $result->getComment()->text );
+ if ( isset( $array['content'] ) ) {
+ $this->assertTrue(
+ $result->getSlot( 'main' )->getContent()->equals( $array['content'] )
+ );
+ } elseif ( isset( $array['text'] ) ) {
+ $this->assertSame( $array['text'], $result->getSlot( 'main' )->getContent()->serialize() );
+ } else {
+ $this->assertSame(
+ $array['content_format'],
+ $result->getSlot( 'main' )->getContent()->getDefaultFormat()
+ );
+ $this->assertSame( $array['content_model'], $result->getSlot( 'main' )->getModel() );
+ }
+ }
+
+ /**
+ * @dataProvider provideNewMutableRevisionFromArray
+ * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
+ */
+ public function testNewMutableRevisionFromArray_legacyEncoding( array $array ) {
+ $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
+ $blobStore = new SqlBlobStore( wfGetLB(), $cache );
+ $blobStore->setLegacyEncoding( 'windows-1252', Language::factory( 'en' ) );
+
+ $factory = $this->getMockBuilder( BlobStoreFactory::class )
+ ->setMethods( [ 'newBlobStore', 'newSqlBlobStore' ] )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $factory->expects( $this->any() )
+ ->method( 'newBlobStore' )
+ ->willReturn( $blobStore );
+ $factory->expects( $this->any() )
+ ->method( 'newSqlBlobStore' )
+ ->willReturn( $blobStore );
+
+ $this->setService( 'BlobStoreFactory', $factory );
+
+ $this->testNewMutableRevisionFromArray( $array );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/Storage/RevisionStoreRecordTest.php b/www/wiki/tests/phpunit/includes/Storage/RevisionStoreRecordTest.php
new file mode 100644
index 00000000..0295e900
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/Storage/RevisionStoreRecordTest.php
@@ -0,0 +1,363 @@
+<?php
+
+namespace MediaWiki\Tests\Storage;
+
+use CommentStoreComment;
+use InvalidArgumentException;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\RevisionSlots;
+use MediaWiki\Storage\RevisionStoreRecord;
+use MediaWiki\Storage\SlotRecord;
+use MediaWiki\User\UserIdentity;
+use MediaWiki\User\UserIdentityValue;
+use MediaWikiTestCase;
+use TextContent;
+use Title;
+
+/**
+ * @covers \MediaWiki\Storage\RevisionStoreRecord
+ * @covers \MediaWiki\Storage\RevisionRecord
+ */
+class RevisionStoreRecordTest extends MediaWikiTestCase {
+
+ use RevisionRecordTests;
+
+ /**
+ * @param array $rowOverrides
+ *
+ * @return RevisionStoreRecord
+ */
+ protected function newRevision( array $rowOverrides = [] ) {
+ $title = Title::newFromText( 'Dummy' );
+ $title->resetArticleID( 17 );
+
+ $user = new UserIdentityValue( 11, 'Tester', 0 );
+ $comment = CommentStoreComment::newUnsavedComment( 'Hello World' );
+
+ $main = SlotRecord::newUnsaved( 'main', new TextContent( 'Lorem Ipsum' ) );
+ $aux = SlotRecord::newUnsaved( 'aux', new TextContent( 'Frumious Bandersnatch' ) );
+ $slots = new RevisionSlots( [ $main, $aux ] );
+
+ $row = [
+ 'rev_id' => '7',
+ 'rev_page' => strval( $title->getArticleID() ),
+ 'rev_timestamp' => '20200101000000',
+ 'rev_deleted' => 0,
+ 'rev_minor_edit' => 0,
+ 'rev_parent_id' => '5',
+ 'rev_len' => $slots->computeSize(),
+ 'rev_sha1' => $slots->computeSha1(),
+ 'page_latest' => '18',
+ ];
+
+ $row = array_merge( $row, $rowOverrides );
+
+ return new RevisionStoreRecord( $title, $user, $comment, (object)$row, $slots );
+ }
+
+ public function provideConstructor() {
+ $title = Title::newFromText( 'Dummy' );
+ $title->resetArticleID( 17 );
+
+ $user = new UserIdentityValue( 11, 'Tester', 0 );
+ $comment = CommentStoreComment::newUnsavedComment( 'Hello World' );
+
+ $main = SlotRecord::newUnsaved( 'main', new TextContent( 'Lorem Ipsum' ) );
+ $aux = SlotRecord::newUnsaved( 'aux', new TextContent( 'Frumious Bandersnatch' ) );
+ $slots = new RevisionSlots( [ $main, $aux ] );
+
+ $protoRow = [
+ 'rev_id' => '7',
+ 'rev_page' => strval( $title->getArticleID() ),
+ 'rev_timestamp' => '20200101000000',
+ 'rev_deleted' => 0,
+ 'rev_minor_edit' => 0,
+ 'rev_parent_id' => '5',
+ 'rev_len' => $slots->computeSize(),
+ 'rev_sha1' => $slots->computeSha1(),
+ 'page_latest' => '18',
+ ];
+
+ $row = $protoRow;
+ yield 'all info' => [
+ $title,
+ $user,
+ $comment,
+ (object)$row,
+ $slots,
+ 'acmewiki'
+ ];
+
+ $row = $protoRow;
+ $row['rev_minor_edit'] = '1';
+ $row['rev_deleted'] = strval( RevisionRecord::DELETED_USER );
+
+ yield 'minor deleted' => [
+ $title,
+ $user,
+ $comment,
+ (object)$row,
+ $slots
+ ];
+
+ $row = $protoRow;
+ $row['page_latest'] = $row['rev_id'];
+
+ yield 'latest' => [
+ $title,
+ $user,
+ $comment,
+ (object)$row,
+ $slots
+ ];
+
+ $row = $protoRow;
+ unset( $row['rev_parent'] );
+
+ yield 'no parent' => [
+ $title,
+ $user,
+ $comment,
+ (object)$row,
+ $slots
+ ];
+
+ $row = $protoRow;
+ $row['rev_len'] = null;
+ $row['rev_sha1'] = '';
+
+ yield 'rev_len is null, rev_sha1 is ""' => [
+ $title,
+ $user,
+ $comment,
+ (object)$row,
+ $slots
+ ];
+
+ $row = $protoRow;
+ yield 'no length, no hash' => [
+ Title::newFromText( 'DummyDoesNotExist' ),
+ $user,
+ $comment,
+ (object)$row,
+ $slots
+ ];
+ }
+
+ /**
+ * @dataProvider provideConstructor
+ *
+ * @param Title $title
+ * @param UserIdentity $user
+ * @param CommentStoreComment $comment
+ * @param object $row
+ * @param RevisionSlots $slots
+ * @param bool $wikiId
+ */
+ public function testConstructorAndGetters(
+ Title $title,
+ UserIdentity $user,
+ CommentStoreComment $comment,
+ $row,
+ RevisionSlots $slots,
+ $wikiId = false
+ ) {
+ $rec = new RevisionStoreRecord( $title, $user, $comment, $row, $slots, $wikiId );
+
+ $this->assertSame( $title, $rec->getPageAsLinkTarget(), 'getPageAsLinkTarget' );
+ $this->assertSame( $user, $rec->getUser( RevisionRecord::RAW ), 'getUser' );
+ $this->assertSame( $comment, $rec->getComment(), 'getComment' );
+
+ $this->assertSame( $slots->getSlotRoles(), $rec->getSlotRoles(), 'getSlotRoles' );
+ $this->assertSame( $wikiId, $rec->getWikiId(), 'getWikiId' );
+
+ $this->assertSame( (int)$row->rev_id, $rec->getId(), 'getId' );
+ $this->assertSame( (int)$row->rev_page, $rec->getPageId(), 'getId' );
+ $this->assertSame( $row->rev_timestamp, $rec->getTimestamp(), 'getTimestamp' );
+ $this->assertSame( (int)$row->rev_deleted, $rec->getVisibility(), 'getVisibility' );
+ $this->assertSame( (bool)$row->rev_minor_edit, $rec->isMinor(), 'getIsMinor' );
+
+ if ( isset( $row->rev_parent_id ) ) {
+ $this->assertSame( (int)$row->rev_parent_id, $rec->getParentId(), 'getParentId' );
+ } else {
+ $this->assertSame( 0, $rec->getParentId(), 'getParentId' );
+ }
+
+ if ( isset( $row->rev_len ) ) {
+ $this->assertSame( (int)$row->rev_len, $rec->getSize(), 'getSize' );
+ } else {
+ $this->assertSame( $slots->computeSize(), $rec->getSize(), 'getSize' );
+ }
+
+ if ( !empty( $row->rev_sha1 ) ) {
+ $this->assertSame( $row->rev_sha1, $rec->getSha1(), 'getSha1' );
+ } else {
+ $this->assertSame( $slots->computeSha1(), $rec->getSha1(), 'getSha1' );
+ }
+
+ if ( isset( $row->page_latest ) ) {
+ $this->assertSame(
+ (int)$row->rev_id === (int)$row->page_latest,
+ $rec->isCurrent(),
+ 'isCurrent'
+ );
+ } else {
+ $this->assertSame(
+ false,
+ $rec->isCurrent(),
+ 'isCurrent'
+ );
+ }
+ }
+
+ public function provideConstructorFailure() {
+ $title = Title::newFromText( 'Dummy' );
+ $title->resetArticleID( 17 );
+
+ $user = new UserIdentityValue( 11, 'Tester', 0 );
+
+ $comment = CommentStoreComment::newUnsavedComment( 'Hello World' );
+
+ $main = SlotRecord::newUnsaved( 'main', new TextContent( 'Lorem Ipsum' ) );
+ $aux = SlotRecord::newUnsaved( 'aux', new TextContent( 'Frumious Bandersnatch' ) );
+ $slots = new RevisionSlots( [ $main, $aux ] );
+
+ $protoRow = [
+ 'rev_id' => '7',
+ 'rev_page' => strval( $title->getArticleID() ),
+ 'rev_timestamp' => '20200101000000',
+ 'rev_deleted' => 0,
+ 'rev_minor_edit' => 0,
+ 'rev_parent_id' => '5',
+ 'rev_len' => $slots->computeSize(),
+ 'rev_sha1' => $slots->computeSha1(),
+ 'page_latest' => '18',
+ ];
+
+ yield 'not a row' => [
+ $title,
+ $user,
+ $comment,
+ 'not a row',
+ $slots,
+ 'acmewiki'
+ ];
+
+ $row = $protoRow;
+ $row['rev_timestamp'] = 'kittens';
+
+ yield 'bad timestamp' => [
+ $title,
+ $user,
+ $comment,
+ (object)$row,
+ $slots
+ ];
+
+ $row = $protoRow;
+ $row['rev_page'] = 99;
+
+ yield 'page ID mismatch' => [
+ $title,
+ $user,
+ $comment,
+ (object)$row,
+ $slots
+ ];
+
+ $row = $protoRow;
+
+ yield 'bad wiki' => [
+ $title,
+ $user,
+ $comment,
+ (object)$row,
+ $slots,
+ 12345
+ ];
+ }
+
+ /**
+ * @dataProvider provideConstructorFailure
+ *
+ * @param Title $title
+ * @param UserIdentity $user
+ * @param CommentStoreComment $comment
+ * @param object $row
+ * @param RevisionSlots $slots
+ * @param bool $wikiId
+ */
+ public function testConstructorFailure(
+ Title $title,
+ UserIdentity $user,
+ CommentStoreComment $comment,
+ $row,
+ RevisionSlots $slots,
+ $wikiId = false
+ ) {
+ $this->setExpectedException( InvalidArgumentException::class );
+ new RevisionStoreRecord( $title, $user, $comment, $row, $slots, $wikiId );
+ }
+
+ public function provideIsCurrent() {
+ yield [
+ [
+ 'rev_id' => 11,
+ 'page_latest' => 11,
+ ],
+ true,
+ ];
+ yield [
+ [
+ 'rev_id' => 11,
+ 'page_latest' => 10,
+ ],
+ false,
+ ];
+ }
+
+ /**
+ * @dataProvider provideIsCurrent
+ */
+ public function testIsCurrent( $row, $current ) {
+ $rev = $this->newRevision( $row );
+
+ $this->assertSame( $current, $rev->isCurrent(), 'isCurrent()' );
+ }
+
+ public function provideGetSlot_audience_latest() {
+ return $this->provideAudienceCheckData( RevisionRecord::DELETED_TEXT );
+ }
+
+ /**
+ * @dataProvider provideGetSlot_audience_latest
+ */
+ public function testGetSlot_audience_latest( $visibility, $groups, $userCan, $publicCan ) {
+ $this->forceStandardPermissions();
+
+ $user = $this->getTestUser( $groups )->getUser();
+ $rev = $this->newRevision(
+ [
+ 'rev_deleted' => $visibility,
+ 'rev_id' => 11,
+ 'page_latest' => 11, // revision is current
+ ]
+ );
+
+ // NOTE: slot meta-data is never suppressed, just the content is!
+ $this->assertNotNull( $rev->getSlot( 'main', RevisionRecord::RAW ), 'raw can' );
+ $this->assertNotNull( $rev->getSlot( 'main', RevisionRecord::FOR_PUBLIC ), 'public can' );
+
+ $this->assertNotNull(
+ $rev->getSlot( 'main', RevisionRecord::FOR_THIS_USER, $user ),
+ 'user can'
+ );
+
+ $rev->getSlot( 'main', RevisionRecord::RAW )->getContent();
+ // NOTE: the content of the current revision is never suppressed!
+ // Check that getContent() doesn't throw SuppressedDataException
+ $rev->getSlot( 'main', RevisionRecord::FOR_PUBLIC )->getContent();
+ $rev->getSlot( 'main', RevisionRecord::FOR_THIS_USER, $user )->getContent();
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/Storage/RevisionStoreTest.php b/www/wiki/tests/phpunit/includes/Storage/RevisionStoreTest.php
new file mode 100644
index 00000000..0bce572d
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/Storage/RevisionStoreTest.php
@@ -0,0 +1,690 @@
+<?php
+
+namespace MediaWiki\Tests\Storage;
+
+use HashBagOStuff;
+use Language;
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\RevisionAccessException;
+use MediaWiki\Storage\RevisionStore;
+use MediaWiki\Storage\SqlBlobStore;
+use MediaWikiTestCase;
+use Title;
+use WANObjectCache;
+use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\LoadBalancer;
+
+class RevisionStoreTest extends MediaWikiTestCase {
+
+ /**
+ * @param LoadBalancer $loadBalancer
+ * @param SqlBlobStore $blobStore
+ * @param WANObjectCache $WANObjectCache
+ *
+ * @return RevisionStore
+ */
+ private function getRevisionStore(
+ $loadBalancer = null,
+ $blobStore = null,
+ $WANObjectCache = null
+ ) {
+ return new RevisionStore(
+ $loadBalancer ? $loadBalancer : $this->getMockLoadBalancer(),
+ $blobStore ? $blobStore : $this->getMockSqlBlobStore(),
+ $WANObjectCache ? $WANObjectCache : $this->getHashWANObjectCache(),
+ MediaWikiServices::getInstance()->getCommentStore(),
+ MediaWikiServices::getInstance()->getActorMigration()
+ );
+ }
+
+ /**
+ * @return \PHPUnit_Framework_MockObject_MockObject|LoadBalancer
+ */
+ private function getMockLoadBalancer() {
+ return $this->getMockBuilder( LoadBalancer::class )
+ ->disableOriginalConstructor()->getMock();
+ }
+
+ /**
+ * @return \PHPUnit_Framework_MockObject_MockObject|Database
+ */
+ private function getMockDatabase() {
+ return $this->getMockBuilder( Database::class )
+ ->disableOriginalConstructor()->getMock();
+ }
+
+ /**
+ * @return \PHPUnit_Framework_MockObject_MockObject|SqlBlobStore
+ */
+ private function getMockSqlBlobStore() {
+ return $this->getMockBuilder( SqlBlobStore::class )
+ ->disableOriginalConstructor()->getMock();
+ }
+
+ private function getHashWANObjectCache() {
+ return new WANObjectCache( [ 'cache' => new \HashBagOStuff() ] );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getContentHandlerUseDB
+ * @covers \MediaWiki\Storage\RevisionStore::setContentHandlerUseDB
+ */
+ public function testGetSetContentHandlerDb() {
+ $store = $this->getRevisionStore();
+ $this->assertTrue( $store->getContentHandlerUseDB() );
+ $store->setContentHandlerUseDB( false );
+ $this->assertFalse( $store->getContentHandlerUseDB() );
+ $store->setContentHandlerUseDB( true );
+ $this->assertTrue( $store->getContentHandlerUseDB() );
+ }
+
+ private function getDefaultQueryFields() {
+ return [
+ 'rev_id',
+ 'rev_page',
+ 'rev_text_id',
+ 'rev_timestamp',
+ 'rev_minor_edit',
+ 'rev_deleted',
+ 'rev_len',
+ 'rev_parent_id',
+ 'rev_sha1',
+ ];
+ }
+
+ private function getCommentQueryFields() {
+ return [
+ 'rev_comment_text' => 'rev_comment',
+ 'rev_comment_data' => 'NULL',
+ 'rev_comment_cid' => 'NULL',
+ ];
+ }
+
+ private function getActorQueryFields() {
+ return [
+ 'rev_user' => 'rev_user',
+ 'rev_user_text' => 'rev_user_text',
+ 'rev_actor' => 'NULL',
+ ];
+ }
+
+ private function getContentHandlerQueryFields() {
+ return [
+ 'rev_content_format',
+ 'rev_content_model',
+ ];
+ }
+
+ public function provideGetQueryInfo() {
+ yield [
+ true,
+ [],
+ [
+ 'tables' => [ 'revision' ],
+ 'fields' => array_merge(
+ $this->getDefaultQueryFields(),
+ $this->getCommentQueryFields(),
+ $this->getActorQueryFields(),
+ $this->getContentHandlerQueryFields()
+ ),
+ 'joins' => [],
+ ]
+ ];
+ yield [
+ false,
+ [],
+ [
+ 'tables' => [ 'revision' ],
+ 'fields' => array_merge(
+ $this->getDefaultQueryFields(),
+ $this->getCommentQueryFields(),
+ $this->getActorQueryFields()
+ ),
+ 'joins' => [],
+ ]
+ ];
+ yield [
+ false,
+ [ 'page' ],
+ [
+ 'tables' => [ 'revision', 'page' ],
+ 'fields' => array_merge(
+ $this->getDefaultQueryFields(),
+ $this->getCommentQueryFields(),
+ $this->getActorQueryFields(),
+ [
+ 'page_namespace',
+ 'page_title',
+ 'page_id',
+ 'page_latest',
+ 'page_is_redirect',
+ 'page_len',
+ ]
+ ),
+ 'joins' => [
+ 'page' => [ 'INNER JOIN', [ 'page_id = rev_page' ] ],
+ ],
+ ]
+ ];
+ yield [
+ false,
+ [ 'user' ],
+ [
+ 'tables' => [ 'revision', 'user' ],
+ 'fields' => array_merge(
+ $this->getDefaultQueryFields(),
+ $this->getCommentQueryFields(),
+ $this->getActorQueryFields(),
+ [
+ 'user_name',
+ ]
+ ),
+ 'joins' => [
+ 'user' => [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
+ ],
+ ]
+ ];
+ yield [
+ false,
+ [ 'text' ],
+ [
+ 'tables' => [ 'revision', 'text' ],
+ 'fields' => array_merge(
+ $this->getDefaultQueryFields(),
+ $this->getCommentQueryFields(),
+ $this->getActorQueryFields(),
+ [
+ 'old_text',
+ 'old_flags',
+ ]
+ ),
+ 'joins' => [
+ 'text' => [ 'INNER JOIN', [ 'rev_text_id=old_id' ] ],
+ ],
+ ]
+ ];
+ yield [
+ true,
+ [ 'page', 'user', 'text' ],
+ [
+ 'tables' => [ 'revision', 'page', 'user', 'text' ],
+ 'fields' => array_merge(
+ $this->getDefaultQueryFields(),
+ $this->getCommentQueryFields(),
+ $this->getActorQueryFields(),
+ $this->getContentHandlerQueryFields(),
+ [
+ 'page_namespace',
+ 'page_title',
+ 'page_id',
+ 'page_latest',
+ 'page_is_redirect',
+ 'page_len',
+ 'user_name',
+ 'old_text',
+ 'old_flags',
+ ]
+ ),
+ 'joins' => [
+ 'page' => [ 'INNER JOIN', [ 'page_id = rev_page' ] ],
+ 'user' => [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
+ 'text' => [ 'INNER JOIN', [ 'rev_text_id=old_id' ] ],
+ ],
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetQueryInfo
+ * @covers \MediaWiki\Storage\RevisionStore::getQueryInfo
+ */
+ public function testGetQueryInfo( $contentHandlerUseDb, $options, $expected ) {
+ $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->overrideMwServices();
+ $store = $this->getRevisionStore();
+ $store->setContentHandlerUseDB( $contentHandlerUseDb );
+ $this->assertEquals( $expected, $store->getQueryInfo( $options ) );
+ }
+
+ private function getDefaultArchiveFields() {
+ return [
+ 'ar_id',
+ 'ar_page_id',
+ 'ar_namespace',
+ 'ar_title',
+ 'ar_rev_id',
+ 'ar_text_id',
+ 'ar_timestamp',
+ 'ar_minor_edit',
+ 'ar_deleted',
+ 'ar_len',
+ 'ar_parent_id',
+ 'ar_sha1',
+ ];
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getArchiveQueryInfo
+ */
+ public function testGetArchiveQueryInfo_contentHandlerDb() {
+ $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->overrideMwServices();
+ $store = $this->getRevisionStore();
+ $store->setContentHandlerUseDB( true );
+ $this->assertEquals(
+ [
+ 'tables' => [
+ 'archive'
+ ],
+ 'fields' => array_merge(
+ $this->getDefaultArchiveFields(),
+ [
+ 'ar_comment_text' => 'ar_comment',
+ 'ar_comment_data' => 'NULL',
+ 'ar_comment_cid' => 'NULL',
+ 'ar_user_text' => 'ar_user_text',
+ 'ar_user' => 'ar_user',
+ 'ar_actor' => 'NULL',
+ 'ar_content_format',
+ 'ar_content_model',
+ ]
+ ),
+ 'joins' => [],
+ ],
+ $store->getArchiveQueryInfo()
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getArchiveQueryInfo
+ */
+ public function testGetArchiveQueryInfo_noContentHandlerDb() {
+ $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->overrideMwServices();
+ $store = $this->getRevisionStore();
+ $store->setContentHandlerUseDB( false );
+ $this->assertEquals(
+ [
+ 'tables' => [
+ 'archive'
+ ],
+ 'fields' => array_merge(
+ $this->getDefaultArchiveFields(),
+ [
+ 'ar_comment_text' => 'ar_comment',
+ 'ar_comment_data' => 'NULL',
+ 'ar_comment_cid' => 'NULL',
+ 'ar_user_text' => 'ar_user_text',
+ 'ar_user' => 'ar_user',
+ 'ar_actor' => 'NULL',
+ ]
+ ),
+ 'joins' => [],
+ ],
+ $store->getArchiveQueryInfo()
+ );
+ }
+
+ public function testGetTitle_successFromPageId() {
+ $mockLoadBalancer = $this->getMockLoadBalancer();
+ // Title calls wfGetDB() so we have to set the main service
+ $this->setService( 'DBLoadBalancer', $mockLoadBalancer );
+
+ $db = $this->getMockDatabase();
+ // Title calls wfGetDB() which uses a regular Connection
+ $mockLoadBalancer->expects( $this->atLeastOnce() )
+ ->method( 'getConnection' )
+ ->willReturn( $db );
+
+ // First call to Title::newFromID, faking no result (db lag?)
+ $db->expects( $this->at( 0 ) )
+ ->method( 'selectRow' )
+ ->with(
+ 'page',
+ $this->anything(),
+ [ 'page_id' => 1 ]
+ )
+ ->willReturn( (object)[
+ 'page_namespace' => '1',
+ 'page_title' => 'Food',
+ ] );
+
+ $store = $this->getRevisionStore( $mockLoadBalancer );
+ $title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
+
+ $this->assertSame( 1, $title->getNamespace() );
+ $this->assertSame( 'Food', $title->getDBkey() );
+ }
+
+ public function testGetTitle_successFromPageIdOnFallback() {
+ $mockLoadBalancer = $this->getMockLoadBalancer();
+ // Title calls wfGetDB() so we have to set the main service
+ $this->setService( 'DBLoadBalancer', $mockLoadBalancer );
+
+ $db = $this->getMockDatabase();
+ // Title calls wfGetDB() which uses a regular Connection
+ // Assert that the first call uses a REPLICA and the second falls back to master
+ $mockLoadBalancer->expects( $this->exactly( 2 ) )
+ ->method( 'getConnection' )
+ ->willReturn( $db );
+ // RevisionStore getTitle uses a ConnectionRef
+ $mockLoadBalancer->expects( $this->atLeastOnce() )
+ ->method( 'getConnectionRef' )
+ ->willReturn( $db );
+
+ // First call to Title::newFromID, faking no result (db lag?)
+ $db->expects( $this->at( 0 ) )
+ ->method( 'selectRow' )
+ ->with(
+ 'page',
+ $this->anything(),
+ [ 'page_id' => 1 ]
+ )
+ ->willReturn( false );
+
+ // First select using rev_id, faking no result (db lag?)
+ $db->expects( $this->at( 1 ) )
+ ->method( 'selectRow' )
+ ->with(
+ [ 'revision', 'page' ],
+ $this->anything(),
+ [ 'rev_id' => 2 ]
+ )
+ ->willReturn( false );
+
+ // Second call to Title::newFromID, no result
+ $db->expects( $this->at( 2 ) )
+ ->method( 'selectRow' )
+ ->with(
+ 'page',
+ $this->anything(),
+ [ 'page_id' => 1 ]
+ )
+ ->willReturn( (object)[
+ 'page_namespace' => '2',
+ 'page_title' => 'Foodey',
+ ] );
+
+ $store = $this->getRevisionStore( $mockLoadBalancer );
+ $title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
+
+ $this->assertSame( 2, $title->getNamespace() );
+ $this->assertSame( 'Foodey', $title->getDBkey() );
+ }
+
+ public function testGetTitle_successFromRevId() {
+ $mockLoadBalancer = $this->getMockLoadBalancer();
+ // Title calls wfGetDB() so we have to set the main service
+ $this->setService( 'DBLoadBalancer', $mockLoadBalancer );
+
+ $db = $this->getMockDatabase();
+ // Title calls wfGetDB() which uses a regular Connection
+ $mockLoadBalancer->expects( $this->atLeastOnce() )
+ ->method( 'getConnection' )
+ ->willReturn( $db );
+ // RevisionStore getTitle uses a ConnectionRef
+ $mockLoadBalancer->expects( $this->atLeastOnce() )
+ ->method( 'getConnectionRef' )
+ ->willReturn( $db );
+
+ // First call to Title::newFromID, faking no result (db lag?)
+ $db->expects( $this->at( 0 ) )
+ ->method( 'selectRow' )
+ ->with(
+ 'page',
+ $this->anything(),
+ [ 'page_id' => 1 ]
+ )
+ ->willReturn( false );
+
+ // First select using rev_id, faking no result (db lag?)
+ $db->expects( $this->at( 1 ) )
+ ->method( 'selectRow' )
+ ->with(
+ [ 'revision', 'page' ],
+ $this->anything(),
+ [ 'rev_id' => 2 ]
+ )
+ ->willReturn( (object)[
+ 'page_namespace' => '1',
+ 'page_title' => 'Food2',
+ ] );
+
+ $store = $this->getRevisionStore( $mockLoadBalancer );
+ $title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
+
+ $this->assertSame( 1, $title->getNamespace() );
+ $this->assertSame( 'Food2', $title->getDBkey() );
+ }
+
+ public function testGetTitle_successFromRevIdOnFallback() {
+ $mockLoadBalancer = $this->getMockLoadBalancer();
+ // Title calls wfGetDB() so we have to set the main service
+ $this->setService( 'DBLoadBalancer', $mockLoadBalancer );
+
+ $db = $this->getMockDatabase();
+ // Title calls wfGetDB() which uses a regular Connection
+ // Assert that the first call uses a REPLICA and the second falls back to master
+ $mockLoadBalancer->expects( $this->exactly( 2 ) )
+ ->method( 'getConnection' )
+ ->willReturn( $db );
+ // RevisionStore getTitle uses a ConnectionRef
+ $mockLoadBalancer->expects( $this->atLeastOnce() )
+ ->method( 'getConnectionRef' )
+ ->willReturn( $db );
+
+ // First call to Title::newFromID, faking no result (db lag?)
+ $db->expects( $this->at( 0 ) )
+ ->method( 'selectRow' )
+ ->with(
+ 'page',
+ $this->anything(),
+ [ 'page_id' => 1 ]
+ )
+ ->willReturn( false );
+
+ // First select using rev_id, faking no result (db lag?)
+ $db->expects( $this->at( 1 ) )
+ ->method( 'selectRow' )
+ ->with(
+ [ 'revision', 'page' ],
+ $this->anything(),
+ [ 'rev_id' => 2 ]
+ )
+ ->willReturn( false );
+
+ // Second call to Title::newFromID, no result
+ $db->expects( $this->at( 2 ) )
+ ->method( 'selectRow' )
+ ->with(
+ 'page',
+ $this->anything(),
+ [ 'page_id' => 1 ]
+ )
+ ->willReturn( false );
+
+ // Second select using rev_id, result
+ $db->expects( $this->at( 3 ) )
+ ->method( 'selectRow' )
+ ->with(
+ [ 'revision', 'page' ],
+ $this->anything(),
+ [ 'rev_id' => 2 ]
+ )
+ ->willReturn( (object)[
+ 'page_namespace' => '2',
+ 'page_title' => 'Foodey',
+ ] );
+
+ $store = $this->getRevisionStore( $mockLoadBalancer );
+ $title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
+
+ $this->assertSame( 2, $title->getNamespace() );
+ $this->assertSame( 'Foodey', $title->getDBkey() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getTitle
+ */
+ public function testGetTitle_correctFallbackAndthrowsExceptionAfterFallbacks() {
+ $mockLoadBalancer = $this->getMockLoadBalancer();
+ // Title calls wfGetDB() so we have to set the main service
+ $this->setService( 'DBLoadBalancer', $mockLoadBalancer );
+
+ $db = $this->getMockDatabase();
+ // Title calls wfGetDB() which uses a regular Connection
+ // Assert that the first call uses a REPLICA and the second falls back to master
+
+ // RevisionStore getTitle uses getConnectionRef
+ // Title::newFromID uses getConnection
+ foreach ( [ 'getConnection', 'getConnectionRef' ] as $method ) {
+ $mockLoadBalancer->expects( $this->exactly( 2 ) )
+ ->method( $method )
+ ->willReturnCallback( function ( $masterOrReplica ) use ( $db ) {
+ static $callCounter = 0;
+ $callCounter++;
+ // The first call should be to a REPLICA, and the second a MASTER.
+ if ( $callCounter === 1 ) {
+ $this->assertSame( DB_REPLICA, $masterOrReplica );
+ } elseif ( $callCounter === 2 ) {
+ $this->assertSame( DB_MASTER, $masterOrReplica );
+ }
+ return $db;
+ } );
+ }
+ // First and third call to Title::newFromID, faking no result
+ foreach ( [ 0, 2 ] as $counter ) {
+ $db->expects( $this->at( $counter ) )
+ ->method( 'selectRow' )
+ ->with(
+ 'page',
+ $this->anything(),
+ [ 'page_id' => 1 ]
+ )
+ ->willReturn( false );
+ }
+
+ foreach ( [ 1, 3 ] as $counter ) {
+ $db->expects( $this->at( $counter ) )
+ ->method( 'selectRow' )
+ ->with(
+ [ 'revision', 'page' ],
+ $this->anything(),
+ [ 'rev_id' => 2 ]
+ )
+ ->willReturn( false );
+ }
+
+ $store = $this->getRevisionStore( $mockLoadBalancer );
+
+ $this->setExpectedException( RevisionAccessException::class );
+ $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
+ }
+
+ public function provideNewRevisionFromRow_legacyEncoding_applied() {
+ yield 'windows-1252, old_flags is empty' => [
+ 'windows-1252',
+ 'en',
+ [
+ 'old_flags' => '',
+ 'old_text' => "S\xF6me Content",
+ ],
+ 'Söme Content'
+ ];
+
+ yield 'windows-1252, old_flags is null' => [
+ 'windows-1252',
+ 'en',
+ [
+ 'old_flags' => null,
+ 'old_text' => "S\xF6me Content",
+ ],
+ 'Söme Content'
+ ];
+ }
+
+ /**
+ * @dataProvider provideNewRevisionFromRow_legacyEncoding_applied
+ *
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29
+ */
+ public function testNewRevisionFromRow_legacyEncoding_applied( $encoding, $locale, $row, $text ) {
+ $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
+
+ $blobStore = new SqlBlobStore( wfGetLB(), $cache );
+ $blobStore->setLegacyEncoding( $encoding, Language::factory( $locale ) );
+
+ $store = $this->getRevisionStore( wfGetLB(), $blobStore, $cache );
+
+ $record = $store->newRevisionFromRow(
+ $this->makeRow( $row ),
+ 0,
+ Title::newFromText( __METHOD__ . '-UTPage' )
+ );
+
+ $this->assertSame( $text, $record->getContent( 'main' )->serialize() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29
+ */
+ public function testNewRevisionFromRow_legacyEncoding_ignored() {
+ $row = [
+ 'old_flags' => 'utf-8',
+ 'old_text' => 'Söme Content',
+ ];
+
+ $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
+
+ $blobStore = new SqlBlobStore( wfGetLB(), $cache );
+ $blobStore->setLegacyEncoding( 'windows-1252', Language::factory( 'en' ) );
+
+ $store = $this->getRevisionStore( wfGetLB(), $blobStore, $cache );
+
+ $record = $store->newRevisionFromRow(
+ $this->makeRow( $row ),
+ 0,
+ Title::newFromText( __METHOD__ . '-UTPage' )
+ );
+ $this->assertSame( 'Söme Content', $record->getContent( 'main' )->serialize() );
+ }
+
+ private function makeRow( array $array ) {
+ $row = $array + [
+ 'rev_id' => 7,
+ 'rev_page' => 5,
+ 'rev_text_id' => 11,
+ 'rev_timestamp' => '20110101000000',
+ 'rev_user_text' => 'Tester',
+ 'rev_user' => 17,
+ 'rev_minor_edit' => 0,
+ 'rev_deleted' => 0,
+ 'rev_len' => 100,
+ 'rev_parent_id' => 0,
+ 'rev_sha1' => 'deadbeef',
+ 'rev_comment_text' => 'Testing',
+ 'rev_comment_data' => '{}',
+ 'rev_comment_cid' => 111,
+ 'rev_content_format' => CONTENT_FORMAT_TEXT,
+ 'rev_content_model' => CONTENT_MODEL_TEXT,
+ 'page_namespace' => 0,
+ 'page_title' => 'TEST',
+ 'page_id' => 5,
+ 'page_latest' => 7,
+ 'page_is_redirect' => 0,
+ 'page_len' => 100,
+ 'user_name' => 'Tester',
+ 'old_is' => 13,
+ 'old_text' => 'Hello World',
+ 'old_flags' => 'utf-8',
+ ];
+
+ return (object)$row;
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/Storage/SlotRecordTest.php b/www/wiki/tests/phpunit/includes/Storage/SlotRecordTest.php
new file mode 100644
index 00000000..8f26494d
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/Storage/SlotRecordTest.php
@@ -0,0 +1,298 @@
+<?php
+
+namespace MediaWiki\Tests\Storage;
+
+use InvalidArgumentException;
+use LogicException;
+use MediaWiki\Storage\IncompleteRevisionException;
+use MediaWiki\Storage\SlotRecord;
+use MediaWiki\Storage\SuppressedDataException;
+use MediaWikiTestCase;
+use WikitextContent;
+
+/**
+ * @covers \MediaWiki\Storage\SlotRecord
+ */
+class SlotRecordTest extends MediaWikiTestCase {
+
+ private function makeRow( $data = [] ) {
+ $data = $data + [
+ 'slot_id' => 1234,
+ 'slot_content_id' => 33,
+ 'content_size' => '5',
+ 'content_sha1' => 'someHash',
+ 'content_address' => 'tt:456',
+ 'model_name' => CONTENT_MODEL_WIKITEXT,
+ 'format_name' => CONTENT_FORMAT_WIKITEXT,
+ 'slot_revision_id' => '2',
+ 'slot_origin' => '1',
+ 'role_name' => 'myRole',
+ ];
+ return (object)$data;
+ }
+
+ public function testCompleteConstruction() {
+ $row = $this->makeRow();
+ $record = new SlotRecord( $row, new WikitextContent( 'A' ) );
+
+ $this->assertTrue( $record->hasAddress() );
+ $this->assertTrue( $record->hasRevision() );
+ $this->assertTrue( $record->isInherited() );
+ $this->assertSame( 'A', $record->getContent()->getNativeData() );
+ $this->assertSame( 5, $record->getSize() );
+ $this->assertSame( 'someHash', $record->getSha1() );
+ $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+ $this->assertSame( 2, $record->getRevision() );
+ $this->assertSame( 1, $record->getOrigin() );
+ $this->assertSame( 'tt:456', $record->getAddress() );
+ $this->assertSame( 33, $record->getContentId() );
+ $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
+ $this->assertSame( 'myRole', $record->getRole() );
+ }
+
+ public function testConstructionDeferred() {
+ $row = $this->makeRow( [
+ 'content_size' => null, // to be computed
+ 'content_sha1' => null, // to be computed
+ 'format_name' => function () {
+ return CONTENT_FORMAT_WIKITEXT;
+ },
+ 'slot_revision_id' => '2',
+ 'slot_origin' => '2',
+ ] );
+
+ $content = function () {
+ return new WikitextContent( 'A' );
+ };
+
+ $record = new SlotRecord( $row, $content );
+
+ $this->assertTrue( $record->hasAddress() );
+ $this->assertTrue( $record->hasRevision() );
+ $this->assertFalse( $record->isInherited() );
+ $this->assertSame( 'A', $record->getContent()->getNativeData() );
+ $this->assertSame( 1, $record->getSize() );
+ $this->assertNotNull( $record->getSha1() );
+ $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+ $this->assertSame( 2, $record->getRevision() );
+ $this->assertSame( 2, $record->getRevision() );
+ $this->assertSame( 'tt:456', $record->getAddress() );
+ $this->assertSame( 33, $record->getContentId() );
+ $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
+ $this->assertSame( 'myRole', $record->getRole() );
+ }
+
+ public function testNewUnsaved() {
+ $record = SlotRecord::newUnsaved( 'myRole', new WikitextContent( 'A' ) );
+
+ $this->assertFalse( $record->hasAddress() );
+ $this->assertFalse( $record->hasRevision() );
+ $this->assertFalse( $record->isInherited() );
+ $this->assertSame( 'A', $record->getContent()->getNativeData() );
+ $this->assertSame( 1, $record->getSize() );
+ $this->assertNotNull( $record->getSha1() );
+ $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+ $this->assertSame( 'myRole', $record->getRole() );
+ }
+
+ public function provideInvalidConstruction() {
+ yield 'both null' => [ null, null ];
+ yield 'null row' => [ null, new WikitextContent( 'A' ) ];
+ yield 'array row' => [ [], new WikitextContent( 'A' ) ];
+ yield 'empty row' => [ (object)[], new WikitextContent( 'A' ) ];
+ yield 'null content' => [ (object)[], null ];
+ }
+
+ /**
+ * @dataProvider provideInvalidConstruction
+ */
+ public function testInvalidConstruction( $row, $content ) {
+ $this->setExpectedException( InvalidArgumentException::class );
+ new SlotRecord( $row, $content );
+ }
+
+ public function testGetContentId_fails() {
+ $record = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ $this->setExpectedException( IncompleteRevisionException::class );
+
+ $record->getContentId();
+ }
+
+ public function testGetAddress_fails() {
+ $record = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ $this->setExpectedException( IncompleteRevisionException::class );
+
+ $record->getAddress();
+ }
+
+ public function provideIncomplete() {
+ $unsaved = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ yield 'unsaved' => [ $unsaved ];
+
+ $parent = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
+ $inherited = SlotRecord::newInherited( $parent );
+ yield 'inherited' => [ $inherited ];
+ }
+
+ /**
+ * @dataProvider provideIncomplete
+ */
+ public function testGetRevision_fails( SlotRecord $record ) {
+ $record = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ $this->setExpectedException( IncompleteRevisionException::class );
+
+ $record->getRevision();
+ }
+
+ /**
+ * @dataProvider provideIncomplete
+ */
+ public function testGetOrigin_fails( SlotRecord $record ) {
+ $record = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ $this->setExpectedException( IncompleteRevisionException::class );
+
+ $record->getOrigin();
+ }
+
+ public function provideHashStability() {
+ yield [ '', 'phoiac9h4m842xq45sp7s6u21eteeq1' ];
+ yield [ 'Lorem ipsum', 'hcr5u40uxr81d3nx89nvwzclfz6r9c5' ];
+ }
+
+ /**
+ * @dataProvider provideHashStability
+ */
+ public function testHashStability( $text, $hash ) {
+ // Changing the output of the hash function will break things horribly!
+
+ $this->assertSame( $hash, SlotRecord::base36Sha1( $text ) );
+
+ $record = SlotRecord::newUnsaved( 'main', new WikitextContent( $text ) );
+ $this->assertSame( $hash, $record->getSha1() );
+ }
+
+ public function testNewWithSuppressedContent() {
+ $input = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
+ $output = SlotRecord::newWithSuppressedContent( $input );
+
+ $this->setExpectedException( SuppressedDataException::class );
+ $output->getContent();
+ }
+
+ public function testNewInherited() {
+ $row = $this->makeRow( [ 'slot_revision_id' => 7, 'slot_origin' => 7 ] );
+ $parent = new SlotRecord( $row, new WikitextContent( 'A' ) );
+
+ // This would happen while doing an edit, before saving revision meta-data.
+ $inherited = SlotRecord::newInherited( $parent );
+
+ $this->assertSame( $parent->getContentId(), $inherited->getContentId() );
+ $this->assertSame( $parent->getAddress(), $inherited->getAddress() );
+ $this->assertSame( $parent->getContent(), $inherited->getContent() );
+ $this->assertTrue( $inherited->isInherited() );
+ $this->assertFalse( $inherited->hasRevision() );
+
+ // make sure we didn't mess with the internal state of $parent
+ $this->assertFalse( $parent->isInherited() );
+ $this->assertSame( 7, $parent->getRevision() );
+
+ // This would happen while doing an edit, after saving the revision meta-data
+ // and content meta-data.
+ $saved = SlotRecord::newSaved(
+ 10,
+ $inherited->getContentId(),
+ $inherited->getAddress(),
+ $inherited
+ );
+ $this->assertSame( $parent->getContentId(), $saved->getContentId() );
+ $this->assertSame( $parent->getAddress(), $saved->getAddress() );
+ $this->assertSame( $parent->getContent(), $saved->getContent() );
+ $this->assertTrue( $saved->isInherited() );
+ $this->assertTrue( $saved->hasRevision() );
+ $this->assertSame( 10, $saved->getRevision() );
+
+ // make sure we didn't mess with the internal state of $parent or $inherited
+ $this->assertSame( 7, $parent->getRevision() );
+ $this->assertFalse( $inherited->hasRevision() );
+ }
+
+ public function testNewSaved() {
+ // This would happen while doing an edit, before saving revision meta-data.
+ $unsaved = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+
+ // This would happen while doing an edit, after saving the revision meta-data
+ // and content meta-data.
+ $saved = SlotRecord::newSaved( 10, 20, 'theNewAddress', $unsaved );
+ $this->assertFalse( $saved->isInherited() );
+ $this->assertTrue( $saved->hasRevision() );
+ $this->assertTrue( $saved->hasAddress() );
+ $this->assertSame( 'theNewAddress', $saved->getAddress() );
+ $this->assertSame( 20, $saved->getContentId() );
+ $this->assertSame( 'A', $saved->getContent()->getNativeData() );
+ $this->assertSame( 10, $saved->getRevision() );
+ $this->assertSame( 10, $saved->getOrigin() );
+
+ // make sure we didn't mess with the internal state of $unsaved
+ $this->assertFalse( $unsaved->hasAddress() );
+ $this->assertFalse( $unsaved->hasRevision() );
+ }
+
+ public function provideNewSaved_LogicException() {
+ $freshRow = $this->makeRow( [
+ 'content_id' => 10,
+ 'content_address' => 'address:1',
+ 'slot_origin' => 1,
+ 'slot_revision_id' => 1,
+ ] );
+
+ $freshSlot = new SlotRecord( $freshRow, new WikitextContent( 'A' ) );
+ yield 'mismatching address' => [ 1, 10, 'address:BAD', $freshSlot ];
+ yield 'mismatching revision' => [ 5, 10, 'address:1', $freshSlot ];
+ yield 'mismatching content ID' => [ 1, 17, 'address:1', $freshSlot ];
+
+ $inheritedRow = $this->makeRow( [
+ 'content_id' => null,
+ 'content_address' => null,
+ 'slot_origin' => 0,
+ 'slot_revision_id' => 1,
+ ] );
+
+ $inheritedSlot = new SlotRecord( $inheritedRow, new WikitextContent( 'A' ) );
+ yield 'inherited, but no address' => [ 1, 10, 'address:2', $inheritedSlot ];
+ }
+
+ /**
+ * @dataProvider provideNewSaved_LogicException
+ */
+ public function testNewSaved_LogicException(
+ $revisionId,
+ $contentId,
+ $contentAddress,
+ SlotRecord $protoSlot
+ ) {
+ $this->setExpectedException( LogicException::class );
+ SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
+ }
+
+ public function provideNewSaved_InvalidArgumentException() {
+ $unsaved = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+
+ yield 'bad revision id' => [ 'xyzzy', 5, 'address', $unsaved ];
+ yield 'bad content id' => [ 7, 'xyzzy', 'address', $unsaved ];
+ yield 'bad content address' => [ 7, 5, 77, $unsaved ];
+ }
+
+ /**
+ * @dataProvider provideNewSaved_InvalidArgumentException
+ */
+ public function testNewSaved_InvalidArgumentException(
+ $revisionId,
+ $contentId,
+ $contentAddress,
+ SlotRecord $protoSlot
+ ) {
+ $this->setExpectedException( InvalidArgumentException::class );
+ SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/Storage/SqlBlobStoreTest.php b/www/wiki/tests/phpunit/includes/Storage/SqlBlobStoreTest.php
new file mode 100644
index 00000000..dbbef11e
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/Storage/SqlBlobStoreTest.php
@@ -0,0 +1,241 @@
+<?php
+
+namespace MediaWiki\Tests\Storage;
+
+use Language;
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\SqlBlobStore;
+use MediaWikiTestCase;
+use stdClass;
+use TitleValue;
+
+/**
+ * @covers \MediaWiki\Storage\SqlBlobStore
+ * @group Database
+ */
+class SqlBlobStoreTest extends MediaWikiTestCase {
+
+ /**
+ * @return SqlBlobStore
+ */
+ public function getBlobStore( $legacyEncoding = false, $compressRevisions = false ) {
+ $services = MediaWikiServices::getInstance();
+
+ $store = new SqlBlobStore(
+ $services->getDBLoadBalancer(),
+ $services->getMainWANObjectCache()
+ );
+
+ if ( $compressRevisions ) {
+ $store->setCompressBlobs( $compressRevisions );
+ }
+ if ( $legacyEncoding ) {
+ $store->setLegacyEncoding( $legacyEncoding, Language::factory( 'en' ) );
+ }
+
+ return $store;
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\SqlBlobStore::getCompressBlobs()
+ * @covers \MediaWiki\Storage\SqlBlobStore::setCompressBlobs()
+ */
+ public function testGetSetCompressRevisions() {
+ $store = $this->getBlobStore();
+ $this->assertFalse( $store->getCompressBlobs() );
+ $store->setCompressBlobs( true );
+ $this->assertTrue( $store->getCompressBlobs() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\SqlBlobStore::getLegacyEncoding()
+ * @covers \MediaWiki\Storage\SqlBlobStore::getLegacyEncodingConversionLang()
+ * @covers \MediaWiki\Storage\SqlBlobStore::setLegacyEncoding()
+ */
+ public function testGetSetLegacyEncoding() {
+ $store = $this->getBlobStore();
+ $this->assertFalse( $store->getLegacyEncoding() );
+ $this->assertNull( $store->getLegacyEncodingConversionLang() );
+ $en = Language::factory( 'en' );
+ $store->setLegacyEncoding( 'foo', $en );
+ $this->assertSame( 'foo', $store->getLegacyEncoding() );
+ $this->assertSame( $en, $store->getLegacyEncodingConversionLang() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\SqlBlobStore::getCacheExpiry()
+ * @covers \MediaWiki\Storage\SqlBlobStore::setCacheExpiry()
+ */
+ public function testGetSetCacheExpiry() {
+ $store = $this->getBlobStore();
+ $this->assertSame( 604800, $store->getCacheExpiry() );
+ $store->setCacheExpiry( 12 );
+ $this->assertSame( 12, $store->getCacheExpiry() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\SqlBlobStore::getUseExternalStore()
+ * @covers \MediaWiki\Storage\SqlBlobStore::setUseExternalStore()
+ */
+ public function testGetSetUseExternalStore() {
+ $store = $this->getBlobStore();
+ $this->assertFalse( $store->getUseExternalStore() );
+ $store->setUseExternalStore( true );
+ $this->assertTrue( $store->getUseExternalStore() );
+ }
+
+ public function provideDecompress() {
+ yield '(no legacy encoding), false in false out' => [ false, false, [], false ];
+ yield '(no legacy encoding), empty in empty out' => [ false, '', [], '' ];
+ yield '(no legacy encoding), empty in empty out' => [ false, 'A', [], 'A' ];
+ yield '(no legacy encoding), string in with gzip flag returns string' => [
+ // gzip string below generated with gzdeflate( 'AAAABBAAA' )
+ false, "sttttr\002\022\000", [ 'gzip' ], 'AAAABBAAA',
+ ];
+ yield '(no legacy encoding), string in with object flag returns false' => [
+ // gzip string below generated with serialize( 'JOJO' )
+ false, "s:4:\"JOJO\";", [ 'object' ], false,
+ ];
+ yield '(no legacy encoding), serialized object in with object flag returns string' => [
+ false,
+ // Using a TitleValue object as it has a getText method (which is needed)
+ serialize( new TitleValue( 0, 'HHJJDDFF' ) ),
+ [ 'object' ],
+ 'HHJJDDFF',
+ ];
+ yield '(no legacy encoding), serialized object in with object & gzip flag returns string' => [
+ false,
+ // Using a TitleValue object as it has a getText method (which is needed)
+ gzdeflate( serialize( new TitleValue( 0, '8219JJJ840' ) ) ),
+ [ 'object', 'gzip' ],
+ '8219JJJ840',
+ ];
+ yield '(ISO-8859-1 encoding), string in string out' => [
+ 'ISO-8859-1',
+ iconv( 'utf-8', 'ISO-8859-1', "1®Àþ1" ),
+ [],
+ '1®Àþ1',
+ ];
+ yield '(ISO-8859-1 encoding), serialized object in with gzip flags returns string' => [
+ 'ISO-8859-1',
+ gzdeflate( iconv( 'utf-8', 'ISO-8859-1', "4®Àþ4" ) ),
+ [ 'gzip' ],
+ '4®Àþ4',
+ ];
+ yield '(ISO-8859-1 encoding), serialized object in with object flags returns string' => [
+ 'ISO-8859-1',
+ serialize( new TitleValue( 0, iconv( 'utf-8', 'ISO-8859-1', "3®Àþ3" ) ) ),
+ [ 'object' ],
+ '3®Àþ3',
+ ];
+ yield '(ISO-8859-1 encoding), serialized object in with object & gzip flags returns string' => [
+ 'ISO-8859-1',
+ gzdeflate( serialize( new TitleValue( 0, iconv( 'utf-8', 'ISO-8859-1', "2®Àþ2" ) ) ) ),
+ [ 'gzip', 'object' ],
+ '2®Àþ2',
+ ];
+ yield 'T184749 (windows-1252 encoding), string in string out' => [
+ 'windows-1252',
+ iconv( 'utf-8', 'windows-1252', "sammansättningar" ),
+ [],
+ 'sammansättningar',
+ ];
+ yield 'T184749 (windows-1252 encoding), string in string out with gzip' => [
+ 'windows-1252',
+ gzdeflate( iconv( 'utf-8', 'windows-1252', "sammansättningar" ) ),
+ [ 'gzip' ],
+ 'sammansättningar',
+ ];
+ }
+
+ /**
+ * @dataProvider provideDecompress
+ * @covers \MediaWiki\Storage\SqlBlobStore::decompressData
+ *
+ * @param string|bool $legacyEncoding
+ * @param mixed $data
+ * @param array $flags
+ * @param mixed $expected
+ */
+ public function testDecompressData( $legacyEncoding, $data, $flags, $expected ) {
+ $store = $this->getBlobStore( $legacyEncoding );
+ $this->assertSame(
+ $expected,
+ $store->decompressData( $data, $flags )
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\SqlBlobStore::compressData
+ */
+ public function testCompressRevisionTextUtf8() {
+ $store = $this->getBlobStore();
+ $row = new stdClass;
+ $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
+ $row->old_flags = $store->compressData( $row->old_text );
+ $this->assertTrue( false !== strpos( $row->old_flags, 'utf-8' ),
+ "Flags should contain 'utf-8'" );
+ $this->assertFalse( false !== strpos( $row->old_flags, 'gzip' ),
+ "Flags should not contain 'gzip'" );
+ $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
+ $row->old_text, "Direct check" );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\SqlBlobStore::compressData
+ */
+ public function testCompressRevisionTextUtf8Gzip() {
+ $store = $this->getBlobStore( false, true );
+ $this->checkPHPExtension( 'zlib' );
+
+ $row = new stdClass;
+ $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
+ $row->old_flags = $store->compressData( $row->old_text );
+ $this->assertTrue( false !== strpos( $row->old_flags, 'utf-8' ),
+ "Flags should contain 'utf-8'" );
+ $this->assertTrue( false !== strpos( $row->old_flags, 'gzip' ),
+ "Flags should contain 'gzip'" );
+ $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
+ gzinflate( $row->old_text ), "Direct check" );
+ }
+
+ public function provideBlobs() {
+ yield [ '' ];
+ yield [ 'someText' ];
+ yield [ "sammansättningar" ];
+ }
+
+ /**
+ * @dataProvider provideBlobs
+ * @covers \MediaWiki\Storage\SqlBlobStore::storeBlob
+ * @covers \MediaWiki\Storage\SqlBlobStore::getBlob
+ */
+ public function testSimpleStoreGetBlobSimpleRoundtrip( $blob ) {
+ $store = $this->getBlobStore();
+ $address = $store->storeBlob( $blob );
+ $this->assertSame( $blob, $store->getBlob( $address ) );
+ }
+
+ /**
+ * @dataProvider provideBlobs
+ * @covers \MediaWiki\Storage\SqlBlobStore::storeBlob
+ * @covers \MediaWiki\Storage\SqlBlobStore::getBlob
+ */
+ public function testSimpleStoreGetBlobSimpleRoundtripWindowsLegacyEncoding( $blob ) {
+ $store = $this->getBlobStore( 'windows-1252' );
+ $address = $store->storeBlob( $blob );
+ $this->assertSame( $blob, $store->getBlob( $address ) );
+ }
+
+ /**
+ * @dataProvider provideBlobs
+ * @covers \MediaWiki\Storage\SqlBlobStore::storeBlob
+ * @covers \MediaWiki\Storage\SqlBlobStore::getBlob
+ */
+ public function testSimpleStoreGetBlobSimpleRoundtripWindowsLegacyEncodingGzip( $blob ) {
+ $store = $this->getBlobStore( 'windows-1252', true );
+ $address = $store->storeBlob( $blob );
+ $this->assertSame( $blob, $store->getBlob( $address ) );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/TemplateCategoriesTest.php b/www/wiki/tests/phpunit/includes/TemplateCategoriesTest.php
index ab231363..ebd8dbd3 100644
--- a/www/wiki/tests/phpunit/includes/TemplateCategoriesTest.php
+++ b/www/wiki/tests/phpunit/includes/TemplateCategoriesTest.php
@@ -1,10 +1,10 @@
<?php
+require __DIR__ . "/../../../maintenance/runJobs.php";
+
/**
* @group Database
*/
-require __DIR__ . "/../../../maintenance/runJobs.php";
-
class TemplateCategoriesTest extends MediaWikiLangTestCase {
/**
diff --git a/www/wiki/tests/phpunit/includes/TemplateParserTest.php b/www/wiki/tests/phpunit/includes/TemplateParserTest.php
index c161f853..ccccf0f9 100644
--- a/www/wiki/tests/phpunit/includes/TemplateParserTest.php
+++ b/www/wiki/tests/phpunit/includes/TemplateParserTest.php
@@ -2,6 +2,7 @@
/**
* @group Templates
+ * @covers TemplateParser
*/
class TemplateParserTest extends MediaWikiTestCase {
@@ -19,9 +20,6 @@ class TemplateParserTest extends MediaWikiTestCase {
/**
* @dataProvider provideProcessTemplate
- * @covers TemplateParser::processTemplate
- * @covers TemplateParser::getTemplate
- * @covers TemplateParser::getTemplateFilename
*/
public function testProcessTemplate( $name, $args, $result, $exception = false ) {
if ( $exception ) {
@@ -118,7 +116,7 @@ class TemplateParserTest extends MediaWikiTestCase {
$this->assertEquals( 'rrr', $tp->processTemplate( 'recurse', $data ) );
$tp->enableRecursivePartials( false );
- $this->setExpectedException( 'Exception' );
+ $this->setExpectedException( Exception::class );
$tp->processTemplate( 'recurse', $data );
}
diff --git a/www/wiki/tests/phpunit/includes/TestUserRegistry.php b/www/wiki/tests/phpunit/includes/TestUserRegistry.php
index 4818b49a..0c178ca1 100644
--- a/www/wiki/tests/phpunit/includes/TestUserRegistry.php
+++ b/www/wiki/tests/phpunit/includes/TestUserRegistry.php
@@ -107,4 +107,19 @@ class TestUserRegistry {
public static function clear() {
self::$testUsers = [];
}
+
+ /**
+ * @todo It would be nice if this were a non-static method of TestUser
+ * instead, but that doesn't seem possible without friends?
+ *
+ * @return bool True if it's safe to modify the user
+ */
+ public static function isMutable( User $user ) {
+ foreach ( self::$testUsers as $key => $testUser ) {
+ if ( $user === $testUser->getUser() ) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/TestingAccessWrapper.php b/www/wiki/tests/phpunit/includes/TestingAccessWrapper.php
deleted file mode 100644
index 7332e15e..00000000
--- a/www/wiki/tests/phpunit/includes/TestingAccessWrapper.php
+++ /dev/null
@@ -1,128 +0,0 @@
-<?php
-/**
- * Circumvent access restrictions on object internals
- *
- * This can be helpful for writing tests that can probe object internals,
- * without having to modify the class under test to accomodate.
- *
- * Wrap an object with private methods as follows:
- * $title = TestingAccessWrapper::newFromObject( Title::newFromDBkey( $key ) );
- *
- * You can access private and protected instance methods and variables:
- * $formatter = $title->getTitleFormatter();
- *
- * TODO:
- * - Organize other helper classes in tests/testHelpers.inc into a directory.
- */
-class TestingAccessWrapper {
- /** @var mixed The object, or the class name for static-only access */
- public $object;
-
- /**
- * Return the same object, without access restrictions.
- */
- public static function newFromObject( $object ) {
- if ( !is_object( $object ) ) {
- throw new InvalidArgumentException( __METHOD__ . ' must be called with an object' );
- }
- $wrapper = new TestingAccessWrapper();
- $wrapper->object = $object;
- return $wrapper;
- }
-
- /**
- * Allow access to non-public static methods and properties of the class.
- * Use non-static access,
- */
- public static function newFromClass( $className ) {
- if ( !is_string( $className ) ) {
- throw new InvalidArgumentException( __METHOD__ . ' must be called with a class name' );
- }
- $wrapper = new TestingAccessWrapper();
- $wrapper->object = $className;
- return $wrapper;
- }
-
- public function __call( $method, $args ) {
- $methodReflection = $this->getMethod( $method );
-
- if ( $this->isStatic() && !$methodReflection->isStatic() ) {
- throw new DomainException( __METHOD__ . ': Cannot call non-static when wrapping static class' );
- }
-
- return $methodReflection->invokeArgs( $methodReflection->isStatic() ? null : $this->object,
- $args );
- }
-
- public function __set( $name, $value ) {
- $propertyReflection = $this->getProperty( $name );
-
- if ( $this->isStatic() && !$propertyReflection->isStatic() ) {
- throw new DomainException( __METHOD__ . ': Cannot set property when wrapping static class' );
- }
-
- $propertyReflection->setValue( $this->object, $value );
- }
-
- public function __get( $name ) {
- $propertyReflection = $this->getProperty( $name );
-
- if ( $this->isStatic() && !$propertyReflection->isStatic() ) {
- throw new DomainException( __METHOD__ . ': Cannot get property when wrapping static class' );
- }
-
- return $propertyReflection->getValue( $this->object );
- }
-
- private function isStatic() {
- return is_string( $this->object );
- }
-
- /**
- * Return a property and make it accessible.
- * @param string $name
- * @return ReflectionMethod
- */
- private function getMethod( $name ) {
- $classReflection = new ReflectionClass( $this->object );
- $methodReflection = $classReflection->getMethod( $name );
- $methodReflection->setAccessible( true );
- return $methodReflection;
- }
-
- /**
- * Return a property and make it accessible.
- *
- * ReflectionClass::getProperty() fails if the private property is defined
- * in a parent class. This works more like ReflectionClass::getMethod().
- *
- * @param string $name
- * @return ReflectionProperty
- * @throws ReflectionException
- */
- private function getProperty( $name ) {
- $classReflection = new ReflectionClass( $this->object );
- try {
- $propertyReflection = $classReflection->getProperty( $name );
- } catch ( ReflectionException $ex ) {
- while ( true ) {
- $classReflection = $classReflection->getParentClass();
- if ( !$classReflection ) {
- throw $ex;
- }
- try {
- $propertyReflection = $classReflection->getProperty( $name );
- } catch ( ReflectionException $ex2 ) {
- continue;
- }
- if ( $propertyReflection->isPrivate() ) {
- break;
- } else {
- throw $ex;
- }
- }
- }
- $propertyReflection->setAccessible( true );
- return $propertyReflection;
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/TestingAccessWrapperTest.php b/www/wiki/tests/phpunit/includes/TestingAccessWrapperTest.php
deleted file mode 100644
index 23eb023a..00000000
--- a/www/wiki/tests/phpunit/includes/TestingAccessWrapperTest.php
+++ /dev/null
@@ -1,119 +0,0 @@
-<?php
-
-class TestingAccessWrapperTest extends MediaWikiTestCase {
- protected $raw;
- protected $wrapped;
- protected $wrappedStatic;
-
- function setUp() {
- parent::setUp();
-
- require_once __DIR__ . '/../data/helpers/WellProtectedClass.php';
- $this->raw = new WellProtectedClass();
- $this->wrapped = TestingAccessWrapper::newFromObject( $this->raw );
- $this->wrappedStatic = TestingAccessWrapper::newFromClass( 'WellProtectedClass' );
- }
-
- /**
- * @expectedException InvalidArgumentException
- */
- function testConstructorException() {
- TestingAccessWrapper::newFromObject( 'WellProtectedClass' );
- }
-
- /**
- * @expectedException InvalidArgumentException
- */
- function testStaticConstructorException() {
- TestingAccessWrapper::newFromClass( new WellProtectedClass() );
- }
-
- function testGetProperty() {
- $this->assertSame( 1, $this->wrapped->property );
- $this->assertSame( 42, $this->wrapped->privateProperty );
- $this->assertSame( 9000, $this->wrapped->privateParentProperty );
- $this->assertSame( 'sp', $this->wrapped->staticProperty );
- $this->assertSame( 'spp', $this->wrapped->staticPrivateProperty );
- $this->assertSame( 'sp', $this->wrappedStatic->staticProperty );
- $this->assertSame( 'spp', $this->wrappedStatic->staticPrivateProperty );
- }
-
- /**
- * @expectedException DomainException
- */
- function testGetException() {
- $this->wrappedStatic->property;
- }
-
- function testSetProperty() {
- $this->wrapped->property = 10;
- $this->assertSame( 10, $this->wrapped->property );
- $this->assertSame( 10, $this->raw->getProperty() );
-
- $this->wrapped->privateProperty = 11;
- $this->assertSame( 11, $this->wrapped->privateProperty );
- $this->assertSame( 11, $this->raw->getPrivateProperty() );
-
- $this->wrapped->privateParentProperty = 12;
- $this->assertSame( 12, $this->wrapped->privateParentProperty );
- $this->assertSame( 12, $this->raw->getPrivateParentProperty() );
-
- $this->wrapped->staticProperty = 'x';
- $this->assertSame( 'x', $this->wrapped->staticProperty );
- $this->assertSame( 'x', $this->wrappedStatic->staticProperty );
-
- $this->wrapped->staticPrivateProperty = 'y';
- $this->assertSame( 'y', $this->wrapped->staticPrivateProperty );
- $this->assertSame( 'y', $this->wrappedStatic->staticPrivateProperty );
-
- $this->wrappedStatic->staticProperty = 'X';
- $this->assertSame( 'X', $this->wrapped->staticProperty );
- $this->assertSame( 'X', $this->wrappedStatic->staticProperty );
-
- $this->wrappedStatic->staticPrivateProperty = 'Y';
- $this->assertSame( 'Y', $this->wrapped->staticPrivateProperty );
- $this->assertSame( 'Y', $this->wrappedStatic->staticPrivateProperty );
-
- // don't rely on PHPUnit to restore static properties
- $this->wrapped->staticProperty = 'sp';
- $this->wrapped->staticPrivateProperty = 'spp';
- }
-
- /**
- * @expectedException DomainException
- */
- function testSetException() {
- $this->wrappedStatic->property = 1;
- }
-
- function testCallMethod() {
- $this->wrapped->incrementPropertyValue();
- $this->assertSame( 2, $this->wrapped->property );
- $this->assertSame( 2, $this->raw->getProperty() );
-
- $this->wrapped->incrementPrivatePropertyValue();
- $this->assertSame( 43, $this->wrapped->privateProperty );
- $this->assertSame( 43, $this->raw->getPrivateProperty() );
-
- $this->wrapped->incrementPrivateParentPropertyValue();
- $this->assertSame( 9001, $this->wrapped->privateParentProperty );
- $this->assertSame( 9001, $this->raw->getPrivateParentProperty() );
-
- $this->assertSame( 'sm', $this->wrapped->staticMethod() );
- $this->assertSame( 'spm', $this->wrapped->staticPrivateMethod() );
- $this->assertSame( 'sm', $this->wrappedStatic->staticMethod() );
- $this->assertSame( 'spm', $this->wrappedStatic->staticPrivateMethod() );
- }
-
- function testCallMethodTwoArgs() {
- $this->assertSame( 'two', $this->wrapped->whatSecondArg( 'one', 'two' ) );
- }
-
- /**
- * @expectedException DomainException
- */
- function testCallMethodException() {
- $this->wrappedStatic->incrementPropertyValue();
- }
-
-}
diff --git a/www/wiki/tests/phpunit/includes/TitleArrayFromResultTest.php b/www/wiki/tests/phpunit/includes/TitleArrayFromResultTest.php
index 7c2973f9..af49ecf7 100644
--- a/www/wiki/tests/phpunit/includes/TitleArrayFromResultTest.php
+++ b/www/wiki/tests/phpunit/includes/TitleArrayFromResultTest.php
@@ -4,10 +4,12 @@
* @author Addshore
* @covers TitleArrayFromResult
*/
-class TitleArrayFromResultTest extends PHPUnit_Framework_TestCase {
+class TitleArrayFromResultTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
private function getMockResultWrapper( $row = null, $numRows = 1 ) {
- $resultWrapper = $this->getMockBuilder( 'ResultWrapper' )
+ $resultWrapper = $this->getMockBuilder( Wikimedia\Rdbms\ResultWrapper::class )
->disableOriginalConstructor();
$resultWrapper = $resultWrapper->getMock();
@@ -59,7 +61,7 @@ class TitleArrayFromResultTest extends PHPUnit_Framework_TestCase {
$this->assertEquals( $resultWrapper, $object->res );
$this->assertSame( 0, $object->key );
- $this->assertInstanceOf( 'Title', $object->current );
+ $this->assertInstanceOf( Title::class, $object->current );
$this->assertEquals( $namespace, $object->current->mNamespace );
$this->assertEquals( $title, $object->current->mTextform );
}
@@ -92,7 +94,7 @@ class TitleArrayFromResultTest extends PHPUnit_Framework_TestCase {
$title = 'foo';
$row = $this->getRowWithTitle( $namespace, $title );
$object = $this->getTitleArrayFromResult( $this->getMockResultWrapper( $row ) );
- $this->assertInstanceOf( 'Title', $object->current() );
+ $this->assertInstanceOf( Title::class, $object->current() );
$this->assertEquals( $namespace, $object->current->mNamespace );
$this->assertEquals( $title, $object->current->mTextform );
}
diff --git a/www/wiki/tests/phpunit/includes/TitleMethodsTest.php b/www/wiki/tests/phpunit/includes/TitleMethodsTest.php
index d9c01cb9..4032b3a1 100644
--- a/www/wiki/tests/phpunit/includes/TitleMethodsTest.php
+++ b/www/wiki/tests/phpunit/includes/TitleMethodsTest.php
@@ -29,7 +29,7 @@ class TitleMethodsTest extends MediaWikiLangTestCase {
]
);
- MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ MWNamespace::clearCaches();
$wgContLang->resetNamespaces(); # reset namespace cache
}
@@ -38,7 +38,7 @@ class TitleMethodsTest extends MediaWikiLangTestCase {
parent::tearDown();
- MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ MWNamespace::clearCaches();
$wgContLang->resetNamespaces(); # reset namespace cache
}
@@ -164,7 +164,7 @@ class TitleMethodsTest extends MediaWikiLangTestCase {
$this->assertTrue( $title->hasContentModel( $expectedModelId ) );
}
- public static function provideIsCssOrJsPage() {
+ public static function provideIsSiteConfigPage() {
return [
[ 'Help:Foo', false ],
[ 'Help:Foo.js', false ],
@@ -172,14 +172,21 @@ class TitleMethodsTest extends MediaWikiLangTestCase {
[ 'User:Foo', false ],
[ 'User:Foo.js', false ],
[ 'User:Foo/bar.js', false ],
+ [ 'User:Foo/bar.json', false ],
[ 'User:Foo/bar.css', false ],
+ [ 'User:Foo/bar.JS', false ],
+ [ 'User:Foo/bar.JSON', false ],
+ [ 'User:Foo/bar.CSS', false ],
[ 'User talk:Foo/bar.css', false ],
[ 'User:Foo/bar.js.xxx', false ],
[ 'User:Foo/bar.xxx', false ],
[ 'MediaWiki:Foo.js', true ],
+ [ 'MediaWiki:Foo.json', true ],
[ 'MediaWiki:Foo.css', true ],
[ 'MediaWiki:Foo.JS', false ],
+ [ 'MediaWiki:Foo.JSON', false ],
[ 'MediaWiki:Foo.CSS', false ],
+ [ 'MediaWiki:Foo/bar.css', true ],
[ 'MediaWiki:Foo.css.xxx', false ],
[ 'TEST-JS:Foo', false ],
[ 'TEST-JS:Foo.js', false ],
@@ -187,15 +194,15 @@ class TitleMethodsTest extends MediaWikiLangTestCase {
}
/**
- * @dataProvider provideIsCssOrJsPage
- * @covers Title::isCssOrJsPage
+ * @dataProvider provideIsSiteConfigPage
+ * @covers Title::isSiteConfigPage
*/
- public function testIsCssOrJsPage( $title, $expectedBool ) {
+ public function testSiteConfigPage( $title, $expectedBool ) {
$title = Title::newFromText( $title );
- $this->assertEquals( $expectedBool, $title->isCssOrJsPage() );
+ $this->assertEquals( $expectedBool, $title->isSiteConfigPage() );
}
- public static function provideIsCssJsSubpage() {
+ public static function provideIsUserConfigPage() {
return [
[ 'Help:Foo', false ],
[ 'Help:Foo.js', false ],
@@ -203,67 +210,79 @@ class TitleMethodsTest extends MediaWikiLangTestCase {
[ 'User:Foo', false ],
[ 'User:Foo.js', false ],
[ 'User:Foo/bar.js', true ],
+ [ 'User:Foo/bar.JS', false ],
+ [ 'User:Foo/bar.json', true ],
+ [ 'User:Foo/bar.JSON', false ],
[ 'User:Foo/bar.css', true ],
+ [ 'User:Foo/bar.CSS', false ],
[ 'User talk:Foo/bar.css', false ],
[ 'User:Foo/bar.js.xxx', false ],
[ 'User:Foo/bar.xxx', false ],
[ 'MediaWiki:Foo.js', false ],
- [ 'User:Foo/bar.JS', false ],
- [ 'User:Foo/bar.CSS', false ],
+ [ 'MediaWiki:Foo.json', false ],
+ [ 'MediaWiki:Foo.css', false ],
+ [ 'MediaWiki:Foo.JS', false ],
+ [ 'MediaWiki:Foo.JSON', false ],
+ [ 'MediaWiki:Foo.CSS', false ],
+ [ 'MediaWiki:Foo.css.xxx', false ],
[ 'TEST-JS:Foo', false ],
[ 'TEST-JS:Foo.js', false ],
];
}
/**
- * @dataProvider provideIsCssJsSubpage
- * @covers Title::isCssJsSubpage
+ * @dataProvider provideIsUserConfigPage
+ * @covers Title::isUserConfigPage
*/
- public function testIsCssJsSubpage( $title, $expectedBool ) {
+ public function testIsUserConfigPage( $title, $expectedBool ) {
$title = Title::newFromText( $title );
- $this->assertEquals( $expectedBool, $title->isCssJsSubpage() );
+ $this->assertEquals( $expectedBool, $title->isUserConfigPage() );
}
- public static function provideIsCssSubpage() {
+ public static function provideIsUserCssConfigPage() {
return [
[ 'Help:Foo', false ],
[ 'Help:Foo.css', false ],
[ 'User:Foo', false ],
[ 'User:Foo.js', false ],
+ [ 'User:Foo.json', false ],
[ 'User:Foo.css', false ],
[ 'User:Foo/bar.js', false ],
+ [ 'User:Foo/bar.json', false ],
[ 'User:Foo/bar.css', true ],
];
}
/**
- * @dataProvider provideIsCssSubpage
- * @covers Title::isCssSubpage
+ * @dataProvider provideIsUserCssConfigPage
+ * @covers Title::isUserCssConfigPage
*/
- public function testIsCssSubpage( $title, $expectedBool ) {
+ public function testIsUserCssConfigPage( $title, $expectedBool ) {
$title = Title::newFromText( $title );
- $this->assertEquals( $expectedBool, $title->isCssSubpage() );
+ $this->assertEquals( $expectedBool, $title->isUserCssConfigPage() );
}
- public static function provideIsJsSubpage() {
+ public static function provideIsUserJsConfigPage() {
return [
[ 'Help:Foo', false ],
[ 'Help:Foo.css', false ],
[ 'User:Foo', false ],
[ 'User:Foo.js', false ],
+ [ 'User:Foo.json', false ],
[ 'User:Foo.css', false ],
[ 'User:Foo/bar.js', true ],
+ [ 'User:Foo/bar.json', false ],
[ 'User:Foo/bar.css', false ],
];
}
/**
- * @dataProvider provideIsJsSubpage
- * @covers Title::isJsSubpage
+ * @dataProvider provideIsUserJsConfigPage
+ * @covers Title::isUserJsConfigPage
*/
- public function testIsJsSubpage( $title, $expectedBool ) {
+ public function testIsUserJsConfigPage( $title, $expectedBool ) {
$title = Title::newFromText( $title );
- $this->assertEquals( $expectedBool, $title->isJsSubpage() );
+ $this->assertEquals( $expectedBool, $title->isUserJsConfigPage() );
}
public static function provideIsWikitextPage() {
@@ -274,18 +293,23 @@ class TitleMethodsTest extends MediaWikiLangTestCase {
[ 'User:Foo', true ],
[ 'User:Foo.js', true ],
[ 'User:Foo/bar.js', false ],
+ [ 'User:Foo/bar.json', false ],
[ 'User:Foo/bar.css', false ],
[ 'User talk:Foo/bar.css', true ],
[ 'User:Foo/bar.js.xxx', true ],
[ 'User:Foo/bar.xxx', true ],
[ 'MediaWiki:Foo.js', false ],
- [ 'MediaWiki:Foo.css', false ],
- [ 'MediaWiki:Foo/bar.css', false ],
[ 'User:Foo/bar.JS', true ],
+ [ 'User:Foo/bar.JSON', true ],
[ 'User:Foo/bar.CSS', true ],
+ [ 'MediaWiki:Foo.json', false ],
+ [ 'MediaWiki:Foo.css', false ],
+ [ 'MediaWiki:Foo.JS', true ],
+ [ 'MediaWiki:Foo.JSON', true ],
+ [ 'MediaWiki:Foo.CSS', true ],
+ [ 'MediaWiki:Foo.css.xxx', true ],
[ 'TEST-JS:Foo', false ],
[ 'TEST-JS:Foo.js', false ],
- [ 'TEST-JS_TALK:Foo.js', true ],
];
}
@@ -318,13 +342,16 @@ class TitleMethodsTest extends MediaWikiLangTestCase {
*/
public function testGetOtherPage( $text, $expected ) {
if ( $expected === null ) {
- $this->setExpectedException( 'MWException' );
+ $this->setExpectedException( MWException::class );
}
$title = Title::newFromText( $text );
$this->assertEquals( $expected, $title->getOtherPage()->getPrefixedText() );
}
+ /**
+ * @covers Title::clearCaches
+ */
public function testClearCaches() {
$linkCache = LinkCache::singleton();
diff --git a/www/wiki/tests/phpunit/includes/TitlePermissionTest.php b/www/wiki/tests/phpunit/includes/TitlePermissionTest.php
index c2516935..6600aa23 100644
--- a/www/wiki/tests/phpunit/includes/TitlePermissionTest.php
+++ b/www/wiki/tests/phpunit/includes/TitlePermissionTest.php
@@ -96,6 +96,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
/**
* @todo This test method should be split up into separate test methods and
* data providers
+ * @covers Title::checkQuickPermissions
*/
public function testQuickPermissions() {
global $wgContLang;
@@ -386,6 +387,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
/**
* @todo This test method should be split up into separate test methods and
* data providers
+ * @covers Title::checkSpecialsAndNSPermissions
*/
public function testSpecialsAndNSPermissions() {
global $wgNamespaceProtection;
@@ -442,91 +444,203 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
/**
* @todo This test method should be split up into separate test methods and
* data providers
+ * @covers Title::checkUserConfigPermissions
*/
- public function testCssAndJavascriptPermissions() {
+ public function testJsConfigEditPermissions() {
$this->setUser( $this->userName );
$this->setTitle( NS_USER, $this->userName . '/test.js' );
- $this->runCSSandJSPermissions(
+ $this->runConfigEditPermissions(
+ [ [ 'badaccess-group0' ], [ 'mycustomjsprotected', 'bogus' ] ],
+
[ [ 'badaccess-group0' ], [ 'mycustomjsprotected', 'bogus' ] ],
[ [ 'badaccess-group0' ], [ 'mycustomjsprotected', 'bogus' ] ],
[ [ 'badaccess-group0' ] ],
+
+ [ [ 'badaccess-group0' ], [ 'mycustomjsprotected', 'bogus' ] ],
[ [ 'badaccess-group0' ], [ 'mycustomjsprotected', 'bogus' ] ],
[ [ 'badaccess-group0' ] ]
);
+ }
+
+ /**
+ * @todo This test method should be split up into separate test methods and
+ * data providers
+ * @covers Title::checkUserConfigPermissions
+ */
+ public function testJsonConfigEditPermissions() {
+ $this->setUser( $this->userName );
+
+ $this->setTitle( NS_USER, $this->userName . '/test.json' );
+ $this->runConfigEditPermissions(
+ [ [ 'badaccess-group0' ], [ 'mycustomjsonprotected', 'bogus' ] ],
+
+ [ [ 'badaccess-group0' ], [ 'mycustomjsonprotected', 'bogus' ] ],
+ [ [ 'badaccess-group0' ] ],
+ [ [ 'badaccess-group0' ], [ 'mycustomjsonprotected', 'bogus' ] ],
+
+ [ [ 'badaccess-group0' ], [ 'mycustomjsonprotected', 'bogus' ] ],
+ [ [ 'badaccess-group0' ] ],
+ [ [ 'badaccess-group0' ], [ 'mycustomjsonprotected', 'bogus' ] ]
+ );
+ }
+
+ /**
+ * @todo This test method should be split up into separate test methods and
+ * data providers
+ * @covers Title::checkUserConfigPermissions
+ */
+ public function testCssConfigEditPermissions() {
+ $this->setUser( $this->userName );
$this->setTitle( NS_USER, $this->userName . '/test.css' );
- $this->runCSSandJSPermissions(
+ $this->runConfigEditPermissions(
[ [ 'badaccess-group0' ], [ 'mycustomcssprotected', 'bogus' ] ],
+
[ [ 'badaccess-group0' ] ],
[ [ 'badaccess-group0' ], [ 'mycustomcssprotected', 'bogus' ] ],
+ [ [ 'badaccess-group0' ], [ 'mycustomcssprotected', 'bogus' ] ],
+
[ [ 'badaccess-group0' ] ],
+ [ [ 'badaccess-group0' ], [ 'mycustomcssprotected', 'bogus' ] ],
[ [ 'badaccess-group0' ], [ 'mycustomcssprotected', 'bogus' ] ]
);
+ }
+
+ /**
+ * @todo This test method should be split up into separate test methods and
+ * data providers
+ * @covers Title::checkUserConfigPermissions
+ */
+ public function testOtherJsConfigEditPermissions() {
+ $this->setUser( $this->userName );
$this->setTitle( NS_USER, $this->altUserName . '/test.js' );
- $this->runCSSandJSPermissions(
+ $this->runConfigEditPermissions(
+ [ [ 'badaccess-group0' ], [ 'customjsprotected', 'bogus' ] ],
+
+ [ [ 'badaccess-group0' ], [ 'customjsprotected', 'bogus' ] ],
[ [ 'badaccess-group0' ], [ 'customjsprotected', 'bogus' ] ],
[ [ 'badaccess-group0' ], [ 'customjsprotected', 'bogus' ] ],
+
[ [ 'badaccess-group0' ], [ 'customjsprotected', 'bogus' ] ],
[ [ 'badaccess-group0' ], [ 'customjsprotected', 'bogus' ] ],
[ [ 'badaccess-group0' ] ]
);
+ }
+
+ /**
+ * @todo This test method should be split up into separate test methods and
+ * data providers
+ * @covers Title::checkUserConfigPermissions
+ */
+ public function testOtherJsonConfigEditPermissions() {
+ $this->setUser( $this->userName );
+
+ $this->setTitle( NS_USER, $this->altUserName . '/test.json' );
+ $this->runConfigEditPermissions(
+ [ [ 'badaccess-group0' ], [ 'customjsonprotected', 'bogus' ] ],
+
+ [ [ 'badaccess-group0' ], [ 'customjsonprotected', 'bogus' ] ],
+ [ [ 'badaccess-group0' ], [ 'customjsonprotected', 'bogus' ] ],
+ [ [ 'badaccess-group0' ], [ 'customjsonprotected', 'bogus' ] ],
+
+ [ [ 'badaccess-group0' ], [ 'customjsonprotected', 'bogus' ] ],
+ [ [ 'badaccess-group0' ] ],
+ [ [ 'badaccess-group0' ], [ 'customjsonprotected', 'bogus' ] ]
+ );
+ }
+
+ /**
+ * @todo This test method should be split up into separate test methods and
+ * data providers
+ * @covers Title::checkUserConfigPermissions
+ */
+ public function testOtherCssConfigEditPermissions() {
+ $this->setUser( $this->userName );
$this->setTitle( NS_USER, $this->altUserName . '/test.css' );
- $this->runCSSandJSPermissions(
+ $this->runConfigEditPermissions(
+ [ [ 'badaccess-group0' ], [ 'customcssprotected', 'bogus' ] ],
+
[ [ 'badaccess-group0' ], [ 'customcssprotected', 'bogus' ] ],
[ [ 'badaccess-group0' ], [ 'customcssprotected', 'bogus' ] ],
[ [ 'badaccess-group0' ], [ 'customcssprotected', 'bogus' ] ],
+
[ [ 'badaccess-group0' ] ],
+ [ [ 'badaccess-group0' ], [ 'customcssprotected', 'bogus' ] ],
[ [ 'badaccess-group0' ], [ 'customcssprotected', 'bogus' ] ]
);
+ }
+
+ /**
+ * @todo This test method should be split up into separate test methods and
+ * data providers
+ * @covers Title::checkUserConfigPermissions
+ */
+ public function testOtherNonConfigEditPermissions() {
+ $this->setUser( $this->userName );
$this->setTitle( NS_USER, $this->altUserName . '/tempo' );
- $this->runCSSandJSPermissions(
+ $this->runConfigEditPermissions(
+ [ [ 'badaccess-group0' ] ],
+
+ [ [ 'badaccess-group0' ] ],
[ [ 'badaccess-group0' ] ],
[ [ 'badaccess-group0' ] ],
+
[ [ 'badaccess-group0' ] ],
[ [ 'badaccess-group0' ] ],
[ [ 'badaccess-group0' ] ]
);
}
- protected function runCSSandJSPermissions( $result0, $result1, $result2, $result3, $result4 ) {
+ protected function runConfigEditPermissions(
+ $resultNone,
+ $resultMyCss,
+ $resultMyJson,
+ $resultMyJs,
+ $resultUserCss,
+ $resultUserJson,
+ $resultUserJs
+ ) {
$this->setUserPerm( '' );
- $this->assertEquals( $result0,
- $this->title->getUserPermissionsErrors( 'bogus',
- $this->user ) );
+ $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
+ $this->assertEquals( $resultNone, $result );
$this->setUserPerm( 'editmyusercss' );
- $this->assertEquals( $result1,
- $this->title->getUserPermissionsErrors( 'bogus',
- $this->user ) );
+ $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
+ $this->assertEquals( $resultMyCss, $result );
+
+ $this->setUserPerm( 'editmyuserjson' );
+ $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
+ $this->assertEquals( $resultMyJson, $result );
$this->setUserPerm( 'editmyuserjs' );
- $this->assertEquals( $result2,
- $this->title->getUserPermissionsErrors( 'bogus',
- $this->user ) );
+ $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
+ $this->assertEquals( $resultMyJs, $result );
$this->setUserPerm( 'editusercss' );
- $this->assertEquals( $result3,
- $this->title->getUserPermissionsErrors( 'bogus',
- $this->user ) );
+ $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
+ $this->assertEquals( $resultUserCss, $result );
+
+ $this->setUserPerm( 'edituserjson' );
+ $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
+ $this->assertEquals( $resultUserJson, $result );
$this->setUserPerm( 'edituserjs' );
- $this->assertEquals( $result4,
- $this->title->getUserPermissionsErrors( 'bogus',
- $this->user ) );
+ $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
+ $this->assertEquals( $resultUserJs, $result );
- $this->setUserPerm( [ 'edituserjs', 'editusercss' ] );
- $this->assertEquals( [ [ 'badaccess-group0' ] ],
- $this->title->getUserPermissionsErrors( 'bogus',
- $this->user ) );
+ $this->setUserPerm( [ 'edituserjs', 'edituserjson', 'editusercss' ] );
+ $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
+ $this->assertEquals( [ [ 'badaccess-group0' ] ], $result );
}
/**
* @todo This test method should be split up into separate test methods and
* data providers
+ * @covers Title::checkPageRestrictions
*/
public function testPageRestrictions() {
global $wgContLang;
@@ -619,6 +733,9 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
$this->user ) );
}
+ /**
+ * @covers Title::checkCascadingSourcesRestrictions
+ */
public function testCascadingSourcesRestrictions() {
$this->setTitle( NS_MAIN, "test page" );
$this->setUserPerm( [ "edit", "bogus" ] );
@@ -648,6 +765,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
/**
* @todo This test method should be split up into separate test methods and
* data providers
+ * @covers Title::checkActionPermissions
*/
public function testActionPermissions() {
$this->setUserPerm( [ "createpage" ] );
@@ -720,6 +838,9 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
$this->title->userCan( 'move-target', $this->user ) );
}
+ /**
+ * @covers Title::checkUserBlock
+ */
public function testUserBlock() {
$this->setMwGlobals( [
'wgEmailConfirmToEdit' => true,
diff --git a/www/wiki/tests/phpunit/includes/TitleTest.php b/www/wiki/tests/phpunit/includes/TitleTest.php
index b0febe8d..c81a0787 100644
--- a/www/wiki/tests/phpunit/includes/TitleTest.php
+++ b/www/wiki/tests/phpunit/includes/TitleTest.php
@@ -163,7 +163,7 @@ class TitleTest extends MediaWikiTestCase {
*/
public function testSecureAndSplitValid( $text ) {
$this->secureAndSplitGlobals();
- $this->assertInstanceOf( 'Title', Title::newFromText( $text ), "Valid: $text" );
+ $this->assertInstanceOf( Title::class, Title::newFromText( $text ), "Valid: $text" );
}
/**
@@ -282,7 +282,6 @@ class TitleTest extends MediaWikiTestCase {
/**
* Auth-less test of Title::isValidMoveOperation
*
- * @group Database
* @param string $source
* @param string $target
* @param array|string|bool $expected Required error
@@ -435,7 +434,7 @@ class TitleTest extends MediaWikiTestCase {
$this->setContentLang( $contLang );
$title = Title::newFromText( $titleText );
- $this->assertInstanceOf( 'Title', $title,
+ $this->assertInstanceOf( Title::class, $title,
"Test must be passed a valid title text, you gave '$titleText'"
);
$this->assertEquals( $expected,
@@ -553,6 +552,7 @@ class TitleTest extends MediaWikiTestCase {
}
/**
+ * @covers Title::newFromTitleValue
* @dataProvider provideNewFromTitleValue
*/
public function testNewFromTitleValue( TitleValue $value ) {
@@ -573,6 +573,7 @@ class TitleTest extends MediaWikiTestCase {
}
/**
+ * @covers Title::getTitleValue
* @dataProvider provideGetTitleValue
*/
public function testGetTitleValue( $text ) {
@@ -604,6 +605,7 @@ class TitleTest extends MediaWikiTestCase {
}
/**
+ * @covers Title::getFragment
* @dataProvider provideGetFragment
*
* @param string $full
@@ -912,4 +914,55 @@ class TitleTest extends MediaWikiTestCase {
public function testGetPrefixedDBKey( Title $title, $expected ) {
$this->assertEquals( $expected, $title->getPrefixedDBkey() );
}
+
+ /**
+ * @covers Title::getFragmentForURL
+ * @dataProvider provideGetFragmentForURL
+ *
+ * @param string $titleStr
+ * @param string $expected
+ */
+ public function testGetFragmentForURL( $titleStr, $expected ) {
+ $this->setMwGlobals( [
+ 'wgFragmentMode' => [ 'html5' ],
+ 'wgExternalInterwikiFragmentMode' => 'legacy',
+ ] );
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->insert( 'interwiki',
+ [
+ [
+ 'iw_prefix' => 'de',
+ 'iw_url' => 'http://de.wikipedia.org/wiki/',
+ 'iw_api' => 'http://de.wikipedia.org/w/api.php',
+ 'iw_wikiid' => 'dewiki',
+ 'iw_local' => 1,
+ 'iw_trans' => 0,
+ ],
+ [
+ 'iw_prefix' => 'zz',
+ 'iw_url' => 'http://zzwiki.org/wiki/',
+ 'iw_api' => 'http://zzwiki.org/w/api.php',
+ 'iw_wikiid' => 'zzwiki',
+ 'iw_local' => 0,
+ 'iw_trans' => 0,
+ ],
+ ],
+ __METHOD__,
+ [ 'IGNORE' ]
+ );
+
+ $title = Title::newFromText( $titleStr );
+ self::assertEquals( $expected, $title->getFragmentForURL() );
+
+ $dbw->delete( 'interwiki', '*', __METHOD__ );
+ }
+
+ public function provideGetFragmentForURL() {
+ return [
+ [ 'Foo', '' ],
+ [ 'Foo#ümlåût', '#ümlåût' ],
+ [ 'de:Foo#Bå®', '#Bå®' ],
+ [ 'zz:Foo#тест', '#.D1.82.D0.B5.D1.81.D1.82' ],
+ ];
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/WatchedItemIntegrationTest.php b/www/wiki/tests/phpunit/includes/WatchedItemIntegrationTest.php
deleted file mode 100644
index 01e7ecb9..00000000
--- a/www/wiki/tests/phpunit/includes/WatchedItemIntegrationTest.php
+++ /dev/null
@@ -1,145 +0,0 @@
-<?php
-use MediaWiki\MediaWikiServices;
-
-/**
- * @author Addshore
- *
- * @group Database
- *
- * @covers WatchedItem
- */
-class WatchedItemIntegrationTest extends MediaWikiTestCase {
-
- public function setUp() {
- parent::setUp();
- self::$users['WatchedItemIntegrationTestUser']
- = new TestUser( 'WatchedItemIntegrationTestUser' );
-
- $this->hideDeprecated( 'WatchedItem::fromUserTitle' );
- $this->hideDeprecated( 'WatchedItem::addWatch' );
- $this->hideDeprecated( 'WatchedItem::removeWatch' );
- $this->hideDeprecated( 'WatchedItem::isWatched' );
- $this->hideDeprecated( 'WatchedItem::duplicateEntries' );
- $this->hideDeprecated( 'WatchedItem::batchAddWatch' );
- }
-
- private function getUser() {
- return self::$users['WatchedItemIntegrationTestUser']->getUser();
- }
-
- public function testWatchAndUnWatchItem() {
- $user = $this->getUser();
- $title = Title::newFromText( 'WatchedItemIntegrationTestPage' );
- // Cleanup after previous tests
- WatchedItem::fromUserTitle( $user, $title )->removeWatch();
-
- $this->assertFalse(
- WatchedItem::fromUserTitle( $user, $title )->isWatched(),
- 'Page should not initially be watched'
- );
- WatchedItem::fromUserTitle( $user, $title )->addWatch();
- $this->assertTrue(
- WatchedItem::fromUserTitle( $user, $title )->isWatched(),
- 'Page should be watched'
- );
- WatchedItem::fromUserTitle( $user, $title )->removeWatch();
- $this->assertFalse(
- WatchedItem::fromUserTitle( $user, $title )->isWatched(),
- 'Page should be unwatched'
- );
- }
-
- public function testUpdateAndResetNotificationTimestamp() {
- $user = $this->getUser();
- $otherUser = ( new TestUser( 'WatchedItemIntegrationTestUser_otherUser' ) )->getUser();
- $title = Title::newFromText( 'WatchedItemIntegrationTestPage' );
- WatchedItem::fromUserTitle( $user, $title )->addWatch();
- $this->assertNull( WatchedItem::fromUserTitle( $user, $title )->getNotificationTimestamp() );
-
- EmailNotification::updateWatchlistTimestamp( $otherUser, $title, '20150202010101' );
- $this->assertEquals(
- '20150202010101',
- WatchedItem::fromUserTitle( $user, $title )->getNotificationTimestamp()
- );
-
- MediaWikiServices::getInstance()->getWatchedItemStore()->resetNotificationTimestamp(
- $user, $title
- );
- $this->assertNull( WatchedItem::fromUserTitle( $user, $title )->getNotificationTimestamp() );
- }
-
- public function testDuplicateAllAssociatedEntries() {
- $user = $this->getUser();
- $titleOld = Title::newFromText( 'WatchedItemIntegrationTestPageOld' );
- $titleNew = Title::newFromText( 'WatchedItemIntegrationTestPageNew' );
- WatchedItem::fromUserTitle( $user, $titleOld->getSubjectPage() )->addWatch();
- WatchedItem::fromUserTitle( $user, $titleOld->getTalkPage() )->addWatch();
- // Cleanup after previous tests
- WatchedItem::fromUserTitle( $user, $titleNew->getSubjectPage() )->removeWatch();
- WatchedItem::fromUserTitle( $user, $titleNew->getTalkPage() )->removeWatch();
-
- WatchedItem::duplicateEntries( $titleOld, $titleNew );
-
- $this->assertTrue(
- WatchedItem::fromUserTitle( $user, $titleOld->getSubjectPage() )->isWatched()
- );
- $this->assertTrue(
- WatchedItem::fromUserTitle( $user, $titleOld->getTalkPage() )->isWatched()
- );
- $this->assertTrue(
- WatchedItem::fromUserTitle( $user, $titleNew->getSubjectPage() )->isWatched()
- );
- $this->assertTrue(
- WatchedItem::fromUserTitle( $user, $titleNew->getTalkPage() )->isWatched()
- );
- }
-
- public function testIsWatched_falseOnNotAllowed() {
- $user = $this->getUser();
- $title = Title::newFromText( 'WatchedItemIntegrationTestPage' );
- WatchedItem::fromUserTitle( $user, $title )->addWatch();
-
- $this->assertTrue( WatchedItem::fromUserTitle( $user, $title )->isWatched() );
- $user->mRights = [];
- $this->assertFalse( WatchedItem::fromUserTitle( $user, $title )->isWatched() );
- }
-
- public function testGetNotificationTimestamp_falseOnNotAllowed() {
- $user = $this->getUser();
- $title = Title::newFromText( 'WatchedItemIntegrationTestPage' );
- WatchedItem::fromUserTitle( $user, $title )->addWatch();
- MediaWikiServices::getInstance()->getWatchedItemStore()->resetNotificationTimestamp(
- $user, $title
- );
-
- $this->assertEquals(
- null,
- WatchedItem::fromUserTitle( $user, $title )->getNotificationTimestamp()
- );
- $user->mRights = [];
- $this->assertFalse( WatchedItem::fromUserTitle( $user, $title )->getNotificationTimestamp() );
- }
-
- public function testRemoveWatch_falseOnNotAllowed() {
- $user = $this->getUser();
- $title = Title::newFromText( 'WatchedItemIntegrationTestPage' );
- WatchedItem::fromUserTitle( $user, $title )->addWatch();
-
- $previousRights = $user->mRights;
- $user->mRights = [];
- $this->assertFalse( WatchedItem::fromUserTitle( $user, $title )->removeWatch() );
- $user->mRights = $previousRights;
- $this->assertTrue( WatchedItem::fromUserTitle( $user, $title )->removeWatch() );
- }
-
- public function testGetNotificationTimestamp_falseOnNotWatched() {
- $user = $this->getUser();
- $title = Title::newFromText( 'WatchedItemIntegrationTestPage' );
-
- WatchedItem::fromUserTitle( $user, $title )->removeWatch();
- $this->assertFalse( WatchedItem::fromUserTitle( $user, $title )->isWatched() );
-
- $this->assertFalse( WatchedItem::fromUserTitle( $user, $title )->getNotificationTimestamp() );
- }
-
-}
diff --git a/www/wiki/tests/phpunit/includes/WatchedItemUnitTest.php b/www/wiki/tests/phpunit/includes/WatchedItemUnitTest.php
deleted file mode 100644
index 4544db49..00000000
--- a/www/wiki/tests/phpunit/includes/WatchedItemUnitTest.php
+++ /dev/null
@@ -1,161 +0,0 @@
-<?php
-use MediaWiki\Linker\LinkTarget;
-
-/**
- * @author Addshore
- *
- * @covers WatchedItem
- */
-class WatchedItemUnitTest extends MediaWikiTestCase {
-
- public function setUp() {
- parent::setUp();
-
- $this->hideDeprecated( 'WatchedItem::fromUserTitle' );
- $this->hideDeprecated( 'WatchedItem::addWatch' );
- $this->hideDeprecated( 'WatchedItem::removeWatch' );
- $this->hideDeprecated( 'WatchedItem::isWatched' );
- $this->hideDeprecated( 'WatchedItem::duplicateEntries' );
- $this->hideDeprecated( 'WatchedItem::batchAddWatch' );
- }
-
- /**
- * @param int $id
- *
- * @return PHPUnit_Framework_MockObject_MockObject|User
- */
- private function getMockUser( $id ) {
- $user = $this->createMock( User::class );
- $user->expects( $this->any() )
- ->method( 'getId' )
- ->will( $this->returnValue( $id ) );
- $user->expects( $this->any() )
- ->method( 'isAllowed' )
- ->will( $this->returnValue( true ) );
- return $user;
- }
-
- public function provideUserTitleTimestamp() {
- $user = $this->getMockUser( 111 );
- return [
- [ $user, Title::newFromText( 'SomeTitle' ), null ],
- [ $user, Title::newFromText( 'SomeTitle' ), '20150101010101' ],
- [ $user, new TitleValue( 0, 'TVTitle', 'frag' ), '20150101010101' ],
- ];
- }
-
- /**
- * @return PHPUnit_Framework_MockObject_MockObject|WatchedItemStore
- */
- private function getMockWatchedItemStore() {
- return $this->getMockBuilder( WatchedItemStore::class )
- ->disableOriginalConstructor()
- ->getMock();
- }
-
- /**
- * @dataProvider provideUserTitleTimestamp
- */
- public function testConstruction( $user, LinkTarget $linkTarget, $notifTimestamp ) {
- $item = new WatchedItem( $user, $linkTarget, $notifTimestamp );
-
- $this->assertSame( $user, $item->getUser() );
- $this->assertSame( $linkTarget, $item->getLinkTarget() );
- $this->assertSame( $notifTimestamp, $item->getNotificationTimestamp() );
-
- // The below tests the internal WatchedItem::getTitle method
- $this->assertInstanceOf( 'Title', $item->getTitle() );
- $this->assertSame( $linkTarget->getDBkey(), $item->getTitle()->getDBkey() );
- $this->assertSame( $linkTarget->getFragment(), $item->getTitle()->getFragment() );
- $this->assertSame( $linkTarget->getNamespace(), $item->getTitle()->getNamespace() );
- $this->assertSame( $linkTarget->getText(), $item->getTitle()->getText() );
- }
-
- /**
- * @dataProvider provideUserTitleTimestamp
- */
- public function testFromUserTitle( $user, $linkTarget, $timestamp ) {
- $store = $this->getMockWatchedItemStore();
- $store->expects( $this->once() )
- ->method( 'loadWatchedItem' )
- ->with( $user, $linkTarget )
- ->will( $this->returnValue( new WatchedItem( $user, $linkTarget, $timestamp ) ) );
- $this->setService( 'WatchedItemStore', $store );
-
- $item = WatchedItem::fromUserTitle( $user, $linkTarget, User::IGNORE_USER_RIGHTS );
-
- $this->assertEquals( $user, $item->getUser() );
- $this->assertEquals( $linkTarget, $item->getLinkTarget() );
- $this->assertEquals( $timestamp, $item->getNotificationTimestamp() );
- }
-
- public function testAddWatch() {
- $title = Title::newFromText( 'SomeTitle' );
- $timestamp = null;
- $checkRights = 0;
-
- /** @var User|PHPUnit_Framework_MockObject_MockObject $user */
- $user = $this->createMock( User::class );
- $user->expects( $this->once() )
- ->method( 'addWatch' )
- ->with( $title, $checkRights );
-
- $item = new WatchedItem( $user, $title, $timestamp, $checkRights );
- $this->assertTrue( $item->addWatch() );
- }
-
- public function testRemoveWatch() {
- $title = Title::newFromText( 'SomeTitle' );
- $timestamp = null;
- $checkRights = 0;
-
- /** @var User|PHPUnit_Framework_MockObject_MockObject $user */
- $user = $this->createMock( User::class );
- $user->expects( $this->once() )
- ->method( 'removeWatch' )
- ->with( $title, $checkRights );
-
- $item = new WatchedItem( $user, $title, $timestamp, $checkRights );
- $this->assertTrue( $item->removeWatch() );
- }
-
- public function provideBooleans() {
- return [
- [ true ],
- [ false ],
- ];
- }
-
- /**
- * @dataProvider provideBooleans
- */
- public function testIsWatched( $returnValue ) {
- $title = Title::newFromText( 'SomeTitle' );
- $timestamp = null;
- $checkRights = 0;
-
- /** @var User|PHPUnit_Framework_MockObject_MockObject $user */
- $user = $this->createMock( User::class );
- $user->expects( $this->once() )
- ->method( 'isWatched' )
- ->with( $title, $checkRights )
- ->will( $this->returnValue( $returnValue ) );
-
- $item = new WatchedItem( $user, $title, $timestamp, $checkRights );
- $this->assertEquals( $returnValue, $item->isWatched() );
- }
-
- public function testDuplicateEntries() {
- $oldTitle = Title::newFromText( 'OldTitle' );
- $newTitle = Title::newFromText( 'NewTitle' );
-
- $store = $this->getMockWatchedItemStore();
- $store->expects( $this->once() )
- ->method( 'duplicateAllAssociatedEntries' )
- ->with( $oldTitle, $newTitle );
- $this->setService( 'WatchedItemStore', $store );
-
- WatchedItem::duplicateEntries( $oldTitle, $newTitle );
- }
-
-}
diff --git a/www/wiki/tests/phpunit/includes/WebRequestTest.php b/www/wiki/tests/phpunit/includes/WebRequestTest.php
index 041e7e3c..9583921d 100644
--- a/www/wiki/tests/phpunit/includes/WebRequestTest.php
+++ b/www/wiki/tests/phpunit/includes/WebRequestTest.php
@@ -26,7 +26,7 @@ class WebRequestTest extends MediaWikiTestCase {
public function testDetectServer( $expected, $input, $description ) {
$this->setMwGlobals( 'wgAssumeProxiesUseDefaultProtocolPorts', true );
- $_SERVER = $input;
+ $this->setServerVars( $input );
$result = WebRequest::detectServer();
$this->assertEquals( $expected, $result, $description );
}
@@ -126,7 +126,7 @@ class WebRequestTest extends MediaWikiTestCase {
protected function mockWebRequest( $data = [] ) {
// Cannot use PHPUnit getMockBuilder() as it does not support
// overriding protected properties afterwards
- $reflection = new ReflectionClass( 'WebRequest' );
+ $reflection = new ReflectionClass( WebRequest::class );
$req = $reflection->newInstanceWithoutConstructor();
$prop = $reflection->getProperty( 'data' );
@@ -363,7 +363,7 @@ class WebRequestTest extends MediaWikiTestCase {
* @covers WebRequest::getIP
*/
public function testGetIP( $expected, $input, $squid, $xffList, $private, $description ) {
- $_SERVER = $input;
+ $this->setServerVars( $input );
$this->setMwGlobals( [
'wgUsePrivateIPs' => $private,
'wgHooks' => [
@@ -608,8 +608,19 @@ class WebRequestTest extends MediaWikiTestCase {
* @covers WebRequest::getAcceptLang
*/
public function testAcceptLang( $acceptLanguageHeader, $expectedLanguages, $description ) {
- $_SERVER = [ 'HTTP_ACCEPT_LANGUAGE' => $acceptLanguageHeader ];
+ $this->setServerVars( [ 'HTTP_ACCEPT_LANGUAGE' => $acceptLanguageHeader ] );
$request = new WebRequest();
$this->assertSame( $request->getAcceptLang(), $expectedLanguages, $description );
}
+
+ protected function setServerVars( $vars ) {
+ // Don't remove vars which should be available in all SAPI.
+ if ( !isset( $vars['REQUEST_TIME_FLOAT'] ) ) {
+ $vars['REQUEST_TIME_FLOAT'] = $_SERVER['REQUEST_TIME_FLOAT'];
+ }
+ if ( !isset( $vars['REQUEST_TIME'] ) ) {
+ $vars['REQUEST_TIME'] = $_SERVER['REQUEST_TIME'];
+ }
+ $_SERVER = $vars;
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/WikiReferenceTest.php b/www/wiki/tests/phpunit/includes/WikiReferenceTest.php
index 724dd605..e4b21ce5 100644
--- a/www/wiki/tests/phpunit/includes/WikiReferenceTest.php
+++ b/www/wiki/tests/phpunit/includes/WikiReferenceTest.php
@@ -3,8 +3,9 @@
/**
* @covers WikiReference
*/
+class WikiReferenceTest extends PHPUnit\Framework\TestCase {
-class WikiReferenceTest extends PHPUnit_Framework_TestCase {
+ use MediaWikiCoversValidator;
public function provideGetDisplayName() {
return [
diff --git a/www/wiki/tests/phpunit/includes/XmlJsTest.php b/www/wiki/tests/phpunit/includes/XmlJsTest.php
index 29e97eb6..c7975efa 100644
--- a/www/wiki/tests/phpunit/includes/XmlJsTest.php
+++ b/www/wiki/tests/phpunit/includes/XmlJsTest.php
@@ -3,7 +3,9 @@
/**
* @group Xml
*/
-class XmlJs extends PHPUnit_Framework_TestCase {
+class XmlJsTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
/**
* @covers XmlJsCode::__construct
diff --git a/www/wiki/tests/phpunit/includes/XmlTest.php b/www/wiki/tests/phpunit/includes/XmlTest.php
index c5572b46..e46fc67f 100644
--- a/www/wiki/tests/phpunit/includes/XmlTest.php
+++ b/www/wiki/tests/phpunit/includes/XmlTest.php
@@ -34,6 +34,11 @@ class XmlTest extends MediaWikiTestCase {
] );
}
+ protected function tearDown() {
+ Language::factory( 'en' )->resetNamespaces();
+ parent::tearDown();
+ }
+
/**
* @covers Xml::expandAttributes
*/
@@ -50,7 +55,7 @@ class XmlTest extends MediaWikiTestCase {
* @covers Xml::expandAttributes
*/
public function testExpandAttributesException() {
- $this->setExpectedException( 'MWException' );
+ $this->setExpectedException( MWException::class );
Xml::expandAttributes( 'string' );
}
@@ -136,6 +141,57 @@ class XmlTest extends MediaWikiTestCase {
$this->assertEquals( '</element>', Xml::closeElement( 'element' ), 'closeElement() shortcut' );
}
+ public function provideMonthSelector() {
+ global $wgLang;
+
+ $header = '<select name="month" id="month" class="mw-month-selector">';
+ $header2 = '<select name="month" id="monthSelector" class="mw-month-selector">';
+ $monthsString = '';
+ for ( $i = 1; $i < 13; $i++ ) {
+ $monthName = $wgLang->getMonthName( $i );
+ $monthsString .= "<option value=\"{$i}\">{$monthName}</option>";
+ if ( $i !== 12 ) {
+ $monthsString .= "\n";
+ }
+ }
+ $monthsString2 = str_replace(
+ '<option value="12">December</option>',
+ '<option value="12" selected="">December</option>',
+ $monthsString
+ );
+ $end = '</select>';
+
+ $allMonths = "<option value=\"AllMonths\">all</option>\n";
+ return [
+ [ $header . $monthsString . $end, '', null, 'month' ],
+ [ $header . $monthsString2 . $end, 12, null, 'month' ],
+ [ $header2 . $monthsString . $end, '', null, 'monthSelector' ],
+ [ $header . $allMonths . $monthsString . $end, '', 'AllMonths', 'month' ],
+
+ ];
+ }
+
+ /**
+ * @covers Xml::monthSelector
+ * @dataProvider provideMonthSelector
+ */
+ public function testMonthSelector( $expected, $selected, $allmonths, $id ) {
+ $this->assertEquals(
+ $expected,
+ Xml::monthSelector( $selected, $allmonths, $id )
+ );
+ }
+
+ /**
+ * @covers Xml::span
+ */
+ public function testSpan() {
+ $this->assertEquals(
+ '<span class="foo" id="testSpan">element</span>',
+ Xml::span( 'element', 'foo', [ 'id' => 'testSpan' ] )
+ );
+ }
+
/**
* @covers Xml::dateMenu
*/
@@ -528,4 +584,34 @@ class XmlTest extends MediaWikiTestCase {
'Entire element with legend and attributes'
);
}
+
+ /**
+ * @covers Xml::buildTable
+ */
+ public function testBuildTable() {
+ $firstRow = [ 'foo', 'bar' ];
+ $secondRow = [ 'Berlin', 'Tehran' ];
+ $headers = [ 'header1', 'header2' ];
+ $expected = '<table id="testTable"><thead id="testTable"><th>header1</th>' .
+ '<th>header2</th></thead><tr><td>foo</td><td>bar</td></tr><tr><td>Berlin</td>' .
+ '<td>Tehran</td></tr></table>';
+ $this->assertEquals(
+ $expected,
+ Xml::buildTable(
+ [ $firstRow, $secondRow ],
+ [ 'id' => 'testTable' ],
+ $headers
+ )
+ );
+ }
+
+ /**
+ * @covers Xml::buildTableRow
+ */
+ public function testBuildTableRow() {
+ $this->assertEquals(
+ '<tr id="testRow"><td>foo</td><td>bar</td></tr>',
+ Xml::buildTableRow( [ 'id' => 'testRow' ], [ 'foo', 'bar' ] )
+ );
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/actions/ActionTest.php b/www/wiki/tests/phpunit/includes/actions/ActionTest.php
index 4a302920..b96b4914 100644
--- a/www/wiki/tests/phpunit/includes/actions/ActionTest.php
+++ b/www/wiki/tests/phpunit/includes/actions/ActionTest.php
@@ -3,10 +3,11 @@
/**
* @covers Action
*
- * @author Thiemo Mättig
- *
* @group Action
* @group Database
+ *
+ * @license GNU GPL v2+
+ * @author Thiemo Kreuz
*/
class ActionTest extends MediaWikiTestCase {
@@ -19,7 +20,7 @@ class ActionTest extends MediaWikiTestCase {
'disabled' => false,
'view' => true,
'edit' => true,
- 'revisiondelete' => 'SpecialPageAction',
+ 'revisiondelete' => SpecialPageAction::class,
'dummy' => true,
'string' => 'NamedDummyAction',
'declared' => 'NonExistingClassName',
diff --git a/www/wiki/tests/phpunit/includes/api/ApiBaseTest.php b/www/wiki/tests/phpunit/includes/api/ApiBaseTest.php
index ee0ad946..4bffc742 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiBaseTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiBaseTest.php
@@ -6,12 +6,45 @@ use Wikimedia\TestingAccessWrapper;
* @group API
* @group Database
* @group medium
+ *
+ * @covers ApiBase
*/
class ApiBaseTest extends ApiTestCase {
-
/**
- * @covers ApiBase::requireOnlyOneParameter
+ * This covers a variety of stub methods that return a fixed value.
+ *
+ * @param string|array $method Name of method, or [ name, params... ]
+ * @param string $value Expected value
+ *
+ * @dataProvider provideStubMethods
*/
+ public function testStubMethods( $expected, $method, $args = [] ) {
+ // Some of these are protected
+ $mock = TestingAccessWrapper::newFromObject( new MockApi() );
+ $result = call_user_func_array( [ $mock, $method ], $args );
+ $this->assertSame( $expected, $result );
+ }
+
+ public function provideStubMethods() {
+ return [
+ [ null, 'getModuleManager' ],
+ [ null, 'getCustomPrinter' ],
+ [ [], 'getHelpUrls' ],
+ // @todo This is actually overriden by MockApi
+ // [ [], 'getAllowedParams' ],
+ [ true, 'shouldCheckMaxLag' ],
+ [ true, 'isReadMode' ],
+ [ false, 'isWriteMode' ],
+ [ false, 'mustBePosted' ],
+ [ false, 'isDeprecated' ],
+ [ false, 'isInternal' ],
+ [ false, 'needsToken' ],
+ [ null, 'getWebUITokenSalt', [ [] ] ],
+ [ null, 'getConditionalRequestData', [ 'etag' ] ],
+ [ null, 'dynamicParameterDocumentation' ],
+ ];
+ }
+
public function testRequireOnlyOneParameterDefault() {
$mock = new MockApi();
$mock->requireOnlyOneParameter(
@@ -23,7 +56,6 @@ class ApiBaseTest extends ApiTestCase {
/**
* @expectedException ApiUsageException
- * @covers ApiBase::requireOnlyOneParameter
*/
public function testRequireOnlyOneParameterZero() {
$mock = new MockApi();
@@ -35,7 +67,6 @@ class ApiBaseTest extends ApiTestCase {
/**
* @expectedException ApiUsageException
- * @covers ApiBase::requireOnlyOneParameter
*/
public function testRequireOnlyOneParameterTrue() {
$mock = new MockApi();
@@ -45,37 +76,230 @@ class ApiBaseTest extends ApiTestCase {
);
}
+ public function testRequireOnlyOneParameterMissing() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'One of the parameters "foo" and "bar" is required.' );
+ $mock = new MockApi();
+ $mock->requireOnlyOneParameter(
+ [ "filename" => "foo.txt", "enablechunks" => false ],
+ "foo", "bar" );
+ }
+
+ public function testRequireMaxOneParameterZero() {
+ $mock = new MockApi();
+ $mock->requireMaxOneParameter(
+ [ 'foo' => 'bar', 'baz' => 'quz' ],
+ 'squirrel' );
+ $this->assertTrue( true );
+ }
+
+ public function testRequireMaxOneParameterOne() {
+ $mock = new MockApi();
+ $mock->requireMaxOneParameter(
+ [ 'foo' => 'bar', 'baz' => 'quz' ],
+ 'foo', 'squirrel' );
+ $this->assertTrue( true );
+ }
+
+ public function testRequireMaxOneParameterTwo() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'The parameters "foo" and "baz" can not be used together.' );
+ $mock = new MockApi();
+ $mock->requireMaxOneParameter(
+ [ 'foo' => 'bar', 'baz' => 'quz' ],
+ 'foo', 'baz' );
+ }
+
+ public function testRequireAtLeastOneParameterZero() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'At least one of the parameters "foo" and "bar" is required.' );
+ $mock = new MockApi();
+ $mock->requireAtLeastOneParameter(
+ [ 'a' => 'b', 'c' => 'd' ],
+ 'foo', 'bar' );
+ }
+
+ public function testRequireAtLeastOneParameterOne() {
+ $mock = new MockApi();
+ $mock->requireAtLeastOneParameter(
+ [ 'a' => 'b', 'c' => 'd' ],
+ 'foo', 'a' );
+ $this->assertTrue( true );
+ }
+
+ public function testRequireAtLeastOneParameterTwo() {
+ $mock = new MockApi();
+ $mock->requireAtLeastOneParameter(
+ [ 'a' => 'b', 'c' => 'd' ],
+ 'a', 'c' );
+ $this->assertTrue( true );
+ }
+
+ public function testGetTitleOrPageIdBadParams() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'The parameters "title" and "pageid" can not be used together.' );
+ $mock = new MockApi();
+ $mock->getTitleOrPageId( [ 'title' => 'a', 'pageid' => 7 ] );
+ }
+
+ public function testGetTitleOrPageIdTitle() {
+ $mock = new MockApi();
+ $result = $mock->getTitleOrPageId( [ 'title' => 'Foo' ] );
+ $this->assertInstanceOf( WikiPage::class, $result );
+ $this->assertSame( 'Foo', $result->getTitle()->getPrefixedText() );
+ }
+
+ public function testGetTitleOrPageIdInvalidTitle() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'Bad title "|".' );
+ $mock = new MockApi();
+ $mock->getTitleOrPageId( [ 'title' => '|' ] );
+ }
+
+ public function testGetTitleOrPageIdSpecialTitle() {
+ $this->setExpectedException( ApiUsageException::class,
+ "Namespace doesn't allow actual pages." );
+ $mock = new MockApi();
+ $mock->getTitleOrPageId( [ 'title' => 'Special:RandomPage' ] );
+ }
+
+ public function testGetTitleOrPageIdPageId() {
+ $result = ( new MockApi() )->getTitleOrPageId(
+ [ 'pageid' => Title::newFromText( 'UTPage' )->getArticleId() ] );
+ $this->assertInstanceOf( WikiPage::class, $result );
+ $this->assertSame( 'UTPage', $result->getTitle()->getPrefixedText() );
+ }
+
+ public function testGetTitleOrPageIdInvalidPageId() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'There is no page with ID 2147483648.' );
+ $mock = new MockApi();
+ $mock->getTitleOrPageId( [ 'pageid' => 2147483648 ] );
+ }
+
+ public function testGetTitleFromTitleOrPageIdBadParams() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'The parameters "title" and "pageid" can not be used together.' );
+ $mock = new MockApi();
+ $mock->getTitleFromTitleOrPageId( [ 'title' => 'a', 'pageid' => 7 ] );
+ }
+
+ public function testGetTitleFromTitleOrPageIdTitle() {
+ $mock = new MockApi();
+ $result = $mock->getTitleFromTitleOrPageId( [ 'title' => 'Foo' ] );
+ $this->assertInstanceOf( Title::class, $result );
+ $this->assertSame( 'Foo', $result->getPrefixedText() );
+ }
+
+ public function testGetTitleFromTitleOrPageIdInvalidTitle() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'Bad title "|".' );
+ $mock = new MockApi();
+ $mock->getTitleFromTitleOrPageId( [ 'title' => '|' ] );
+ }
+
+ public function testGetTitleFromTitleOrPageIdPageId() {
+ $result = ( new MockApi() )->getTitleFromTitleOrPageId(
+ [ 'pageid' => Title::newFromText( 'UTPage' )->getArticleId() ] );
+ $this->assertInstanceOf( Title::class, $result );
+ $this->assertSame( 'UTPage', $result->getPrefixedText() );
+ }
+
+ public function testGetTitleFromTitleOrPageIdInvalidPageId() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'There is no page with ID 298401643.' );
+ $mock = new MockApi();
+ $mock->getTitleFromTitleOrPageId( [ 'pageid' => 298401643 ] );
+ }
+
/**
* @dataProvider provideGetParameterFromSettings
* @param string|null $input
* @param array $paramSettings
* @param mixed $expected
+ * @param array $options Key-value pairs:
+ * 'parseLimits': true|false
+ * 'apihighlimits': true|false
+ * 'internalmode': true|false
* @param string[] $warnings
*/
- public function testGetParameterFromSettings( $input, $paramSettings, $expected, $warnings ) {
+ public function testGetParameterFromSettings(
+ $input, $paramSettings, $expected, $warnings, $options = []
+ ) {
$mock = new MockApi();
$wrapper = TestingAccessWrapper::newFromObject( $mock );
$context = new DerivativeContext( $mock );
- $context->setRequest( new FauxRequest( $input !== null ? [ 'foo' => $input ] : [] ) );
+ $context->setRequest( new FauxRequest(
+ $input !== null ? [ 'myParam' => $input ] : [] ) );
$wrapper->mMainModule = new ApiMain( $context );
- if ( $expected instanceof ApiUsageException ) {
+ $parseLimits = isset( $options['parseLimits'] ) ?
+ $options['parseLimits'] : true;
+
+ if ( !empty( $options['apihighlimits'] ) ) {
+ $context->setUser( self::$users['sysop']->getUser() );
+ }
+
+ if ( isset( $options['internalmode'] ) && !$options['internalmode'] ) {
+ $mainWrapper = TestingAccessWrapper::newFromObject( $wrapper->mMainModule );
+ $mainWrapper->mInternalMode = false;
+ }
+
+ // If we're testing tags, set up some tags
+ if ( isset( $paramSettings[ApiBase::PARAM_TYPE] ) &&
+ $paramSettings[ApiBase::PARAM_TYPE] === 'tags'
+ ) {
+ ChangeTags::defineTag( 'tag1' );
+ ChangeTags::defineTag( 'tag2' );
+ }
+
+ if ( $expected instanceof Exception ) {
try {
- $wrapper->getParameterFromSettings( 'foo', $paramSettings, true );
- } catch ( ApiUsageException $ex ) {
+ $wrapper->getParameterFromSettings( 'myParam', $paramSettings,
+ $parseLimits );
+ $this->fail( 'No exception thrown' );
+ } catch ( Exception $ex ) {
$this->assertEquals( $expected, $ex );
}
} else {
- $result = $wrapper->getParameterFromSettings( 'foo', $paramSettings, true );
- $this->assertSame( $expected, $result );
- $this->assertSame( $warnings, $mock->warnings );
+ $result = $wrapper->getParameterFromSettings( 'myParam',
+ $paramSettings, $parseLimits );
+ if ( isset( $paramSettings[ApiBase::PARAM_TYPE] ) &&
+ $paramSettings[ApiBase::PARAM_TYPE] === 'timestamp' &&
+ $expected === 'now'
+ ) {
+ // Allow one second of fuzziness. Make sure the formats are
+ // correct!
+ $this->assertRegExp( '/^\d{14}$/', $result );
+ $this->assertLessThanOrEqual( 1,
+ abs( wfTimestamp( TS_UNIX, $result ) - time() ),
+ "Result $result differs from expected $expected by " .
+ 'more than one second' );
+ } else {
+ $this->assertSame( $expected, $result );
+ }
+ $actualWarnings = array_map( function ( $warn ) {
+ return $warn instanceof Message
+ ? array_merge( [ $warn->getKey() ], $warn->getParams() )
+ : $warn;
+ }, $mock->warnings );
+ $this->assertSame( $warnings, $actualWarnings );
+ }
+
+ if ( !empty( $paramSettings[ApiBase::PARAM_SENSITIVE] ) ||
+ ( isset( $paramSettings[ApiBase::PARAM_TYPE] ) &&
+ $paramSettings[ApiBase::PARAM_TYPE] === 'password' )
+ ) {
+ $mainWrapper = TestingAccessWrapper::newFromObject( $wrapper->getMain() );
+ $this->assertSame( [ 'myParam' ],
+ $mainWrapper->getSensitiveParams() );
}
}
public static function provideGetParameterFromSettings() {
$warnings = [
- [ 'apiwarn-badutf8', 'foo' ],
+ [ 'apiwarn-badutf8', 'myParam' ],
];
$c0 = '';
@@ -87,7 +311,7 @@ class ApiBaseTest extends ApiTestCase {
: '�';
}
- return [
+ $returnArray = [
'Basic param' => [ 'bar', null, 'bar', [] ],
'Basic param, C0 controls' => [ $c0, null, $enc, $warnings ],
'String param' => [ 'bar', '', 'bar', [] ],
@@ -96,7 +320,8 @@ class ApiBaseTest extends ApiTestCase {
'String param, required, empty' => [
'',
[ ApiBase::PARAM_DFLT => 'default', ApiBase::PARAM_REQUIRED => true ],
- ApiUsageException::newWithMessage( null, [ 'apierror-missingparam', 'foo' ] ),
+ ApiUsageException::newWithMessage( null,
+ [ 'apierror-missingparam', 'myParam' ] ),
[]
],
'Multi-valued parameter' => [
@@ -123,7 +348,843 @@ class ApiBaseTest extends ApiTestCase {
[ substr( $enc, 0, -3 ), '' ],
$warnings
],
+ 'Multi-valued parameter with limits' => [
+ 'a|b|c',
+ [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_ISMULTI_LIMIT1 => 3,
+ ],
+ [ 'a', 'b', 'c' ],
+ [],
+ ],
+ 'Multi-valued parameter with exceeded limits' => [
+ 'a|b|c',
+ [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_ISMULTI_LIMIT1 => 2,
+ ],
+ [ 'a', 'b' ],
+ [ [ 'apiwarn-toomanyvalues', 'myParam', 2 ] ],
+ ],
+ 'Multi-valued parameter with exceeded limits for non-bot' => [
+ 'a|b|c',
+ [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_ISMULTI_LIMIT1 => 2,
+ ApiBase::PARAM_ISMULTI_LIMIT2 => 3,
+ ],
+ [ 'a', 'b' ],
+ [ [ 'apiwarn-toomanyvalues', 'myParam', 2 ] ],
+ ],
+ 'Multi-valued parameter with non-exceeded limits for bot' => [
+ 'a|b|c',
+ [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_ISMULTI_LIMIT1 => 2,
+ ApiBase::PARAM_ISMULTI_LIMIT2 => 3,
+ ],
+ [ 'a', 'b', 'c' ],
+ [],
+ [ 'apihighlimits' => true ],
+ ],
+ 'Multi-valued parameter with prohibited duplicates' => [
+ 'a|b|a|c',
+ [ ApiBase::PARAM_ISMULTI => true ],
+ // Note that the keys are not sequential! This matches
+ // array_unique, but might be unexpected.
+ [ 0 => 'a', 1 => 'b', 3 => 'c' ],
+ [],
+ ],
+ 'Multi-valued parameter with allowed duplicates' => [
+ 'a|a',
+ [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_ALLOW_DUPLICATES => true,
+ ],
+ [ 'a', 'a' ],
+ [],
+ ],
+ 'Empty boolean param' => [
+ '',
+ [ ApiBase::PARAM_TYPE => 'boolean' ],
+ true,
+ [],
+ ],
+ 'Boolean param 0' => [
+ '0',
+ [ ApiBase::PARAM_TYPE => 'boolean' ],
+ true,
+ [],
+ ],
+ 'Boolean param false' => [
+ 'false',
+ [ ApiBase::PARAM_TYPE => 'boolean' ],
+ true,
+ [],
+ ],
+ 'Boolean multi-param' => [
+ 'true|false',
+ [
+ ApiBase::PARAM_TYPE => 'boolean',
+ ApiBase::PARAM_ISMULTI => true,
+ ],
+ new MWException(
+ 'Internal error in ApiBase::getParameterFromSettings: ' .
+ 'Multi-values not supported for myParam'
+ ),
+ [],
+ ],
+ 'Empty boolean param with non-false default' => [
+ '',
+ [
+ ApiBase::PARAM_TYPE => 'boolean',
+ ApiBase::PARAM_DFLT => true,
+ ],
+ new MWException(
+ 'Internal error in ApiBase::getParameterFromSettings: ' .
+ "Boolean param myParam's default is set to '1'. " .
+ 'Boolean parameters must default to false.' ),
+ [],
+ ],
+ 'Deprecated parameter' => [
+ 'foo',
+ [ ApiBase::PARAM_DEPRECATED => true ],
+ 'foo',
+ [ [ 'apiwarn-deprecation-parameter', 'myParam' ] ],
+ ],
+ 'Deprecated parameter value' => [
+ 'a',
+ [ ApiBase::PARAM_DEPRECATED_VALUES => [ 'a' => true ] ],
+ 'a',
+ [ [ 'apiwarn-deprecation-parameter', 'myParam=a' ] ],
+ ],
+ 'Multiple deprecated parameter values' => [
+ 'a|b|c|d',
+ [ ApiBase::PARAM_DEPRECATED_VALUES =>
+ [ 'b' => true, 'd' => true ],
+ ApiBase::PARAM_ISMULTI => true ],
+ [ 'a', 'b', 'c', 'd' ],
+ [
+ [ 'apiwarn-deprecation-parameter', 'myParam=b' ],
+ [ 'apiwarn-deprecation-parameter', 'myParam=d' ],
+ ],
+ ],
+ 'Deprecated parameter value with custom warning' => [
+ 'a',
+ [ ApiBase::PARAM_DEPRECATED_VALUES => [ 'a' => 'my-msg' ] ],
+ 'a',
+ [ 'my-msg' ],
+ ],
+ '"*" when wildcard not allowed' => [
+ '*',
+ [ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => [ 'a', 'b', 'c' ] ],
+ [],
+ [ [ 'apiwarn-unrecognizedvalues', 'myParam',
+ [ 'list' => [ '&#42;' ], 'type' => 'comma' ], 1 ] ],
+ ],
+ 'Wildcard "*"' => [
+ '*',
+ [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => [ 'a', 'b', 'c' ],
+ ApiBase::PARAM_ALL => true,
+ ],
+ [ 'a', 'b', 'c' ],
+ [],
+ ],
+ 'Wildcard "*" with multiples not allowed' => [
+ '*',
+ [
+ ApiBase::PARAM_TYPE => [ 'a', 'b', 'c' ],
+ ApiBase::PARAM_ALL => true,
+ ],
+ ApiUsageException::newWithMessage( null,
+ [ 'apierror-unrecognizedvalue', 'myParam', '&#42;' ],
+ 'unknown_myParam' ),
+ [],
+ ],
+ 'Wildcard "*" with unrestricted type' => [
+ '*',
+ [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_ALL => true,
+ ],
+ [ '*' ],
+ [],
+ ],
+ 'Wildcard "x"' => [
+ 'x',
+ [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => [ 'a', 'b', 'c' ],
+ ApiBase::PARAM_ALL => 'x',
+ ],
+ [ 'a', 'b', 'c' ],
+ [],
+ ],
+ 'Wildcard conflicting with allowed value' => [
+ 'a',
+ [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => [ 'a', 'b', 'c' ],
+ ApiBase::PARAM_ALL => 'a',
+ ],
+ new MWException(
+ 'Internal error in ApiBase::getParameterFromSettings: ' .
+ 'For param myParam, PARAM_ALL collides with a possible ' .
+ 'value' ),
+ [],
+ ],
+ 'Namespace with wildcard' => [
+ '*',
+ [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'namespace',
+ ],
+ MWNamespace::getValidNamespaces(),
+ [],
+ ],
+ // PARAM_ALL is ignored with namespace types.
+ 'Namespace with wildcard suppressed' => [
+ '*',
+ [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'namespace',
+ ApiBase::PARAM_ALL => false,
+ ],
+ MWNamespace::getValidNamespaces(),
+ [],
+ ],
+ 'Namespace with wildcard "x"' => [
+ 'x',
+ [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'namespace',
+ ApiBase::PARAM_ALL => 'x',
+ ],
+ [],
+ [ [ 'apiwarn-unrecognizedvalues', 'myParam',
+ [ 'list' => [ 'x' ], 'type' => 'comma' ], 1 ] ],
+ ],
+ 'Password' => [
+ 'dDy+G?e?txnr.1:(@[Ru',
+ [ ApiBase::PARAM_TYPE => 'password' ],
+ 'dDy+G?e?txnr.1:(@[Ru',
+ [],
+ ],
+ 'Sensitive field' => [
+ 'I am fond of pineapples',
+ [ ApiBase::PARAM_SENSITIVE => true ],
+ 'I am fond of pineapples',
+ [],
+ ],
+ 'Upload with default' => [
+ '',
+ [
+ ApiBase::PARAM_TYPE => 'upload',
+ ApiBase::PARAM_DFLT => '',
+ ],
+ new MWException(
+ 'Internal error in ApiBase::getParameterFromSettings: ' .
+ "File upload param myParam's default is set to ''. " .
+ 'File upload parameters may not have a default.' ),
+ [],
+ ],
+ 'Multiple upload' => [
+ '',
+ [
+ ApiBase::PARAM_TYPE => 'upload',
+ ApiBase::PARAM_ISMULTI => true,
+ ],
+ new MWException(
+ 'Internal error in ApiBase::getParameterFromSettings: ' .
+ 'Multi-values not supported for myParam' ),
+ [],
+ ],
+ // @todo Test actual upload
+ 'Namespace -1' => [
+ '-1',
+ [ ApiBase::PARAM_TYPE => 'namespace' ],
+ ApiUsageException::newWithMessage( null,
+ [ 'apierror-unrecognizedvalue', 'myParam', '-1' ],
+ 'unknown_myParam' ),
+ [],
+ ],
+ 'Extra namespace -1' => [
+ '-1',
+ [
+ ApiBase::PARAM_TYPE => 'namespace',
+ ApiBase::PARAM_EXTRA_NAMESPACES => [ '-1' ],
+ ],
+ '-1',
+ [],
+ ],
+ // @todo Test with PARAM_SUBMODULE_MAP unset, need
+ // getModuleManager() to return something real
+ 'Nonexistent module' => [
+ 'not-a-module-name',
+ [
+ ApiBase::PARAM_TYPE => 'submodule',
+ ApiBase::PARAM_SUBMODULE_MAP =>
+ [ 'foo' => 'foo', 'bar' => 'foo+bar' ],
+ ],
+ ApiUsageException::newWithMessage(
+ null,
+ [
+ 'apierror-unrecognizedvalue',
+ 'myParam',
+ 'not-a-module-name',
+ ],
+ 'unknown_myParam'
+ ),
+ [],
+ ],
+ '\\x1f with multiples not allowed' => [
+ "\x1f",
+ [],
+ ApiUsageException::newWithMessage( null,
+ 'apierror-badvalue-notmultivalue',
+ 'badvalue_notmultivalue' ),
+ [],
+ ],
+ 'Integer with unenforced min' => [
+ '-2',
+ [
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_MIN => -1,
+ ],
+ -1,
+ [ [ 'apierror-integeroutofrange-belowminimum', 'myParam', -1,
+ -2 ] ],
+ ],
+ 'Integer with enforced min' => [
+ '-2',
+ [
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_MIN => -1,
+ ApiBase::PARAM_RANGE_ENFORCE => true,
+ ],
+ ApiUsageException::newWithMessage( null,
+ [ 'apierror-integeroutofrange-belowminimum', 'myParam',
+ '-1', '-2' ], 'integeroutofrange',
+ [ 'min' => -1, 'max' => null, 'botMax' => null ] ),
+ [],
+ ],
+ 'Integer with unenforced max (internal mode)' => [
+ '8',
+ [
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_MAX => 7,
+ ],
+ 8,
+ [],
+ ],
+ 'Integer with enforced max (internal mode)' => [
+ '8',
+ [
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_MAX => 7,
+ ApiBase::PARAM_RANGE_ENFORCE => true,
+ ],
+ 8,
+ [],
+ ],
+ 'Integer with unenforced max (non-internal mode)' => [
+ '8',
+ [
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_MAX => 7,
+ ],
+ 7,
+ [ [ 'apierror-integeroutofrange-abovemax', 'myParam', 7, 8 ] ],
+ [ 'internalmode' => false ],
+ ],
+ 'Integer with enforced max (non-internal mode)' => [
+ '8',
+ [
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_MAX => 7,
+ ApiBase::PARAM_RANGE_ENFORCE => true,
+ ],
+ ApiUsageException::newWithMessage(
+ null,
+ [ 'apierror-integeroutofrange-abovemax', 'myParam', '7', '8' ],
+ 'integeroutofrange',
+ [ 'min' => null, 'max' => 7, 'botMax' => 7 ]
+ ),
+ [],
+ [ 'internalmode' => false ],
+ ],
+ 'Array of integers' => [
+ '3|12|966|-1',
+ [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'integer',
+ ],
+ [ 3, 12, 966, -1 ],
+ [],
+ ],
+ 'Array of integers with unenforced min/max (internal mode)' => [
+ '3|12|966|-1',
+ [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_MIN => 0,
+ ApiBase::PARAM_MAX => 100,
+ ],
+ [ 3, 12, 966, 0 ],
+ [ [ 'apierror-integeroutofrange-belowminimum', 'myParam', 0, -1 ] ],
+ ],
+ 'Array of integers with enforced min/max (internal mode)' => [
+ '3|12|966|-1',
+ [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_MIN => 0,
+ ApiBase::PARAM_MAX => 100,
+ ApiBase::PARAM_RANGE_ENFORCE => true,
+ ],
+ ApiUsageException::newWithMessage(
+ null,
+ [ 'apierror-integeroutofrange-belowminimum', 'myParam', 0, -1 ],
+ 'integeroutofrange',
+ [ 'min' => 0, 'max' => 100, 'botMax' => 100 ]
+ ),
+ [],
+ ],
+ 'Array of integers with unenforced min/max (non-internal mode)' => [
+ '3|12|966|-1',
+ [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_MIN => 0,
+ ApiBase::PARAM_MAX => 100,
+ ],
+ [ 3, 12, 100, 0 ],
+ [
+ [ 'apierror-integeroutofrange-abovemax', 'myParam', 100, 966 ],
+ [ 'apierror-integeroutofrange-belowminimum', 'myParam', 0, -1 ]
+ ],
+ [ 'internalmode' => false ],
+ ],
+ 'Array of integers with enforced min/max (non-internal mode)' => [
+ '3|12|966|-1',
+ [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_MIN => 0,
+ ApiBase::PARAM_MAX => 100,
+ ApiBase::PARAM_RANGE_ENFORCE => true,
+ ],
+ ApiUsageException::newWithMessage(
+ null,
+ [ 'apierror-integeroutofrange-abovemax', 'myParam', 100, 966 ],
+ 'integeroutofrange',
+ [ 'min' => 0, 'max' => 100, 'botMax' => 100 ]
+ ),
+ [],
+ [ 'internalmode' => false ],
+ ],
+ 'Limit with parseLimits false' => [
+ '100',
+ [ ApiBase::PARAM_TYPE => 'limit' ],
+ '100',
+ [],
+ [ 'parseLimits' => false ],
+ ],
+ 'Limit with no max' => [
+ '100',
+ [
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MAX2 => 10,
+ ApiBase::PARAM_ISMULTI => true,
+ ],
+ new MWException(
+ 'Internal error in ApiBase::getParameterFromSettings: ' .
+ 'MAX1 or MAX2 are not defined for the limit myParam' ),
+ [],
+ ],
+ 'Limit with no max2' => [
+ '100',
+ [
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MAX => 10,
+ ApiBase::PARAM_ISMULTI => true,
+ ],
+ new MWException(
+ 'Internal error in ApiBase::getParameterFromSettings: ' .
+ 'MAX1 or MAX2 are not defined for the limit myParam' ),
+ [],
+ ],
+ 'Limit with multi-value' => [
+ '100',
+ [
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MAX => 10,
+ ApiBase::PARAM_MAX2 => 10,
+ ApiBase::PARAM_ISMULTI => true,
+ ],
+ new MWException(
+ 'Internal error in ApiBase::getParameterFromSettings: ' .
+ 'Multi-values not supported for myParam' ),
+ [],
+ ],
+ 'Valid limit' => [
+ '100',
+ [
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MAX => 100,
+ ApiBase::PARAM_MAX2 => 100,
+ ],
+ 100,
+ [],
+ ],
+ 'Limit max' => [
+ 'max',
+ [
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MAX => 100,
+ ApiBase::PARAM_MAX2 => 101,
+ ],
+ 100,
+ [],
+ ],
+ 'Limit max for apihighlimits' => [
+ 'max',
+ [
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MAX => 100,
+ ApiBase::PARAM_MAX2 => 101,
+ ],
+ 101,
+ [],
+ [ 'apihighlimits' => true ],
+ ],
+ 'Limit too large (internal mode)' => [
+ '101',
+ [
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MAX => 100,
+ ApiBase::PARAM_MAX2 => 101,
+ ],
+ 101,
+ [],
+ ],
+ 'Limit okay for apihighlimits (internal mode)' => [
+ '101',
+ [
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MAX => 100,
+ ApiBase::PARAM_MAX2 => 101,
+ ],
+ 101,
+ [],
+ [ 'apihighlimits' => true ],
+ ],
+ 'Limit too large for apihighlimits (internal mode)' => [
+ '102',
+ [
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MAX => 100,
+ ApiBase::PARAM_MAX2 => 101,
+ ],
+ 102,
+ [],
+ [ 'apihighlimits' => true ],
+ ],
+ 'Limit too large (non-internal mode)' => [
+ '101',
+ [
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MAX => 100,
+ ApiBase::PARAM_MAX2 => 101,
+ ],
+ 100,
+ [ [ 'apierror-integeroutofrange-abovemax', 'myParam', 100, 101 ] ],
+ [ 'internalmode' => false ],
+ ],
+ 'Limit okay for apihighlimits (non-internal mode)' => [
+ '101',
+ [
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MAX => 100,
+ ApiBase::PARAM_MAX2 => 101,
+ ],
+ 101,
+ [],
+ [ 'internalmode' => false, 'apihighlimits' => true ],
+ ],
+ 'Limit too large for apihighlimits (non-internal mode)' => [
+ '102',
+ [
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MAX => 100,
+ ApiBase::PARAM_MAX2 => 101,
+ ],
+ 101,
+ [ [ 'apierror-integeroutofrange-abovebotmax', 'myParam', 101, 102 ] ],
+ [ 'internalmode' => false, 'apihighlimits' => true ],
+ ],
+ 'Limit too small' => [
+ '-2',
+ [
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => -1,
+ ApiBase::PARAM_MAX => 100,
+ ApiBase::PARAM_MAX2 => 100,
+ ],
+ -1,
+ [ [ 'apierror-integeroutofrange-belowminimum', 'myParam', -1,
+ -2 ] ],
+ ],
+ 'Timestamp' => [
+ wfTimestamp( TS_UNIX, '20211221122112' ),
+ [ ApiBase::PARAM_TYPE => 'timestamp' ],
+ '20211221122112',
+ [],
+ ],
+ 'Timestamp 0' => [
+ '0',
+ [ ApiBase::PARAM_TYPE => 'timestamp' ],
+ // Magic keyword
+ 'now',
+ [ [ 'apiwarn-unclearnowtimestamp', 'myParam', '0' ] ],
+ ],
+ 'Timestamp empty' => [
+ '',
+ [ ApiBase::PARAM_TYPE => 'timestamp' ],
+ 'now',
+ [ [ 'apiwarn-unclearnowtimestamp', 'myParam', '' ] ],
+ ],
+ // wfTimestamp() interprets this as Unix time
+ 'Timestamp 00' => [
+ '00',
+ [ ApiBase::PARAM_TYPE => 'timestamp' ],
+ '19700101000000',
+ [],
+ ],
+ 'Timestamp now' => [
+ 'now',
+ [ ApiBase::PARAM_TYPE => 'timestamp' ],
+ 'now',
+ [],
+ ],
+ 'Invalid timestamp' => [
+ 'a potato',
+ [ ApiBase::PARAM_TYPE => 'timestamp' ],
+ ApiUsageException::newWithMessage(
+ null,
+ [ 'apierror-badtimestamp', 'myParam', 'a potato' ],
+ 'badtimestamp_myParam'
+ ),
+ [],
+ ],
+ 'Timestamp array' => [
+ '100|101',
+ [
+ ApiBase::PARAM_TYPE => 'timestamp',
+ ApiBase::PARAM_ISMULTI => 1,
+ ],
+ [ wfTimestamp( TS_MW, 100 ), wfTimestamp( TS_MW, 101 ) ],
+ [],
+ ],
+ 'User' => [
+ 'foo_bar',
+ [ ApiBase::PARAM_TYPE => 'user' ],
+ 'Foo bar',
+ [],
+ ],
+ 'Invalid username "|"' => [
+ '|',
+ [ ApiBase::PARAM_TYPE => 'user' ],
+ ApiUsageException::newWithMessage( null,
+ [ 'apierror-baduser', 'myParam', '&#124;' ],
+ 'baduser_myParam' ),
+ [],
+ ],
+ 'Invalid username "300.300.300.300"' => [
+ '300.300.300.300',
+ [ ApiBase::PARAM_TYPE => 'user' ],
+ ApiUsageException::newWithMessage( null,
+ [ 'apierror-baduser', 'myParam', '300.300.300.300' ],
+ 'baduser_myParam' ),
+ [],
+ ],
+ 'IP range as username' => [
+ '10.0.0.0/8',
+ [ ApiBase::PARAM_TYPE => 'user' ],
+ '10.0.0.0/8',
+ [],
+ ],
+ 'IPv6 as username' => [
+ '::1',
+ [ ApiBase::PARAM_TYPE => 'user' ],
+ '0:0:0:0:0:0:0:1',
+ [],
+ ],
+ 'Obsolete cloaked usemod IP address as username' => [
+ '1.2.3.xxx',
+ [ ApiBase::PARAM_TYPE => 'user' ],
+ '1.2.3.xxx',
+ [],
+ ],
+ 'Invalid username containing IP address' => [
+ 'This is [not] valid 1.2.3.xxx, ha!',
+ [ ApiBase::PARAM_TYPE => 'user' ],
+ ApiUsageException::newWithMessage(
+ null,
+ [ 'apierror-baduser', 'myParam', 'This is &#91;not&#93; valid 1.2.3.xxx, ha!' ],
+ 'baduser_myParam'
+ ),
+ [],
+ ],
+ 'External username' => [
+ 'M>Foo bar',
+ [ ApiBase::PARAM_TYPE => 'user' ],
+ 'M>Foo bar',
+ [],
+ ],
+ 'Array of usernames' => [
+ 'foo|bar',
+ [
+ ApiBase::PARAM_TYPE => 'user',
+ ApiBase::PARAM_ISMULTI => true,
+ ],
+ [ 'Foo', 'Bar' ],
+ [],
+ ],
+ 'tag' => [
+ 'tag1',
+ [ ApiBase::PARAM_TYPE => 'tags' ],
+ [ 'tag1' ],
+ [],
+ ],
+ 'Array of one tag' => [
+ 'tag1',
+ [
+ ApiBase::PARAM_TYPE => 'tags',
+ ApiBase::PARAM_ISMULTI => true,
+ ],
+ [ 'tag1' ],
+ [],
+ ],
+ 'Array of tags' => [
+ 'tag1|tag2',
+ [
+ ApiBase::PARAM_TYPE => 'tags',
+ ApiBase::PARAM_ISMULTI => true,
+ ],
+ [ 'tag1', 'tag2' ],
+ [],
+ ],
+ 'Invalid tag' => [
+ 'invalid tag',
+ [ ApiBase::PARAM_TYPE => 'tags' ],
+ new ApiUsageException( null,
+ Status::newFatal( 'tags-apply-not-allowed-one',
+ 'invalid tag', 1 ) ),
+ [],
+ ],
+ 'Unrecognized type' => [
+ 'foo',
+ [ ApiBase::PARAM_TYPE => 'nonexistenttype' ],
+ new MWException(
+ 'Internal error in ApiBase::getParameterFromSettings: ' .
+ "Param myParam's type is unknown - nonexistenttype" ),
+ [],
+ ],
+ 'Too many bytes' => [
+ '1',
+ [
+ ApiBase::PARAM_MAX_BYTES => 0,
+ ApiBase::PARAM_MAX_CHARS => 0,
+ ],
+ ApiUsageException::newWithMessage( null,
+ [ 'apierror-maxbytes', 'myParam', 0 ] ),
+ [],
+ ],
+ 'Too many chars' => [
+ '§§',
+ [
+ ApiBase::PARAM_MAX_BYTES => 4,
+ ApiBase::PARAM_MAX_CHARS => 1,
+ ],
+ ApiUsageException::newWithMessage( null,
+ [ 'apierror-maxchars', 'myParam', 1 ] ),
+ [],
+ ],
+ 'Omitted required param' => [
+ null,
+ [ ApiBase::PARAM_REQUIRED => true ],
+ ApiUsageException::newWithMessage( null,
+ [ 'apierror-missingparam', 'myParam' ] ),
+ [],
+ ],
+ 'Empty multi-value' => [
+ '',
+ [ ApiBase::PARAM_ISMULTI => true ],
+ [],
+ [],
+ ],
+ 'Multi-value \x1f' => [
+ "\x1f",
+ [ ApiBase::PARAM_ISMULTI => true ],
+ [],
+ [],
+ ],
+ 'Allowed non-multi-value with "|"' => [
+ 'a|b',
+ [ ApiBase::PARAM_TYPE => [ 'a|b' ] ],
+ 'a|b',
+ [],
+ ],
+ 'Prohibited multi-value' => [
+ 'a|b',
+ [ ApiBase::PARAM_TYPE => [ 'a', 'b' ] ],
+ ApiUsageException::newWithMessage( null,
+ [
+ 'apierror-multival-only-one-of',
+ 'myParam',
+ Message::listParam( [ '<kbd>a</kbd>', '<kbd>b</kbd>' ] ),
+ 2
+ ],
+ 'multival_myParam'
+ ),
+ [],
+ ],
+ ];
+
+ // The following really just test PHP's string-to-int conversion.
+ $integerTests = [
+ [ '+1', 1 ],
+ [ '-1', -1 ],
+ [ '1.5', 1 ],
+ [ '-1.5', -1 ],
+ [ '1abc', 1 ],
+ [ ' 1', 1 ],
+ [ "\t1", 1, '\t1' ],
+ [ "\r1", 1, '\r1' ],
+ [ "\f1", 0, '\f1', 'badutf-8' ],
+ [ "\n1", 1, '\n1' ],
+ [ "\v1", 0, '\v1', 'badutf-8' ],
+ [ "\e1", 0, '\e1', 'badutf-8' ],
+ [ "\x001", 0, '\x001', 'badutf-8' ],
];
+
+ foreach ( $integerTests as $test ) {
+ $desc = isset( $test[2] ) ? $test[2] : $test[0];
+ $warnings = isset( $test[3] ) ?
+ [ [ 'apiwarn-badutf8', 'myParam' ] ] : [];
+ $returnArray["\"$desc\" as integer"] = [
+ $test[0],
+ [ ApiBase::PARAM_TYPE => 'integer' ],
+ $test[1],
+ $warnings,
+ ];
+ }
+
+ return $returnArray;
}
public function testErrorArrayToStatus() {
@@ -153,6 +1214,7 @@ class ApiBaseTest extends ApiTestCase {
$block = new \Block( [
'address' => $user->getName(),
'user' => $user->getID(),
+ 'by' => $this->getTestSysop()->getUser()->getId(),
'reason' => __METHOD__,
'expiry' => time() + 100500,
] );
@@ -174,9 +1236,6 @@ class ApiBaseTest extends ApiTestCase {
], $user ) );
}
- /**
- * @covers ApiBase::dieStatus
- */
public function testDieStatus() {
$mock = new MockApi();
diff --git a/www/wiki/tests/phpunit/includes/api/ApiBlockTest.php b/www/wiki/tests/phpunit/includes/api/ApiBlockTest.php
index 832a113f..efefc09d 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiBlockTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiBlockTest.php
@@ -8,13 +8,20 @@
* @covers ApiBlock
*/
class ApiBlockTest extends ApiTestCase {
+ protected $mUser = null;
+
protected function setUp() {
parent::setUp();
- $this->doLogin();
+
+ $this->mUser = $this->getMutableTestUser()->getUser();
+ $this->setMwGlobals( 'wgBlockCIDRLimit', [
+ 'IPv4' => 16,
+ 'IPv6' => 19,
+ ] );
}
protected function tearDown() {
- $block = Block::newFromTarget( 'UTApiBlockee' );
+ $block = Block::newFromTarget( $this->mUser->getName() );
if ( !is_null( $block ) ) {
$block->delete();
}
@@ -25,80 +32,191 @@ class ApiBlockTest extends ApiTestCase {
return $this->getTokenList( self::$users['sysop'] );
}
- function addDBDataOnce() {
- $user = User::newFromName( 'UTApiBlockee' );
-
- if ( $user->getId() == 0 ) {
- $user->addToDatabase();
- TestUser::setPasswordForUser( $user, 'UTApiBlockeePassword' );
-
- $user->saveSettings();
- }
- }
-
/**
- * This test has probably always been broken and use an invalid token
- * Bug tracking brokenness is https://phabricator.wikimedia.org/T37646
- *
- * Root cause is https://gerrit.wikimedia.org/r/3434
- * Which made the Block/Unblock API to actually verify the token
- * previously always considered valid (T36212).
+ * @param array $extraParams Extra API parameters to pass to doApiRequest
+ * @param User $blocker User to do the blocking, null to pick
+ * arbitrarily
*/
- public function testMakeNormalBlock() {
- $tokens = $this->getTokens();
+ private function doBlock( array $extraParams = [], User $blocker = null ) {
+ if ( $blocker === null ) {
+ $blocker = self::$users['sysop']->getUser();
+ }
- $user = User::newFromName( 'UTApiBlockee' );
+ $tokens = $this->getTokens();
- if ( !$user->getId() ) {
- $this->markTestIncomplete( "The user UTApiBlockee does not exist" );
- }
+ $this->assertNotNull( $this->mUser, 'Sanity check' );
- if ( !array_key_exists( 'blocktoken', $tokens ) ) {
- $this->markTestIncomplete( "No block token found" );
- }
+ $this->assertArrayHasKey( 'blocktoken', $tokens, 'Sanity check' );
- $this->doApiRequest( [
+ $params = [
'action' => 'block',
- 'user' => 'UTApiBlockee',
+ 'user' => $this->mUser->getName(),
'reason' => 'Some reason',
- 'token' => $tokens['blocktoken'] ], null, false, self::$users['sysop']->getUser() );
+ 'token' => $tokens['blocktoken'],
+ ];
+ if ( array_key_exists( 'userid', $extraParams ) ) {
+ // Make sure we don't have both user and userid
+ unset( $params['user'] );
+ }
+ $ret = $this->doApiRequest( array_merge( $params, $extraParams ), null,
+ false, $blocker );
- $block = Block::newFromTarget( 'UTApiBlockee' );
+ $block = Block::newFromTarget( $this->mUser->getName() );
$this->assertTrue( !is_null( $block ), 'Block is valid' );
- $this->assertEquals( 'UTApiBlockee', (string)$block->getTarget() );
- $this->assertEquals( 'Some reason', $block->mReason );
- $this->assertEquals( 'infinity', $block->mExpiry );
+ $this->assertSame( $this->mUser->getName(), (string)$block->getTarget() );
+ $this->assertSame( 'Some reason', $block->mReason );
+
+ return $ret;
+ }
+
+ /**
+ * Block by username
+ */
+ public function testNormalBlock() {
+ $this->doBlock();
}
/**
* Block by user ID
*/
- public function testMakeNormalBlockId() {
- $tokens = $this->getTokens();
- $user = User::newFromName( 'UTApiBlockee' );
+ public function testBlockById() {
+ $this->doBlock( [ 'userid' => $this->mUser->getId() ] );
+ }
- if ( !$user->getId() ) {
- $this->markTestIncomplete( "The user UTApiBlockee does not exist." );
- }
+ /**
+ * A blocked user can't block
+ */
+ public function testBlockByBlockedUser() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'You cannot block or unblock other users because you are yourself blocked.' );
+
+ $blocked = $this->getMutableTestUser( [ 'sysop' ] )->getUser();
+ $block = new Block( [
+ 'address' => $blocked->getName(),
+ 'by' => self::$users['sysop']->getUser()->getId(),
+ 'reason' => 'Capriciousness',
+ 'timestamp' => '19370101000000',
+ 'expiry' => 'infinity',
+ ] );
+ $block->insert();
+
+ $this->doBlock( [], $blocked );
+ }
- if ( !array_key_exists( 'blocktoken', $tokens ) ) {
- $this->markTestIncomplete( "No block token found" );
- }
+ public function testBlockOfNonexistentUser() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'There is no user by the name "Nonexistent". Check your spelling.' );
- $data = $this->doApiRequest( [
- 'action' => 'block',
- 'userid' => $user->getId(),
- 'reason' => 'Some reason',
- 'token' => $tokens['blocktoken'] ], null, false, self::$users['sysop']->getUser() );
+ $this->doBlock( [ 'user' => 'Nonexistent' ] );
+ }
+
+ public function testBlockOfNonexistentUserId() {
+ $id = 948206325;
+ $this->setExpectedException( ApiUsageException::class,
+ "There is no user with ID $id." );
+
+ $this->assertFalse( User::whoIs( $id ), 'Sanity check' );
+
+ $this->doBlock( [ 'userid' => $id ] );
+ }
+
+ public function testBlockWithTag() {
+ ChangeTags::defineTag( 'custom tag' );
- $block = Block::newFromTarget( 'UTApiBlockee' );
+ $this->doBlock( [ 'tags' => 'custom tag' ] );
- $this->assertTrue( !is_null( $block ), 'Block is valid.' );
- $this->assertEquals( 'UTApiBlockee', (string)$block->getTarget() );
- $this->assertEquals( 'Some reason', $block->mReason );
- $this->assertEquals( 'infinity', $block->mExpiry );
+ $dbw = wfGetDB( DB_MASTER );
+ $this->assertSame( 'custom tag', $dbw->selectField(
+ [ 'change_tag', 'logging' ],
+ 'ct_tag',
+ [ 'log_type' => 'block' ],
+ __METHOD__,
+ [],
+ [ 'change_tag' => [ 'INNER JOIN', 'ct_log_id = log_id' ] ]
+ ) );
+ }
+
+ public function testBlockWithProhibitedTag() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'You do not have permission to apply change tags along with your changes.' );
+
+ ChangeTags::defineTag( 'custom tag' );
+
+ $this->setMwGlobals( 'wgRevokePermissions',
+ [ 'user' => [ 'applychangetags' => true ] ] );
+
+ $this->doBlock( [ 'tags' => 'custom tag' ] );
+ }
+
+ public function testBlockWithHide() {
+ global $wgGroupPermissions;
+ $newPermissions = $wgGroupPermissions['sysop'];
+ $newPermissions['hideuser'] = true;
+ $this->mergeMwGlobalArrayValue( 'wgGroupPermissions',
+ [ 'sysop' => $newPermissions ] );
+
+ $res = $this->doBlock( [ 'hidename' => '' ] );
+
+ $dbw = wfGetDB( DB_MASTER );
+ $this->assertSame( '1', $dbw->selectField(
+ 'ipblocks',
+ 'ipb_deleted',
+ [ 'ipb_id' => $res[0]['block']['id'] ],
+ __METHOD__
+ ) );
+ }
+
+ public function testBlockWithProhibitedHide() {
+ $this->setExpectedException( ApiUsageException::class,
+ "You don't have permission to hide user names from the block log." );
+
+ $this->doBlock( [ 'hidename' => '' ] );
+ }
+
+ public function testBlockWithEmailBlock() {
+ $res = $this->doBlock( [ 'noemail' => '' ] );
+
+ $dbw = wfGetDB( DB_MASTER );
+ $this->assertSame( '1', $dbw->selectField(
+ 'ipblocks',
+ 'ipb_block_email',
+ [ 'ipb_id' => $res[0]['block']['id'] ],
+ __METHOD__
+ ) );
+ }
+
+ public function testBlockWithProhibitedEmailBlock() {
+ $this->setExpectedException( ApiUsageException::class,
+ "You don't have permission to block users from sending email through the wiki." );
+
+ $this->setMwGlobals( 'wgRevokePermissions',
+ [ 'sysop' => [ 'blockemail' => true ] ] );
+
+ $this->doBlock( [ 'noemail' => '' ] );
+ }
+
+ public function testBlockWithExpiry() {
+ $res = $this->doBlock( [ 'expiry' => '1 day' ] );
+
+ $dbw = wfGetDB( DB_MASTER );
+ $expiry = $dbw->selectField(
+ 'ipblocks',
+ 'ipb_expiry',
+ [ 'ipb_id' => $res[0]['block']['id'] ],
+ __METHOD__
+ );
+
+ // Allow flakiness up to one second
+ $this->assertLessThanOrEqual( 1,
+ abs( wfTimestamp( TS_UNIX, $expiry ) - ( time() + 86400 ) ) );
+ }
+
+ public function testBlockWithInvalidExpiry() {
+ $this->setExpectedException( ApiUsageException::class, "Expiry time invalid." );
+
+ $this->doBlock( [ 'expiry' => '' ] );
}
/**
@@ -109,7 +227,7 @@ class ApiBlockTest extends ApiTestCase {
$this->doApiRequest(
[
'action' => 'block',
- 'user' => 'UTApiBlockee',
+ 'user' => $this->mUser->getName(),
'reason' => 'Some reason',
],
null,
@@ -117,4 +235,18 @@ class ApiBlockTest extends ApiTestCase {
self::$users['sysop']->getUser()
);
}
+
+ public function testRangeBlock() {
+ $this->mUser = User::newFromName( '128.0.0.0/16', false );
+ $this->doBlock();
+ }
+
+ /**
+ * @expectedException ApiUsageException
+ * @expectedExceptionMessage Range blocks larger than /16 are not allowed.
+ */
+ public function testVeryLargeRangeBlock() {
+ $this->mUser = User::newFromName( '128.0.0.0/1', false );
+ $this->doBlock();
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiCheckTokenTest.php b/www/wiki/tests/phpunit/includes/api/ApiCheckTokenTest.php
new file mode 100644
index 00000000..f1d95d03
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/api/ApiCheckTokenTest.php
@@ -0,0 +1,95 @@
+<?php
+
+use MediaWiki\Session\Token;
+
+/**
+ * @group API
+ * @group medium
+ * @covers ApiCheckToken
+ */
+class ApiCheckTokenTest extends ApiTestCase {
+
+ /**
+ * Test result of checking previously queried token (should be valid)
+ */
+ public function testCheckTokenValid() {
+ // Query token which will be checked later
+ $tokens = $this->doApiRequest( [
+ 'action' => 'query',
+ 'meta' => 'tokens',
+ ] );
+
+ $data = $this->doApiRequest( [
+ 'action' => 'checktoken',
+ 'type' => 'csrf',
+ 'token' => $tokens[0]['query']['tokens']['csrftoken'],
+ ], $tokens[1]->getSessionArray() );
+
+ $this->assertEquals( 'valid', $data[0]['checktoken']['result'] );
+ $this->assertArrayHasKey( 'generated', $data[0]['checktoken'] );
+ }
+
+ /**
+ * Test result of checking invalid token
+ */
+ public function testCheckTokenInvalid() {
+ $session = [];
+ $data = $this->doApiRequest( [
+ 'action' => 'checktoken',
+ 'type' => 'csrf',
+ 'token' => 'invalid_token',
+ ], $session );
+
+ $this->assertEquals( 'invalid', $data[0]['checktoken']['result'] );
+ }
+
+ /**
+ * Test result of checking token with negative max age (should be expired)
+ */
+ public function testCheckTokenExpired() {
+ // Query token which will be checked later
+ $tokens = $this->doApiRequest( [
+ 'action' => 'query',
+ 'meta' => 'tokens',
+ ] );
+
+ $data = $this->doApiRequest( [
+ 'action' => 'checktoken',
+ 'type' => 'csrf',
+ 'token' => $tokens[0]['query']['tokens']['csrftoken'],
+ 'maxtokenage' => -1,
+ ], $tokens[1]->getSessionArray() );
+
+ $this->assertEquals( 'expired', $data[0]['checktoken']['result'] );
+ $this->assertArrayHasKey( 'generated', $data[0]['checktoken'] );
+ }
+
+ /**
+ * Test if using token with incorrect suffix will produce a warning
+ */
+ public function testCheckTokenSuffixWarning() {
+ // Query token which will be checked later
+ $tokens = $this->doApiRequest( [
+ 'action' => 'query',
+ 'meta' => 'tokens',
+ ] );
+
+ // Get token and change the suffix
+ $token = $tokens[0]['query']['tokens']['csrftoken'];
+ $token = substr( $token, 0, -strlen( Token::SUFFIX ) ) . urldecode( Token::SUFFIX );
+
+ $data = $this->doApiRequest( [
+ 'action' => 'checktoken',
+ 'type' => 'csrf',
+ 'token' => $token,
+ 'errorformat' => 'raw',
+ ], $tokens[1]->getSessionArray() );
+
+ $this->assertEquals( 'invalid', $data[0]['checktoken']['result'] );
+ $this->assertArrayHasKey( 'warnings', $data[0] );
+ $this->assertCount( 1, $data[0]['warnings'] );
+ $this->assertEquals( 'checktoken', $data[0]['warnings'][0]['module'] );
+ $this->assertEquals( 'checktoken-percentencoding', $data[0]['warnings'][0]['code'] );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiClearHasMsgTest.php b/www/wiki/tests/phpunit/includes/api/ApiClearHasMsgTest.php
new file mode 100644
index 00000000..5b124074
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/api/ApiClearHasMsgTest.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @group API
+ * @group medium
+ * @covers ApiClearHasMsg
+ */
+class ApiClearHasMsgTest extends ApiTestCase {
+
+ /**
+ * Test clearing hasmsg flag for current user
+ */
+ public function testClearFlag() {
+ $user = self::$users['sysop']->getUser();
+ $user->setNewtalk( true );
+ $this->assertTrue( $user->getNewtalk(), 'sanity check' );
+
+ $data = $this->doApiRequest( [ 'action' => 'clearhasmsg' ], [] );
+
+ $this->assertEquals( 'success', $data[0]['clearhasmsg'] );
+ $this->assertFalse( $user->getNewtalk() );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiComparePagesTest.php b/www/wiki/tests/phpunit/includes/api/ApiComparePagesTest.php
index 989d6bb5..ea13a0d3 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiComparePagesTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiComparePagesTest.php
@@ -29,7 +29,7 @@ class ApiComparePagesTest extends ApiTestCase {
$status = $page->doEditContent(
$content, 'Test for ApiComparePagesTest: ' . $text, 0, false, $user
);
- if ( !$status->isOk() ) {
+ if ( !$status->isOK() ) {
$this->fail( "Failed to create $title: " . $status->getWikiText( false, false, 'en' ) );
}
return $status->value['revision']->getId();
@@ -70,6 +70,9 @@ class ApiComparePagesTest extends ApiTestCase {
'page', [ 'page_latest' => 0 ], [ 'page_id' => self::$repl['pageE'] ]
);
+ self::$repl['revF1'] = $this->addPage( 'F', "== Section 1 ==\nF 1.1\n\n== Section 2 ==\nF 1.2" );
+ self::$repl['pageF'] = Title::newFromText( 'ApiComparePagesTest F' )->getArticleId();
+
WikiPage::factory( Title::newFromText( 'ApiComparePagesTest C' ) )
->doDeleteArticleReal( 'Test for ApiComparePagesTest' );
@@ -151,8 +154,8 @@ class ApiComparePagesTest extends ApiTestCase {
}
public static function provideDiff() {
+ // phpcs:disable Generic.Files.LineLength.TooLong
return [
- // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
'Basic diff, titles' => [
[
'fromtitle' => 'ApiComparePagesTest A',
@@ -372,6 +375,26 @@ class ApiComparePagesTest extends ApiTestCase {
],
false, true
],
+ 'Basic diff, test with sections' => [
+ [
+ 'fromtitle' => 'ApiComparePagesTest F',
+ 'fromsection' => 1,
+ 'totext' => "== Section 1 ==\nTo text\n\n== Section 2 ==\nTo text?",
+ 'tosection' => 2,
+ ],
+ [
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div>== Section <del class="diffchange diffchange-inline">1 </del>==</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div>== Section <ins class="diffchange diffchange-inline">2 </ins>==</div></td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">F 1.1</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To text?</ins></div></td></tr>' . "\n",
+ 'fromid' => '{{REPL:pageF}}',
+ 'fromrevid' => '{{REPL:revF1}}',
+ 'fromns' => '0',
+ 'fromtitle' => 'ApiComparePagesTest F',
+ ]
+ ],
+ ],
'Diff with all props' => [
[
'fromrev' => '{{REPL:revB1}}',
@@ -568,6 +591,26 @@ class ApiComparePagesTest extends ApiTestCase {
[],
'compare-no-title',
],
+ 'Error, test with invalid from section ID' => [
+ [
+ 'fromtitle' => 'ApiComparePagesTest F',
+ 'fromsection' => 5,
+ 'totext' => "== Section 1 ==\nTo text\n\n== Section 2 ==\nTo text?",
+ 'tosection' => 2,
+ ],
+ [],
+ 'nosuchfromsection',
+ ],
+ 'Error, test with invalid to section ID' => [
+ [
+ 'fromtitle' => 'ApiComparePagesTest F',
+ 'fromsection' => 1,
+ 'totext' => "== Section 1 ==\nTo text\n\n== Section 2 ==\nTo text?",
+ 'tosection' => 5,
+ ],
+ [],
+ 'nosuchtosection',
+ ],
'Error, Relative diff, no from revision' => [
[
'fromtext' => 'Foo',
@@ -604,8 +647,7 @@ class ApiComparePagesTest extends ApiTestCase {
[],
'missingcontent'
],
-
- // @codingStandardsIgnoreEnd
];
+ // phpcs:enable
}
}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiContinuationManagerTest.php b/www/wiki/tests/phpunit/includes/api/ApiContinuationManagerTest.php
index bb4ea758..788d120c 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiContinuationManagerTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiContinuationManagerTest.php
@@ -22,7 +22,7 @@ class ApiContinuationManagerTest extends MediaWikiTestCase {
$generator = new MockApiQueryBase( 'generator' );
$manager = self::getManager( '', $allModules, [ 'mock1', 'mock2' ] );
- $this->assertSame( 'ApiMain', $manager->getSource() );
+ $this->assertSame( ApiMain::class, $manager->getSource() );
$this->assertSame( false, $manager->isGeneratorDone() );
$this->assertSame( $allModules, $manager->getRunModules() );
$manager->addContinueParam( $allModules[0], 'm1continue', [ 1, 2 ] );
diff --git a/www/wiki/tests/phpunit/includes/api/ApiDeleteTest.php b/www/wiki/tests/phpunit/includes/api/ApiDeleteTest.php
new file mode 100644
index 00000000..0f2bcc61
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/api/ApiDeleteTest.php
@@ -0,0 +1,168 @@
+<?php
+
+/**
+ * Tests for MediaWiki api.php?action=delete.
+ *
+ * @author Yifei He
+ *
+ * @group API
+ * @group Database
+ * @group medium
+ *
+ * @covers ApiDelete
+ */
+class ApiDeleteTest extends ApiTestCase {
+ public function testDelete() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ // create new page
+ $this->editPage( $name, 'Some text' );
+
+ // test deletion
+ $apiResult = $this->doApiRequestWithToken( [
+ 'action' => 'delete',
+ 'title' => $name,
+ ] )[0];
+
+ $this->assertArrayHasKey( 'delete', $apiResult );
+ $this->assertArrayHasKey( 'title', $apiResult['delete'] );
+ $this->assertSame( $name, $apiResult['delete']['title'] );
+ $this->assertArrayHasKey( 'logid', $apiResult['delete'] );
+
+ $this->assertFalse( Title::newFromText( $name )->exists() );
+ }
+
+ public function testDeleteNonexistent() {
+ $this->setExpectedException( ApiUsageException::class,
+ "The page you specified doesn't exist." );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'delete',
+ 'title' => 'This page deliberately left nonexistent',
+ ] );
+ }
+
+ public function testDeletionWithoutPermission() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'The action you have requested is limited to users in the group:' );
+
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ // create new page
+ $this->editPage( $name, 'Some text' );
+
+ // test deletion without permission
+ try {
+ $user = new User();
+ $apiResult = $this->doApiRequest( [
+ 'action' => 'delete',
+ 'title' => $name,
+ 'token' => $user->getEditToken(),
+ ], null, null, $user );
+ } finally {
+ $this->assertTrue( Title::newFromText( $name )->exists() );
+ }
+ }
+
+ public function testDeleteWithTag() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ ChangeTags::defineTag( 'custom tag' );
+
+ $this->editPage( $name, 'Some text' );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'delete',
+ 'title' => $name,
+ 'tags' => 'custom tag',
+ ] );
+
+ $this->assertFalse( Title::newFromText( $name )->exists() );
+
+ $dbw = wfGetDB( DB_MASTER );
+ $this->assertSame( 'custom tag', $dbw->selectField(
+ [ 'change_tag', 'logging' ],
+ 'ct_tag',
+ [
+ 'log_namespace' => NS_HELP,
+ 'log_title' => ucfirst( __FUNCTION__ ),
+ ],
+ __METHOD__,
+ [],
+ [ 'change_tag' => [ 'INNER JOIN', 'ct_log_id = log_id' ] ]
+ ) );
+ }
+
+ public function testDeleteWithoutTagPermission() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'You do not have permission to apply change tags along with your changes.' );
+
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ ChangeTags::defineTag( 'custom tag' );
+ $this->setMwGlobals( 'wgRevokePermissions',
+ [ 'user' => [ 'applychangetags' => true ] ] );
+
+ $this->editPage( $name, 'Some text' );
+
+ try {
+ $this->doApiRequestWithToken( [
+ 'action' => 'delete',
+ 'title' => $name,
+ 'tags' => 'custom tag',
+ ] );
+ } finally {
+ $this->assertTrue( Title::newFromText( $name )->exists() );
+ }
+ }
+
+ public function testDeleteAbortedByHook() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'Deletion aborted by hook. It gave no explanation.' );
+
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, 'Some text' );
+
+ $this->setTemporaryHook( 'ArticleDelete',
+ function () {
+ return false;
+ }
+ );
+
+ try {
+ $this->doApiRequestWithToken( [ 'action' => 'delete', 'title' => $name ] );
+ } finally {
+ $this->assertTrue( Title::newFromText( $name )->exists() );
+ }
+ }
+
+ public function testDeleteWatch() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+ $user = self::$users['sysop']->getUser();
+
+ $this->editPage( $name, 'Some text' );
+ $this->assertTrue( Title::newFromText( $name )->exists() );
+ $this->assertFalse( $user->isWatched( Title::newFromText( $name ) ) );
+
+ $this->doApiRequestWithToken( [ 'action' => 'delete', 'title' => $name, 'watch' => '' ] );
+
+ $this->assertFalse( Title::newFromText( $name )->exists() );
+ $this->assertTrue( $user->isWatched( Title::newFromText( $name ) ) );
+ }
+
+ public function testDeleteUnwatch() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+ $user = self::$users['sysop']->getUser();
+
+ $this->editPage( $name, 'Some text' );
+ $this->assertTrue( Title::newFromText( $name )->exists() );
+ $user->addWatch( Title::newFromText( $name ) );
+ $this->assertTrue( $user->isWatched( Title::newFromText( $name ) ) );
+
+ $this->doApiRequestWithToken( [ 'action' => 'delete', 'title' => $name, 'unwatch' => '' ] );
+
+ $this->assertFalse( Title::newFromText( $name )->exists() );
+ $this->assertFalse( $user->isWatched( Title::newFromText( $name ) ) );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiDisabledTest.php b/www/wiki/tests/phpunit/includes/api/ApiDisabledTest.php
new file mode 100644
index 00000000..cfdd57b8
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/api/ApiDisabledTest.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @group API
+ * @group medium
+ *
+ * @covers ApiDisabled
+ */
+class ApiDisabledTest extends ApiTestCase {
+ public function testDisabled() {
+ $this->mergeMwGlobalArrayValue( 'wgAPIModules',
+ [ 'login' => 'ApiDisabled' ] );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'The "login" module has been disabled.' );
+
+ $this->doApiRequest( [ 'action' => 'login' ] );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiEditPageTest.php b/www/wiki/tests/phpunit/includes/api/ApiEditPageTest.php
index e0911532..c1963389 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiEditPageTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiEditPageTest.php
@@ -35,15 +35,19 @@ class ApiEditPageTest extends ApiTestCase {
$wgContentHandlers["testing"] = 'DummyContentHandlerForTesting';
$wgContentHandlers["testing-nontext"] = 'DummyNonTextContentHandler';
+ $wgContentHandlers["testing-serialize-error"] =
+ 'DummySerializeErrorContentHandler';
- MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ MWNamespace::clearCaches();
$wgContLang->resetNamespaces(); # reset namespace cache
-
- $this->doLogin();
}
protected function tearDown() {
- MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ global $wgContLang;
+
+ MWNamespace::clearCaches();
+ $wgContLang->resetNamespaces(); # reset namespace cache
+
parent::tearDown();
}
@@ -61,7 +65,7 @@ class ApiEditPageTest extends ApiTestCase {
// Validate API result data
$this->assertArrayHasKey( 'edit', $apiResult );
$this->assertArrayHasKey( 'result', $apiResult['edit'] );
- $this->assertEquals( 'Success', $apiResult['edit']['result'] );
+ $this->assertSame( 'Success', $apiResult['edit']['result'] );
$this->assertArrayHasKey( 'new', $apiResult['edit'] );
$this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
@@ -75,7 +79,7 @@ class ApiEditPageTest extends ApiTestCase {
'text' => 'some text',
] );
- $this->assertEquals( 'Success', $data[0]['edit']['result'] );
+ $this->assertSame( 'Success', $data[0]['edit']['result'] );
$this->assertArrayNotHasKey( 'new', $data[0]['edit'] );
$this->assertArrayHasKey( 'nochange', $data[0]['edit'] );
@@ -87,7 +91,7 @@ class ApiEditPageTest extends ApiTestCase {
'text' => 'different text'
] );
- $this->assertEquals( 'Success', $data[0]['edit']['result'] );
+ $this->assertSame( 'Success', $data[0]['edit']['result'] );
$this->assertArrayNotHasKey( 'new', $data[0]['edit'] );
$this->assertArrayNotHasKey( 'nochange', $data[0]['edit'] );
@@ -144,7 +148,7 @@ class ApiEditPageTest extends ApiTestCase {
'title' => $name,
'text' => $text, ] );
- $this->assertEquals( 'Success', $re['edit']['result'] ); // sanity
+ $this->assertSame( 'Success', $re['edit']['result'] ); // sanity
}
// -- try append/prepend --------------------------------------------
@@ -153,7 +157,7 @@ class ApiEditPageTest extends ApiTestCase {
'title' => $name,
$op . 'text' => $append, ] );
- $this->assertEquals( 'Success', $re['edit']['result'] );
+ $this->assertSame( 'Success', $re['edit']['result'] );
// -- validate -----------------------------------------------------
$page = new WikiPage( Title::newFromText( $name ) );
@@ -162,7 +166,7 @@ class ApiEditPageTest extends ApiTestCase {
$text = $content->getNativeData();
- $this->assertEquals( $expected, $text );
+ $this->assertSame( $expected, $text );
}
/**
@@ -181,11 +185,11 @@ class ApiEditPageTest extends ApiTestCase {
'section' => '1',
'text' => "==section 1==\nnew content 1",
] );
- $this->assertEquals( 'Success', $re['edit']['result'] );
+ $this->assertSame( 'Success', $re['edit']['result'] );
$newtext = WikiPage::factory( Title::newFromText( $name ) )
->getContent( Revision::RAW )
->getNativeData();
- $this->assertEquals( "==section 1==\nnew content 1\n\n==section 2==\ncontent2", $newtext );
+ $this->assertSame( "==section 1==\nnew content 1\n\n==section 2==\ncontent2", $newtext );
// Test that we raise a 'nosuchsection' error
try {
@@ -220,12 +224,12 @@ class ApiEditPageTest extends ApiTestCase {
'summary' => 'header',
] );
- $this->assertEquals( 'Success', $re['edit']['result'] );
+ $this->assertSame( 'Success', $re['edit']['result'] );
// Check the page text is correct
$text = WikiPage::factory( Title::newFromText( $name ) )
->getContent( Revision::RAW )
->getNativeData();
- $this->assertEquals( "== header ==\n\ntest", $text );
+ $this->assertSame( "== header ==\n\ntest", $text );
// Now on one that does
$this->assertTrue( Title::newFromText( $name )->exists() );
@@ -237,11 +241,11 @@ class ApiEditPageTest extends ApiTestCase {
'summary' => 'header',
] );
- $this->assertEquals( 'Success', $re2['edit']['result'] );
+ $this->assertSame( 'Success', $re2['edit']['result'] );
$text = WikiPage::factory( Title::newFromText( $name ) )
->getContent( Revision::RAW )
->getNativeData();
- $this->assertEquals( "== header ==\n\ntest\n\n== header ==\n\ntest", $text );
+ $this->assertSame( "== header ==\n\ntest\n\n== header ==\n\ntest", $text );
}
/**
@@ -284,9 +288,9 @@ class ApiEditPageTest extends ApiTestCase {
'basetimestamp' => $baseTime,
'section' => 'new',
'redirect' => true,
- ], null, self::$users['sysop']->getUser() );
+ ] );
- $this->assertEquals( 'Success', $re['edit']['result'],
+ $this->assertSame( 'Success', $re['edit']['result'],
"no problems expected when following redirect" );
}
@@ -330,7 +334,7 @@ class ApiEditPageTest extends ApiTestCase {
'text' => 'nix bar!',
'basetimestamp' => $baseTime,
'redirect' => true,
- ], null, self::$users['sysop']->getUser() );
+ ] );
$this->fail( 'redirect-appendonly error expected' );
} catch ( ApiUsageException $ex ) {
@@ -366,7 +370,7 @@ class ApiEditPageTest extends ApiTestCase {
'title' => $name,
'text' => 'nix bar!',
'basetimestamp' => $baseTime,
- ], null, self::$users['sysop']->getUser() );
+ ] );
$this->fail( 'edit conflict expected' );
} catch ( ApiUsageException $ex ) {
@@ -405,9 +409,9 @@ class ApiEditPageTest extends ApiTestCase {
'text' => 'nix bar!',
'basetimestamp' => $baseTime,
'section' => 'new',
- ], null, self::$users['sysop']->getUser() );
+ ] );
- $this->assertEquals( 'Success', $re['edit']['result'],
+ $this->assertSame( 'Success', $re['edit']['result'],
"no edit conflict expected here" );
}
@@ -452,9 +456,9 @@ class ApiEditPageTest extends ApiTestCase {
'text' => 'nix bar!',
'section' => 'new',
'redirect' => true,
- ], null, self::$users['sysop']->getUser() );
+ ] );
- $this->assertEquals( 'Success', $re['edit']['result'],
+ $this->assertSame( 'Success', $re['edit']['result'],
"no edit conflict expected here" );
}
@@ -474,7 +478,7 @@ class ApiEditPageTest extends ApiTestCase {
public function testCheckDirectApiEditingDisallowed_forNonTextContent() {
$this->setExpectedException(
- 'ApiUsageException',
+ ApiUsageException::class,
'Direct editing via API is not supported for content model ' .
'testing used by Dummy:ApiEditPageTest_nonTextPageEdit'
);
@@ -501,7 +505,7 @@ class ApiEditPageTest extends ApiTestCase {
// Validate API result data
$this->assertArrayHasKey( 'edit', $apiResult );
$this->assertArrayHasKey( 'result', $apiResult['edit'] );
- $this->assertEquals( 'Success', $apiResult['edit']['result'] );
+ $this->assertSame( 'Success', $apiResult['edit']['result'] );
$this->assertArrayHasKey( 'new', $apiResult['edit'] );
$this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
@@ -510,8 +514,8 @@ class ApiEditPageTest extends ApiTestCase {
// validate resulting revision
$page = WikiPage::factory( Title::newFromText( $name ) );
- $this->assertEquals( "testing-nontext", $page->getContentModel() );
- $this->assertEquals( $data, $page->getContent()->serialize() );
+ $this->assertSame( "testing-nontext", $page->getContentModel() );
+ $this->assertSame( $data, $page->getContent()->serialize() );
}
/**
@@ -523,6 +527,7 @@ class ApiEditPageTest extends ApiTestCase {
$name = 'Help:' . __FUNCTION__;
$uploader = self::$users['uploader']->getUser();
$sysop = self::$users['sysop']->getUser();
+
$apiResult = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
@@ -532,10 +537,10 @@ class ApiEditPageTest extends ApiTestCase {
// Check success
$this->assertArrayHasKey( 'edit', $apiResult );
$this->assertArrayHasKey( 'result', $apiResult['edit'] );
- $this->assertEquals( 'Success', $apiResult['edit']['result'] );
+ $this->assertSame( 'Success', $apiResult['edit']['result'] );
$this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
// Content model is wikitext
- $this->assertEquals( 'wikitext', $apiResult['edit']['contentmodel'] );
+ $this->assertSame( 'wikitext', $apiResult['edit']['contentmodel'] );
// Convert the page to JSON
$apiResult = $this->doApiRequestWithToken( [
@@ -548,9 +553,9 @@ class ApiEditPageTest extends ApiTestCase {
// Check success
$this->assertArrayHasKey( 'edit', $apiResult );
$this->assertArrayHasKey( 'result', $apiResult['edit'] );
- $this->assertEquals( 'Success', $apiResult['edit']['result'] );
+ $this->assertSame( 'Success', $apiResult['edit']['result'] );
$this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
- $this->assertEquals( 'json', $apiResult['edit']['contentmodel'] );
+ $this->assertSame( 'json', $apiResult['edit']['contentmodel'] );
$apiResult = $this->doApiRequestWithToken( [
'action' => 'edit',
@@ -561,9 +566,1039 @@ class ApiEditPageTest extends ApiTestCase {
// Check success
$this->assertArrayHasKey( 'edit', $apiResult );
$this->assertArrayHasKey( 'result', $apiResult['edit'] );
- $this->assertEquals( 'Success', $apiResult['edit']['result'] );
+ $this->assertSame( 'Success', $apiResult['edit']['result'] );
$this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
// Check that the contentmodel is back to wikitext now.
- $this->assertEquals( 'wikitext', $apiResult['edit']['contentmodel'] );
+ $this->assertSame( 'wikitext', $apiResult['edit']['contentmodel'] );
+ }
+
+ // The tests below are mostly not commented because they do exactly what
+ // you'd expect from the name.
+
+ public function testCorrectContentFormat() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'some text',
+ 'contentmodel' => 'wikitext',
+ 'contentformat' => 'text/x-wiki',
+ ] );
+
+ $this->assertTrue( Title::newFromText( $name )->exists() );
+ }
+
+ public function testUnsupportedContentFormat() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'Unrecognized value for parameter "contentformat": nonexistent format.' );
+
+ try {
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'some text',
+ 'contentformat' => 'nonexistent format',
+ ] );
+ } finally {
+ $this->assertFalse( Title::newFromText( $name )->exists() );
+ }
+ }
+
+ public function testMismatchedContentFormat() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'The requested format text/plain is not supported for content ' .
+ "model wikitext used by $name." );
+
+ try {
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'some text',
+ 'contentmodel' => 'wikitext',
+ 'contentformat' => 'text/plain',
+ ] );
+ } finally {
+ $this->assertFalse( Title::newFromText( $name )->exists() );
+ }
+ }
+
+ public function testUndoToInvalidRev() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $revId = $this->editPage( $name, 'Some text' )->value['revision']
+ ->getId();
+ $revId++;
+
+ $this->setExpectedException( ApiUsageException::class,
+ "There is no revision with ID $revId." );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'undo' => $revId,
+ ] );
+ }
+
+ /**
+ * Tests what happens if the undo parameter is a valid revision, but
+ * the undoafter parameter doesn't refer to a revision that exists in the
+ * database.
+ */
+ public function testUndoAfterToInvalidRev() {
+ // We can't just pick a large number for undoafter (as in
+ // testUndoToInvalidRev above), because then MediaWiki will helpfully
+ // assume we switched around undo and undoafter and we'll test the code
+ // path for undo being invalid, not undoafter. So instead we delete
+ // the revision from the database. In real life this case could come
+ // up if a revision number was skipped, e.g., if two transactions try
+ // to insert new revision rows at once and the first one to succeed
+ // gets rolled back.
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+ $titleObj = Title::newFromText( $name );
+
+ $revId1 = $this->editPage( $name, '1' )->value['revision']->getId();
+ $revId2 = $this->editPage( $name, '2' )->value['revision']->getId();
+ $revId3 = $this->editPage( $name, '3' )->value['revision']->getId();
+
+ // Make the middle revision disappear
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->delete( 'revision', [ 'rev_id' => $revId2 ], __METHOD__ );
+ $dbw->update( 'revision', [ 'rev_parent_id' => $revId1 ],
+ [ 'rev_id' => $revId3 ], __METHOD__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ "There is no revision with ID $revId2." );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'undo' => $revId3,
+ 'undoafter' => $revId2,
+ ] );
+ }
+
+ /**
+ * Tests what happens if the undo parameter is a valid revision, but
+ * undoafter is hidden (rev_deleted).
+ */
+ public function testUndoAfterToHiddenRev() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+ $titleObj = Title::newFromText( $name );
+
+ $this->editPage( $name, '0' );
+
+ $revId1 = $this->editPage( $name, '1' )->value['revision']->getId();
+
+ $revId2 = $this->editPage( $name, '2' )->value['revision']->getId();
+
+ // Hide the middle revision
+ $list = RevisionDeleter::createList( 'revision',
+ RequestContext::getMain(), $titleObj, [ $revId1 ] );
+ $list->setVisibility( [
+ 'value' => [ Revision::DELETED_TEXT => 1 ],
+ 'comment' => 'Bye-bye',
+ ] );
+
+ $this->setExpectedException( ApiUsageException::class,
+ "There is no revision with ID $revId1." );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'undo' => $revId2,
+ 'undoafter' => $revId1,
+ ] );
+ }
+
+ /**
+ * Test undo when a revision with a higher id has an earlier timestamp.
+ * This can happen if importing an old revision.
+ */
+ public function testUndoWithSwappedRevisions() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+ $titleObj = Title::newFromText( $name );
+
+ $this->editPage( $name, '0' );
+
+ $revId2 = $this->editPage( $name, '2' )->value['revision']->getId();
+
+ $revId1 = $this->editPage( $name, '1' )->value['revision']->getId();
+
+ // Now monkey with the timestamp
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update(
+ 'revision',
+ [ 'rev_timestamp' => wfTimestamp( TS_MW, time() - 86400 ) ],
+ [ 'rev_id' => $revId1 ],
+ __METHOD__
+ );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'undo' => $revId2,
+ 'undoafter' => $revId1,
+ ] );
+
+ $text = ( new WikiPage( $titleObj ) )->getContent()->getNativeData();
+
+ // This is wrong! It should be 1. But let's test for our incorrect
+ // behavior for now, so if someone fixes it they'll fix the test as
+ // well to expect 1. If we disabled the test, it might stay disabled
+ // even once the bug is fixed, which would be a shame.
+ $this->assertSame( '2', $text );
+ }
+
+ public function testUndoWithConflicts() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'The edit could not be undone due to conflicting intermediate edits.' );
+
+ $this->editPage( $name, '1' );
+
+ $revId = $this->editPage( $name, '2' )->value['revision']->getId();
+
+ $this->editPage( $name, '3' );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'undo' => $revId,
+ ] );
+
+ $text = ( new WikiPage( Title::newFromText( $name ) ) )->getContent()
+ ->getNativeData();
+ $this->assertSame( '3', $text );
+ }
+
+ /**
+ * undoafter is supposed to be less than undo. If not, we reverse their
+ * meaning, so that the two are effectively interchangeable.
+ */
+ public function testReversedUndoAfter() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, '0' );
+ $revId1 = $this->editPage( $name, '1' )->value['revision']->getId();
+ $revId2 = $this->editPage( $name, '2' )->value['revision']->getId();
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'undo' => $revId1,
+ 'undoafter' => $revId2,
+ ] );
+
+ $text = ( new WikiPage( Title::newFromText( $name ) ) )->getContent()
+ ->getNativeData();
+ $this->assertSame( '1', $text );
+ }
+
+ public function testUndoToRevFromDifferentPage() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->editPage( "$name-1", 'Some text' );
+ $revId = $this->editPage( "$name-1", 'Some more text' )
+ ->value['revision']->getId();
+
+ $this->editPage( "$name-2", 'Some text' );
+
+ $this->setExpectedException( ApiUsageException::class,
+ "r$revId is not a revision of $name-2." );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => "$name-2",
+ 'undo' => $revId,
+ ] );
+ }
+
+ public function testUndoAfterToRevFromDifferentPage() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $revId1 = $this->editPage( "$name-1", 'Some text' )
+ ->value['revision']->getId();
+
+ $revId2 = $this->editPage( "$name-2", 'Some text' )
+ ->value['revision']->getId();
+
+ $this->setExpectedException( ApiUsageException::class,
+ "r$revId1 is not a revision of $name-2." );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => "$name-2",
+ 'undo' => $revId2,
+ 'undoafter' => $revId1,
+ ] );
+ }
+
+ public function testMd5Text() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->assertFalse( Title::newFromText( $name )->exists() );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Some text',
+ 'md5' => md5( 'Some text' ),
+ ] );
+
+ $this->assertTrue( Title::newFromText( $name )->exists() );
+ }
+
+ public function testMd5PrependText() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, 'Some text' );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'prependtext' => 'Alert: ',
+ 'md5' => md5( 'Alert: ' ),
+ ] );
+
+ $text = ( new WikiPage( Title::newFromText( $name ) ) )
+ ->getContent()->getNativeData();
+ $this->assertSame( 'Alert: Some text', $text );
+ }
+
+ public function testMd5AppendText() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, 'Some text' );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'appendtext' => ' is nice',
+ 'md5' => md5( ' is nice' ),
+ ] );
+
+ $text = ( new WikiPage( Title::newFromText( $name ) ) )
+ ->getContent()->getNativeData();
+ $this->assertSame( 'Some text is nice', $text );
+ }
+
+ public function testMd5PrependAndAppendText() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, 'Some text' );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'prependtext' => 'Alert: ',
+ 'appendtext' => ' is nice',
+ 'md5' => md5( 'Alert: is nice' ),
+ ] );
+
+ $text = ( new WikiPage( Title::newFromText( $name ) ) )
+ ->getContent()->getNativeData();
+ $this->assertSame( 'Alert: Some text is nice', $text );
+ }
+
+ public function testIncorrectMd5Text() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'The supplied MD5 hash was incorrect.' );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Some text',
+ 'md5' => md5( '' ),
+ ] );
+ }
+
+ public function testIncorrectMd5PrependText() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'The supplied MD5 hash was incorrect.' );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'prependtext' => 'Some ',
+ 'appendtext' => 'text',
+ 'md5' => md5( 'Some ' ),
+ ] );
+ }
+
+ public function testIncorrectMd5AppendText() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'The supplied MD5 hash was incorrect.' );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'prependtext' => 'Some ',
+ 'appendtext' => 'text',
+ 'md5' => md5( 'text' ),
+ ] );
+ }
+
+ public function testCreateOnly() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'The article you tried to create has been created already.' );
+
+ $this->editPage( $name, 'Some text' );
+ $this->assertTrue( Title::newFromText( $name )->exists() );
+
+ try {
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Some more text',
+ 'createonly' => '',
+ ] );
+ } finally {
+ // Validate that content was not changed
+ $text = ( new WikiPage( Title::newFromText( $name ) ) )
+ ->getContent()->getNativeData();
+
+ $this->assertSame( 'Some text', $text );
+ }
+ }
+
+ public function testNoCreate() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ "The page you specified doesn't exist." );
+
+ $this->assertFalse( Title::newFromText( $name )->exists() );
+
+ try {
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Some text',
+ 'nocreate' => '',
+ ] );
+ } finally {
+ $this->assertFalse( Title::newFromText( $name )->exists() );
+ }
+ }
+
+ /**
+ * Appending/prepending is currently only supported for TextContent. We
+ * test this right now, and when support is added this test should be
+ * replaced by tests that the support is correct.
+ */
+ public function testAppendWithNonTextContentHandler() {
+ $name = 'MediaWiki:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ "Can't append to pages using content model testing-nontext." );
+
+ $this->setTemporaryHook( 'ContentHandlerDefaultModelFor',
+ function ( Title $title, &$model ) use ( $name ) {
+ if ( $title->getPrefixedText() === $name ) {
+ $model = 'testing-nontext';
+ }
+ return true;
+ }
+ );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'appendtext' => 'Some text',
+ ] );
+ }
+
+ public function testAppendInMediaWikiNamespace() {
+ $name = 'MediaWiki:' . ucfirst( __FUNCTION__ );
+
+ $this->assertFalse( Title::newFromText( $name )->exists() );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'appendtext' => 'Some text',
+ ] );
+
+ $this->assertTrue( Title::newFromText( $name )->exists() );
+ }
+
+ public function testAppendInMediaWikiNamespaceWithSerializationError() {
+ $name = 'MediaWiki:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'Content serialization failed: Could not unserialize content' );
+
+ $this->setTemporaryHook( 'ContentHandlerDefaultModelFor',
+ function ( Title $title, &$model ) use ( $name ) {
+ if ( $title->getPrefixedText() === $name ) {
+ $model = 'testing-serialize-error';
+ }
+ return true;
+ }
+ );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'appendtext' => 'Some text',
+ ] );
+ }
+
+ public function testAppendNewSection() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, 'Initial content' );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'appendtext' => '== New section ==',
+ 'section' => 'new',
+ ] );
+
+ $text = ( new WikiPage( Title::newFromText( $name ) ) )
+ ->getContent()->getNativeData();
+
+ $this->assertSame( "Initial content\n\n== New section ==", $text );
+ }
+
+ public function testAppendNewSectionWithInvalidContentModel() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'Sections are not supported for content model text.' );
+
+ $this->editPage( $name, 'Initial content' );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'appendtext' => '== New section ==',
+ 'section' => 'new',
+ 'contentmodel' => 'text',
+ ] );
+ }
+
+ public function testAppendNewSectionWithTitle() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, 'Initial content' );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'sectiontitle' => 'My section',
+ 'appendtext' => 'More content',
+ 'section' => 'new',
+ ] );
+
+ $page = new WikiPage( Title::newFromText( $name ) );
+
+ $this->assertSame( "Initial content\n\n== My section ==\n\nMore content",
+ $page->getContent()->getNativeData() );
+ $this->assertSame( '/* My section */ new section',
+ $page->getRevision()->getComment() );
+ }
+
+ public function testAppendNewSectionWithSummary() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, 'Initial content' );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'appendtext' => 'More content',
+ 'section' => 'new',
+ 'summary' => 'Add new section',
+ ] );
+
+ $page = new WikiPage( Title::newFromText( $name ) );
+
+ $this->assertSame( "Initial content\n\n== Add new section ==\n\nMore content",
+ $page->getContent()->getNativeData() );
+ // EditPage actually assumes the summary is the section name here
+ $this->assertSame( '/* Add new section */ new section',
+ $page->getRevision()->getComment() );
+ }
+
+ public function testAppendNewSectionWithTitleAndSummary() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, 'Initial content' );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'sectiontitle' => 'My section',
+ 'appendtext' => 'More content',
+ 'section' => 'new',
+ 'summary' => 'Add new section',
+ ] );
+
+ $page = new WikiPage( Title::newFromText( $name ) );
+
+ $this->assertSame( "Initial content\n\n== My section ==\n\nMore content",
+ $page->getContent()->getNativeData() );
+ $this->assertSame( 'Add new section',
+ $page->getRevision()->getComment() );
+ }
+
+ public function testAppendToSection() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, "== Section 1 ==\n\nContent\n\n" .
+ "== Section 2 ==\n\nFascinating!" );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'appendtext' => ' and more content',
+ 'section' => '1',
+ ] );
+
+ $text = ( new WikiPage( Title::newFromText( $name ) ) )
+ ->getContent()->getNativeData();
+
+ $this->assertSame( "== Section 1 ==\n\nContent and more content\n\n" .
+ "== Section 2 ==\n\nFascinating!", $text );
+ }
+
+ public function testAppendToFirstSection() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, "Content\n\n== Section 1 ==\n\nFascinating!" );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'appendtext' => ' and more content',
+ 'section' => '0',
+ ] );
+
+ $text = ( new WikiPage( Title::newFromText( $name ) ) )
+ ->getContent()->getNativeData();
+
+ $this->assertSame( "Content and more content\n\n== Section 1 ==\n\n" .
+ "Fascinating!", $text );
+ }
+
+ public function testAppendToNonexistentSection() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class, 'There is no section 1.' );
+
+ $this->editPage( $name, 'Content' );
+
+ try {
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'appendtext' => ' and more content',
+ 'section' => '1',
+ ] );
+ } finally {
+ $text = ( new WikiPage( Title::newFromText( $name ) ) )
+ ->getContent()->getNativeData();
+
+ $this->assertSame( 'Content', $text );
+ }
+ }
+
+ public function testEditMalformedSection() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'The "section" parameter must be a valid section ID or "new".' );
+ $this->editPage( $name, 'Content' );
+
+ try {
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Different content',
+ 'section' => 'It is unlikely that this is valid',
+ ] );
+ } finally {
+ $text = ( new WikiPage( Title::newFromText( $name ) ) )
+ ->getContent()->getNativeData();
+
+ $this->assertSame( 'Content', $text );
+ }
+ }
+
+ public function testEditWithStartTimestamp() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+ $this->setExpectedException( ApiUsageException::class,
+ 'The page has been deleted since you fetched its timestamp.' );
+
+ $startTime = MWTimestamp::convert( TS_MW, time() - 1 );
+
+ $this->editPage( $name, 'Some text' );
+
+ $pageObj = new WikiPage( Title::newFromText( $name ) );
+ $pageObj->doDeleteArticle( 'Bye-bye' );
+
+ $this->assertFalse( $pageObj->exists() );
+
+ try {
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Different text',
+ 'starttimestamp' => $startTime,
+ ] );
+ } finally {
+ $this->assertFalse( $pageObj->exists() );
+ }
+ }
+
+ public function testEditMinor() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, 'Some text' );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Different text',
+ 'minor' => '',
+ ] );
+
+ $revisionStore = \MediaWiki\MediaWikiServices::getInstance()->getRevisionStore();
+ $revision = $revisionStore->getRevisionByTitle( Title::newFromText( $name ) );
+ $this->assertTrue( $revision->isMinor() );
+ }
+
+ public function testEditRecreate() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $startTime = MWTimestamp::convert( TS_MW, time() - 1 );
+
+ $this->editPage( $name, 'Some text' );
+
+ $pageObj = new WikiPage( Title::newFromText( $name ) );
+ $pageObj->doDeleteArticle( 'Bye-bye' );
+
+ $this->assertFalse( $pageObj->exists() );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Different text',
+ 'starttimestamp' => $startTime,
+ 'recreate' => '',
+ ] );
+
+ $this->assertTrue( Title::newFromText( $name )->exists() );
+ }
+
+ public function testEditWatch() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+ $user = self::$users['sysop']->getUser();
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Some text',
+ 'watch' => '',
+ ] );
+
+ $this->assertTrue( Title::newFromText( $name )->exists() );
+ $this->assertTrue( $user->isWatched( Title::newFromText( $name ) ) );
+ }
+
+ public function testEditUnwatch() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+ $user = self::$users['sysop']->getUser();
+ $titleObj = Title::newFromText( $name );
+
+ $user->addWatch( $titleObj );
+
+ $this->assertFalse( $titleObj->exists() );
+ $this->assertTrue( $user->isWatched( $titleObj ) );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Some text',
+ 'unwatch' => '',
+ ] );
+
+ $this->assertTrue( $titleObj->exists() );
+ $this->assertFalse( $user->isWatched( $titleObj ) );
+ }
+
+ public function testEditWithTag() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ ChangeTags::defineTag( 'custom tag' );
+
+ $revId = $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Some text',
+ 'tags' => 'custom tag',
+ ] )[0]['edit']['newrevid'];
+
+ $dbw = wfGetDB( DB_MASTER );
+ $this->assertSame( 'custom tag', $dbw->selectField(
+ 'change_tag', 'ct_tag', [ 'ct_rev_id' => $revId ], __METHOD__ ) );
+ }
+
+ public function testEditWithoutTagPermission() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'You do not have permission to apply change tags along with your changes.' );
+
+ $this->assertFalse( Title::newFromText( $name )->exists() );
+
+ ChangeTags::defineTag( 'custom tag' );
+ $this->setMwGlobals( 'wgRevokePermissions',
+ [ 'user' => [ 'applychangetags' => true ] ] );
+ try {
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Some text',
+ 'tags' => 'custom tag',
+ ] );
+ } finally {
+ $this->assertFalse( Title::newFromText( $name )->exists() );
+ }
+ }
+
+ public function testEditAbortedByHook() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'The modification you tried to make was aborted by an extension.' );
+
+ $this->hideDeprecated( 'APIEditBeforeSave hook (used in ' .
+ 'hook-APIEditBeforeSave-closure)' );
+
+ $this->setTemporaryHook( 'APIEditBeforeSave',
+ function () {
+ return false;
+ }
+ );
+
+ try {
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Some text',
+ ] );
+ } finally {
+ $this->assertFalse( Title::newFromText( $name )->exists() );
+ }
+ }
+
+ public function testEditAbortedByHookWithCustomOutput() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->hideDeprecated( 'APIEditBeforeSave hook (used in ' .
+ 'hook-APIEditBeforeSave-closure)' );
+
+ $this->setTemporaryHook( 'APIEditBeforeSave',
+ function ( $unused1, $unused2, &$r ) {
+ $r['msg'] = 'Some message';
+ return false;
+ } );
+
+ $result = $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Some text',
+ ] );
+ Wikimedia\restoreWarnings();
+
+ $this->assertSame( [ 'msg' => 'Some message', 'result' => 'Failure' ],
+ $result[0]['edit'] );
+
+ $this->assertFalse( Title::newFromText( $name )->exists() );
+ }
+
+ public function testEditAbortedByEditPageHookWithResult() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setTemporaryHook( 'EditFilterMergedContent',
+ function ( $unused1, $unused2, Status $status ) {
+ $status->apiHookResult = [ 'msg' => 'A message for you!' ];
+ return false;
+ } );
+
+ $res = $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Some text',
+ ] );
+
+ $this->assertFalse( Title::newFromText( $name )->exists() );
+ $this->assertSame( [ 'edit' => [ 'msg' => 'A message for you!',
+ 'result' => 'Failure' ] ], $res[0] );
+ }
+
+ public function testEditAbortedByEditPageHookWithNoResult() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'The modification you tried to make was aborted by an extension.' );
+
+ $this->setTemporaryHook( 'EditFilterMergedContent',
+ function () {
+ return false;
+ }
+ );
+
+ try {
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Some text',
+ ] );
+ } finally {
+ $this->assertFalse( Title::newFromText( $name )->exists() );
+ }
+ }
+
+ public function testEditWhileBlocked() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'You have been blocked from editing.' );
+
+ $block = new Block( [
+ 'address' => self::$users['sysop']->getUser()->getName(),
+ 'by' => self::$users['sysop']->getUser()->getId(),
+ 'reason' => 'Capriciousness',
+ 'timestamp' => '19370101000000',
+ 'expiry' => 'infinity',
+ ] );
+ $block->insert();
+
+ try {
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Some text',
+ ] );
+ } finally {
+ $block->delete();
+ self::$users['sysop']->getUser()->clearInstanceCache();
+ }
+ }
+
+ public function testEditWhileReadOnly() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'The wiki is currently in read-only mode.' );
+
+ $svc = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
+ $svc->setReason( "Read-only for testing" );
+
+ try {
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Some text',
+ ] );
+ } finally {
+ $svc->setReason( false );
+ }
+ }
+
+ public function testCreateImageRedirectAnon() {
+ $name = 'File:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ "Anonymous users can't create image redirects." );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => '#REDIRECT [[File:Other file.png]]',
+ ], null, new User() );
+ }
+
+ public function testCreateImageRedirectLoggedIn() {
+ $name = 'File:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ "You don't have permission to create image redirects." );
+
+ $this->setMwGlobals( 'wgRevokePermissions',
+ [ 'user' => [ 'upload' => true ] ] );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => '#REDIRECT [[File:Other file.png]]',
+ ] );
+ }
+
+ public function testTooBigEdit() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'The content you supplied exceeds the article size limit of 1 kilobyte.' );
+
+ $this->setMwGlobals( 'wgMaxArticleSize', 1 );
+
+ $text = str_repeat( '!', 1025 );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => $text,
+ ] );
+ }
+
+ public function testProhibitedAnonymousEdit() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ 'The action you have requested is limited to users in the group: ' );
+
+ $this->setMwGlobals( 'wgRevokePermissions', [ '*' => [ 'edit' => true ] ] );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Some text',
+ ], null, new User() );
+ }
+
+ public function testProhibitedChangeContentModel() {
+ $name = 'Help:' . ucfirst( __FUNCTION__ );
+
+ $this->setExpectedException( ApiUsageException::class,
+ "You don't have permission to change the content model of a page." );
+
+ $this->setMwGlobals( 'wgRevokePermissions',
+ [ 'user' => [ 'editcontentmodel' => true ] ] );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => 'Some text',
+ 'contentmodel' => 'json',
+ ] );
}
}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiErrorFormatterTest.php b/www/wiki/tests/phpunit/includes/api/ApiErrorFormatterTest.php
index d47481cb..aa579ab0 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiErrorFormatterTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiErrorFormatterTest.php
@@ -575,11 +575,11 @@ class ApiErrorFormatterTest extends MediaWikiLangTestCase {
}
public static function provideGetMessageFromException() {
- MediaWiki\suppressWarnings();
+ Wikimedia\suppressWarnings();
$usageException = new UsageException(
'<b>Something broke!</b>', 'ue-code', 0, [ 'xxx' => 'yyy', 'baz' => 23 ]
);
- MediaWiki\restoreWarnings();
+ Wikimedia\restoreWarnings();
return [
'Normal exception' => [
diff --git a/www/wiki/tests/phpunit/includes/api/ApiLoginTest.php b/www/wiki/tests/phpunit/includes/api/ApiLoginTest.php
index 3cf1fdee..d382c83c 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiLoginTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiLoginTest.php
@@ -142,7 +142,7 @@ class ApiLoginTest extends ApiTestCase {
libxml_use_internal_errors( true );
$sxe = simplexml_load_string( $req->getContent() );
$this->assertNotInternalType( "bool", $sxe );
- $this->assertThat( $sxe, $this->isInstanceOf( "SimpleXMLElement" ) );
+ $this->assertThat( $sxe, $this->isInstanceOf( SimpleXMLElement::class ) );
$this->assertNotInternalType( "null", $sxe->login[0] );
$a = $sxe->login[0]->attributes()->result[0];
@@ -282,4 +282,20 @@ class ApiLoginTest extends ApiTestCase {
$this->assertEquals( 'Success', $a );
}
+ public function testLoginWithNoSameOriginSecurity() {
+ $this->setTemporaryHook( 'RequestHasSameOriginSecurity',
+ function () {
+ return false;
+ }
+ );
+
+ $result = $this->doApiRequest( [
+ 'action' => 'login',
+ ] )[0]['login'];
+
+ $this->assertSame( [
+ 'result' => 'Aborted',
+ 'reason' => 'Cannot log in when the same-origin policy is not applied.',
+ ], $result );
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiLogoutTest.php b/www/wiki/tests/phpunit/includes/api/ApiLogoutTest.php
new file mode 100644
index 00000000..8254fdba
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/api/ApiLogoutTest.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @group API
+ * @group Database
+ * @group medium
+ *
+ * @covers ApiLogout
+ */
+class ApiLogoutTest extends ApiTestCase {
+
+ protected function setUp() {
+ global $wgRequest, $wgUser;
+
+ parent::setUp();
+
+ // Link the user to the Session properly so User::doLogout() doesn't complain.
+ $wgRequest->getSession()->setUser( $wgUser );
+ $wgUser = User::newFromSession( $wgRequest );
+ $this->apiContext->setUser( $wgUser );
+ }
+
+ public function testUserLogoutBadToken() {
+ global $wgUser;
+
+ $this->setExpectedApiException( 'apierror-badtoken' );
+
+ try {
+ $token = 'invalid token';
+ $this->doUserLogout( $token );
+ } finally {
+ $this->assertTrue( $wgUser->isLoggedIn(), 'not logged out' );
+ }
+ }
+
+ public function testUserLogout() {
+ global $wgUser;
+
+ $this->assertTrue( $wgUser->isLoggedIn(), 'sanity check' );
+ $token = $this->getUserCsrfTokenFromApi();
+ $this->doUserLogout( $token );
+ $this->assertFalse( $wgUser->isLoggedIn() );
+ }
+
+ public function testUserLogoutWithWebToken() {
+ global $wgUser, $wgRequest;
+
+ $this->assertTrue( $wgUser->isLoggedIn(), 'sanity check' );
+
+ // Logic copied from SkinTemplate.
+ $token = $wgUser->getEditToken( 'logoutToken', $wgRequest );
+
+ $this->doUserLogout( $token );
+ $this->assertFalse( $wgUser->isLoggedIn() );
+ }
+
+ private function getUserCsrfTokenFromApi() {
+ $retToken = $this->doApiRequest( [
+ 'action' => 'query',
+ 'meta' => 'tokens',
+ 'type' => 'csrf'
+ ] );
+
+ $this->assertArrayNotHasKey( 'warnings', $retToken );
+
+ return $retToken[0]['query']['tokens']['csrftoken'];
+ }
+
+ private function doUserLogout( $logoutToken ) {
+ return $this->doApiRequest( [
+ 'action' => 'logout',
+ 'token' => $logoutToken
+ ] );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiMainTest.php b/www/wiki/tests/phpunit/includes/api/ApiMainTest.php
index ad334e94..d17334bb 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiMainTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiMainTest.php
@@ -24,6 +24,356 @@ class ApiMainTest extends ApiTestCase {
$this->assertArrayHasKey( 'query', $data );
}
+ public function testApiNoParam() {
+ $api = new ApiMain();
+ $api->execute();
+ $data = $api->getResult()->getResultData();
+ $this->assertInternalType( 'array', $data );
+ }
+
+ /**
+ * ApiMain behaves differently if passed a FauxRequest (mInternalMode set
+ * to true) or a proper WebRequest (mInternalMode false). For most tests
+ * we can just set mInternalMode to false using TestingAccessWrapper, but
+ * this doesn't work for the constructor. This method returns an ApiMain
+ * that's been set up in non-internal mode.
+ *
+ * Note that calling execute() will print to the console. Wrap it in
+ * ob_start()/ob_end_clean() to prevent this.
+ *
+ * @param array $requestData Query parameters for the WebRequest
+ * @param array $headers Headers for the WebRequest
+ */
+ private function getNonInternalApiMain( array $requestData, array $headers = [] ) {
+ $req = $this->getMockBuilder( WebRequest::class )
+ ->setMethods( [ 'response', 'getRawIP' ] )
+ ->getMock();
+ $response = new FauxResponse();
+ $req->method( 'response' )->willReturn( $response );
+ $req->method( 'getRawIP' )->willReturn( '127.0.0.1' );
+
+ $wrapper = TestingAccessWrapper::newFromObject( $req );
+ $wrapper->data = $requestData;
+ if ( $headers ) {
+ $wrapper->headers = $headers;
+ }
+
+ return new ApiMain( $req );
+ }
+
+ public function testUselang() {
+ global $wgLang;
+
+ $api = $this->getNonInternalApiMain( [
+ 'action' => 'query',
+ 'meta' => 'siteinfo',
+ 'uselang' => 'fr',
+ ] );
+
+ ob_start();
+ $api->execute();
+ ob_end_clean();
+
+ $this->assertSame( 'fr', $wgLang->getCode() );
+ }
+
+ public function testNonWhitelistedCorsWithCookies() {
+ $logFile = $this->getNewTempFile();
+
+ $this->mergeMwGlobalArrayValue( '_COOKIE', [ 'forceHTTPS' => '1' ] );
+ $logger = new TestLogger( true );
+ $this->setLogger( 'cors', $logger );
+
+ $api = $this->getNonInternalApiMain( [
+ 'action' => 'query',
+ 'meta' => 'siteinfo',
+ // For some reason multiple origins (which are not allowed in the
+ // WHATWG Fetch spec that supersedes the RFC) are always considered to
+ // be problematic.
+ ], [ 'ORIGIN' => 'https://www.example.com https://www.com.example' ] );
+
+ $this->assertSame(
+ [ [ Psr\Log\LogLevel::WARNING, 'Non-whitelisted CORS request with session cookies' ] ],
+ $logger->getBuffer()
+ );
+ }
+
+ public function testSuppressedLogin() {
+ global $wgUser;
+ $origUser = $wgUser;
+
+ $api = $this->getNonInternalApiMain( [
+ 'action' => 'query',
+ 'meta' => 'siteinfo',
+ 'origin' => '*',
+ ] );
+
+ ob_start();
+ $api->execute();
+ ob_end_clean();
+
+ $this->assertNotSame( $origUser, $wgUser );
+ $this->assertSame( 'true', $api->getContext()->getRequest()->response()
+ ->getHeader( 'MediaWiki-Login-Suppressed' ) );
+ }
+
+ public function testSetContinuationManager() {
+ $api = new ApiMain();
+ $manager = $this->createMock( ApiContinuationManager::class );
+ $api->setContinuationManager( $manager );
+ $this->assertTrue( true, 'No exception' );
+ return [ $api, $manager ];
+ }
+
+ /**
+ * @depends testSetContinuationManager
+ */
+ public function testSetContinuationManagerTwice( $args ) {
+ $this->setExpectedException( UnexpectedValueException::class,
+ 'ApiMain::setContinuationManager: tried to set manager from ' .
+ 'when a manager is already set from ' );
+
+ list( $api, $manager ) = $args;
+ $api->setContinuationManager( $manager );
+ }
+
+ public function testSetCacheModeUnrecognized() {
+ $api = new ApiMain();
+ $api->setCacheMode( 'unrecognized' );
+ $this->assertSame(
+ 'private',
+ TestingAccessWrapper::newFromObject( $api )->mCacheMode,
+ 'Unrecognized params must be silently ignored'
+ );
+ }
+
+ public function testSetCacheModePrivateWiki() {
+ $this->setGroupPermissions( '*', 'read', false );
+
+ $wrappedApi = TestingAccessWrapper::newFromObject( new ApiMain() );
+ $wrappedApi->setCacheMode( 'public' );
+ $this->assertSame( 'private', $wrappedApi->mCacheMode );
+ $wrappedApi->setCacheMode( 'anon-public-user-private' );
+ $this->assertSame( 'private', $wrappedApi->mCacheMode );
+ }
+
+ public function testAddRequestedFieldsRequestId() {
+ $req = new FauxRequest( [
+ 'action' => 'query',
+ 'meta' => 'siteinfo',
+ 'requestid' => '123456',
+ ] );
+ $api = new ApiMain( $req );
+ $api->execute();
+ $this->assertSame( '123456', $api->getResult()->getResultData()['requestid'] );
+ }
+
+ public function testAddRequestedFieldsCurTimestamp() {
+ $req = new FauxRequest( [
+ 'action' => 'query',
+ 'meta' => 'siteinfo',
+ 'curtimestamp' => '',
+ ] );
+ $api = new ApiMain( $req );
+ $api->execute();
+ $timestamp = $api->getResult()->getResultData()['curtimestamp'];
+ $this->assertLessThanOrEqual( 1, abs( strtotime( $timestamp ) - time() ) );
+ }
+
+ public function testAddRequestedFieldsResponseLangInfo() {
+ $req = new FauxRequest( [
+ 'action' => 'query',
+ 'meta' => 'siteinfo',
+ // errorlang is ignored if errorformat is not specified
+ 'errorformat' => 'plaintext',
+ 'uselang' => 'FR',
+ 'errorlang' => 'ja',
+ 'responselanginfo' => '',
+ ] );
+ $api = new ApiMain( $req );
+ $api->execute();
+ $data = $api->getResult()->getResultData();
+ $this->assertSame( 'fr', $data['uselang'] );
+ $this->assertSame( 'ja', $data['errorlang'] );
+ }
+
+ public function testSetupModuleUnknown() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'Unrecognized value for parameter "action": unknownaction.' );
+
+ $req = new FauxRequest( [ 'action' => 'unknownaction' ] );
+ $api = new ApiMain( $req );
+ $api->execute();
+ }
+
+ public function testSetupModuleNoTokenProvided() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'The "token" parameter must be set.' );
+
+ $req = new FauxRequest( [
+ 'action' => 'edit',
+ 'title' => 'New page',
+ 'text' => 'Some text',
+ ] );
+ $api = new ApiMain( $req );
+ $api->execute();
+ }
+
+ public function testSetupModuleInvalidTokenProvided() {
+ $this->setExpectedException( ApiUsageException::class, 'Invalid CSRF token.' );
+
+ $req = new FauxRequest( [
+ 'action' => 'edit',
+ 'title' => 'New page',
+ 'text' => 'Some text',
+ 'token' => "This isn't a real token!",
+ ] );
+ $api = new ApiMain( $req );
+ $api->execute();
+ }
+
+ public function testSetupModuleNeedsTokenTrue() {
+ $this->setExpectedException( MWException::class,
+ "Module 'testmodule' must be updated for the new token handling. " .
+ "See documentation for ApiBase::needsToken for details." );
+
+ $mock = $this->createMock( ApiBase::class );
+ $mock->method( 'getModuleName' )->willReturn( 'testmodule' );
+ $mock->method( 'needsToken' )->willReturn( true );
+
+ $api = new ApiMain( new FauxRequest( [ 'action' => 'testmodule' ] ) );
+ $api->getModuleManager()->addModule( 'testmodule', 'action', get_class( $mock ),
+ function () use ( $mock ) {
+ return $mock;
+ }
+ );
+ $api->execute();
+ }
+
+ public function testSetupModuleNeedsTokenNeedntBePosted() {
+ $this->setExpectedException( MWException::class,
+ "Module 'testmodule' must require POST to use tokens." );
+
+ $mock = $this->createMock( ApiBase::class );
+ $mock->method( 'getModuleName' )->willReturn( 'testmodule' );
+ $mock->method( 'needsToken' )->willReturn( 'csrf' );
+ $mock->method( 'mustBePosted' )->willReturn( false );
+
+ $api = new ApiMain( new FauxRequest( [ 'action' => 'testmodule' ] ) );
+ $api->getModuleManager()->addModule( 'testmodule', 'action', get_class( $mock ),
+ function () use ( $mock ) {
+ return $mock;
+ }
+ );
+ $api->execute();
+ }
+
+ public function testCheckMaxLagFailed() {
+ // It's hard to mock the LoadBalancer properly, so instead we'll mock
+ // checkMaxLag (which is tested directly in other tests below).
+ $req = new FauxRequest( [
+ 'action' => 'query',
+ 'meta' => 'siteinfo',
+ ] );
+
+ $mock = $this->getMockBuilder( ApiMain::class )
+ ->setConstructorArgs( [ $req ] )
+ ->setMethods( [ 'checkMaxLag' ] )
+ ->getMock();
+ $mock->method( 'checkMaxLag' )->willReturn( false );
+
+ $mock->execute();
+
+ $this->assertArrayNotHasKey( 'query', $mock->getResult()->getResultData() );
+ }
+
+ public function testCheckConditionalRequestHeadersFailed() {
+ // The detailed checking of all cases of checkConditionalRequestHeaders
+ // is below in testCheckConditionalRequestHeaders(), which calls the
+ // method directly. Here we just check that it will stop execution if
+ // it does fail.
+ $now = time();
+
+ $this->setMwGlobals( 'wgCacheEpoch', '20030516000000' );
+
+ $mock = $this->createMock( ApiBase::class );
+ $mock->method( 'getModuleName' )->willReturn( 'testmodule' );
+ $mock->method( 'getConditionalRequestData' )
+ ->willReturn( wfTimestamp( TS_MW, $now - 3600 ) );
+ $mock->expects( $this->exactly( 0 ) )->method( 'execute' );
+
+ $req = new FauxRequest( [
+ 'action' => 'testmodule',
+ ] );
+ $req->setHeader( 'If-Modified-Since', wfTimestamp( TS_RFC2822, $now - 3600 ) );
+ $req->setRequestURL( "http://localhost" );
+
+ $api = new ApiMain( $req );
+ $api->getModuleManager()->addModule( 'testmodule', 'action', get_class( $mock ),
+ function () use ( $mock ) {
+ return $mock;
+ }
+ );
+
+ $wrapper = TestingAccessWrapper::newFromObject( $api );
+ $wrapper->mInternalMode = false;
+
+ ob_start();
+ $api->execute();
+ ob_end_clean();
+ }
+
+ private function doTestCheckMaxLag( $lag ) {
+ $mockLB = $this->getMockBuilder( LoadBalancer::class )
+ ->disableOriginalConstructor()
+ ->setMethods( [ 'getMaxLag', '__destruct' ] )
+ ->getMock();
+ $mockLB->method( 'getMaxLag' )->willReturn( [ 'somehost', $lag ] );
+ $this->setService( 'DBLoadBalancer', $mockLB );
+
+ $req = new FauxRequest();
+
+ $api = new ApiMain( $req );
+ $wrapper = TestingAccessWrapper::newFromObject( $api );
+
+ $mockModule = $this->createMock( ApiBase::class );
+ $mockModule->method( 'shouldCheckMaxLag' )->willReturn( true );
+
+ try {
+ $wrapper->checkMaxLag( $mockModule, [ 'maxlag' => 3 ] );
+ } finally {
+ if ( $lag > 3 ) {
+ $this->assertSame( '5', $req->response()->getHeader( 'Retry-After' ) );
+ $this->assertSame( (string)$lag, $req->response()->getHeader( 'X-Database-Lag' ) );
+ }
+ }
+ }
+
+ public function testCheckMaxLagOkay() {
+ $this->doTestCheckMaxLag( 3 );
+
+ // No exception, we're happy
+ $this->assertTrue( true );
+ }
+
+ public function testCheckMaxLagExceeded() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'Waiting for a database server: 4 seconds lagged.' );
+
+ $this->setMwGlobals( 'wgShowHostnames', false );
+
+ $this->doTestCheckMaxLag( 4 );
+ }
+
+ public function testCheckMaxLagExceededWithHostNames() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'Waiting for somehost: 4 seconds lagged.' );
+
+ $this->setMwGlobals( 'wgShowHostnames', true );
+
+ $this->doTestCheckMaxLag( 4 );
+ }
+
public static function provideAssert() {
return [
[ false, [], 'user', 'assertuserfailed' ],
@@ -37,7 +387,6 @@ class ApiMainTest extends ApiTestCase {
/**
* Tests the assert={user|bot} functionality
*
- * @covers ApiMain::checkAsserts
* @dataProvider provideAssert
* @param bool $registered
* @param array $rights
@@ -66,8 +415,6 @@ class ApiMainTest extends ApiTestCase {
/**
* Tests the assertuser= functionality
- *
- * @covers ApiMain::checkAsserts
*/
public function testAssertUser() {
$user = $this->getTestUser()->getUser();
@@ -107,17 +454,21 @@ class ApiMainTest extends ApiTestCase {
/**
* Test HTTP precondition headers
*
- * @covers ApiMain::checkConditionalRequestHeaders
* @dataProvider provideCheckConditionalRequestHeaders
* @param array $headers HTTP headers
* @param array $conditions Return data for ApiBase::getConditionalRequestData
* @param int $status Expected response status
- * @param bool $post Request is a POST
+ * @param array $options Array of options:
+ * post => true Request is a POST
+ * cdn => true CDN is enabled ($wgUseSquid)
*/
public function testCheckConditionalRequestHeaders(
- $headers, $conditions, $status, $post = false
+ $headers, $conditions, $status, $options = []
) {
- $request = new FauxRequest( [ 'action' => 'query', 'meta' => 'siteinfo' ], $post );
+ $request = new FauxRequest(
+ [ 'action' => 'query', 'meta' => 'siteinfo' ],
+ !empty( $options['post'] )
+ );
$request->setHeaders( $headers );
$request->response()->statusHeader( 200 ); // Why doesn't it default?
@@ -126,7 +477,14 @@ class ApiMainTest extends ApiTestCase {
$priv = TestingAccessWrapper::newFromObject( $api );
$priv->mInternalMode = false;
- $module = $this->getMockBuilder( 'ApiBase' )
+ if ( !empty( $options['cdn'] ) ) {
+ $this->setMwGlobals( 'wgUseSquid', true );
+ }
+
+ // Can't do this in TestSetup.php because Setup.php will override it
+ $this->setMwGlobals( 'wgCacheEpoch', '20030516000000' );
+
+ $module = $this->getMockBuilder( ApiBase::class )
->setConstructorArgs( [ $api, 'mock' ] )
->setMethods( [ 'getConditionalRequestData' ] )
->getMockForAbstractClass();
@@ -143,65 +501,99 @@ class ApiMainTest extends ApiTestCase {
}
public static function provideCheckConditionalRequestHeaders() {
+ global $wgSquidMaxage;
$now = time();
return [
// Non-existing from module is ignored
- [ [ 'If-None-Match' => '"foo", "bar"' ], [], 200 ],
- [ [ 'If-Modified-Since' => 'Tue, 18 Aug 2015 00:00:00 GMT' ], [], 200 ],
+ 'If-None-Match' => [ [ 'If-None-Match' => '"foo", "bar"' ], [], 200 ],
+ 'If-Modified-Since' =>
+ [ [ 'If-Modified-Since' => 'Tue, 18 Aug 2015 00:00:00 GMT' ], [], 200 ],
// No headers
- [
- [],
- [
- 'etag' => '""',
- 'last-modified' => '20150815000000',
- ],
- 200
- ],
+ 'No headers' => [ [], [ 'etag' => '""', 'last-modified' => '20150815000000', ], 200 ],
// Basic If-None-Match
- [ [ 'If-None-Match' => '"foo", "bar"' ], [ 'etag' => '"bar"' ], 304 ],
- [ [ 'If-None-Match' => '"foo", "bar"' ], [ 'etag' => '"baz"' ], 200 ],
- [ [ 'If-None-Match' => '"foo"' ], [ 'etag' => 'W/"foo"' ], 304 ],
- [ [ 'If-None-Match' => 'W/"foo"' ], [ 'etag' => '"foo"' ], 304 ],
- [ [ 'If-None-Match' => 'W/"foo"' ], [ 'etag' => 'W/"foo"' ], 304 ],
+ 'If-None-Match with matching etag' =>
+ [ [ 'If-None-Match' => '"foo", "bar"' ], [ 'etag' => '"bar"' ], 304 ],
+ 'If-None-Match with non-matching etag' =>
+ [ [ 'If-None-Match' => '"foo", "bar"' ], [ 'etag' => '"baz"' ], 200 ],
+ 'Strong If-None-Match with weak matching etag' =>
+ [ [ 'If-None-Match' => '"foo"' ], [ 'etag' => 'W/"foo"' ], 304 ],
+ 'Weak If-None-Match with strong matching etag' =>
+ [ [ 'If-None-Match' => 'W/"foo"' ], [ 'etag' => '"foo"' ], 304 ],
+ 'Weak If-None-Match with weak matching etag' =>
+ [ [ 'If-None-Match' => 'W/"foo"' ], [ 'etag' => 'W/"foo"' ], 304 ],
- // Pointless, but supported
- [ [ 'If-None-Match' => '*' ], [], 304 ],
+ // Pointless for GET, but supported
+ 'If-None-Match: *' => [ [ 'If-None-Match' => '*' ], [], 304 ],
// Basic If-Modified-Since
- [ [ 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ],
- [ 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ], 304 ],
- [ [ 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ],
- [ 'last-modified' => wfTimestamp( TS_MW, $now ) ], 304 ],
- [ [ 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ],
- [ 'last-modified' => wfTimestamp( TS_MW, $now + 1 ) ], 200 ],
+ 'If-Modified-Since, modified one second earlier' =>
+ [ [ 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ],
+ [ 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ], 304 ],
+ 'If-Modified-Since, modified now' =>
+ [ [ 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ],
+ [ 'last-modified' => wfTimestamp( TS_MW, $now ) ], 304 ],
+ 'If-Modified-Since, modified one second later' =>
+ [ [ 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ],
+ [ 'last-modified' => wfTimestamp( TS_MW, $now + 1 ) ], 200 ],
// If-Modified-Since ignored when If-None-Match is given too
- [ [ 'If-None-Match' => '""', 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ],
- [ 'etag' => '"x"', 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ], 200 ],
- [ [ 'If-None-Match' => '""', 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ],
- [ 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ], 304 ],
+ 'Non-matching If-None-Match and matching If-Modified-Since' =>
+ [ [ 'If-None-Match' => '""',
+ 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ],
+ [ 'etag' => '"x"', 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ], 200 ],
+ 'Non-matching If-None-Match and matching If-Modified-Since with no ETag' =>
+ [
+ [
+ 'If-None-Match' => '""',
+ 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now )
+ ],
+ [ 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ],
+ 304
+ ],
// Ignored for POST
- [ [ 'If-None-Match' => '"foo", "bar"' ], [ 'etag' => '"bar"' ], 200, true ],
- [ [ 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ],
- [ 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ], 200, true ],
+ 'Matching If-None-Match with POST' =>
+ [ [ 'If-None-Match' => '"foo", "bar"' ], [ 'etag' => '"bar"' ], 200,
+ [ 'post' => true ] ],
+ 'Matching If-Modified-Since with POST' =>
+ [ [ 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ],
+ [ 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ], 200,
+ [ 'post' => true ] ],
// Other date formats allowed by the RFC
- [ [ 'If-Modified-Since' => gmdate( 'l, d-M-y H:i:s', $now ) . ' GMT' ],
- [ 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ], 304 ],
- [ [ 'If-Modified-Since' => gmdate( 'D M j H:i:s Y', $now ) ],
- [ 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ], 304 ],
+ 'If-Modified-Since with alternate date format 1' =>
+ [ [ 'If-Modified-Since' => gmdate( 'l, d-M-y H:i:s', $now ) . ' GMT' ],
+ [ 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ], 304 ],
+ 'If-Modified-Since with alternate date format 2' =>
+ [ [ 'If-Modified-Since' => gmdate( 'D M j H:i:s Y', $now ) ],
+ [ 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ], 304 ],
// Old browser extension to HTTP/1.0
- [ [ 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) . '; length=123' ],
- [ 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ], 304 ],
+ 'If-Modified-Since with length' =>
+ [ [ 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) . '; length=123' ],
+ [ 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ], 304 ],
// Invalid date formats should be ignored
- [ [ 'If-Modified-Since' => gmdate( 'Y-m-d H:i:s', $now ) . ' GMT' ],
- [ 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ], 200 ],
+ 'If-Modified-Since with invalid date format' =>
+ [ [ 'If-Modified-Since' => gmdate( 'Y-m-d H:i:s', $now ) . ' GMT' ],
+ [ 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ], 200 ],
+ 'If-Modified-Since with entirely unparseable date' =>
+ [ [ 'If-Modified-Since' => 'a potato' ],
+ [ 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ], 200 ],
+
+ // Anything before $wgSquidMaxage seconds ago should be considered
+ // expired.
+ 'If-Modified-Since with CDN post-expiry' =>
+ [ [ 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now - $wgSquidMaxage * 2 ) ],
+ [ 'last-modified' => wfTimestamp( TS_MW, $now - $wgSquidMaxage * 3 ) ],
+ 200, [ 'cdn' => true ] ],
+ 'If-Modified-Since with CDN pre-expiry' =>
+ [ [ 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now - $wgSquidMaxage / 2 ) ],
+ [ 'last-modified' => wfTimestamp( TS_MW, $now - $wgSquidMaxage * 3 ) ],
+ 304, [ 'cdn' => true ] ],
];
}
@@ -223,7 +615,7 @@ class ApiMainTest extends ApiTestCase {
$priv = TestingAccessWrapper::newFromObject( $api );
$priv->mInternalMode = false;
- $module = $this->getMockBuilder( 'ApiBase' )
+ $module = $this->getMockBuilder( ApiBase::class )
->setConstructorArgs( [ $api, 'mock' ] )
->setMethods( [ 'getConditionalRequestData' ] )
->getMockForAbstractClass();
@@ -277,9 +669,101 @@ class ApiMainTest extends ApiTestCase {
];
}
- /**
- * @covers ApiMain::lacksSameOriginSecurity
- */
+ public function testCheckExecutePermissionsReadProhibited() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'You need read permission to use this module.' );
+
+ $this->setGroupPermissions( '*', 'read', false );
+
+ $main = new ApiMain( new FauxRequest( [ 'action' => 'query', 'meta' => 'siteinfo' ] ) );
+ $main->execute();
+ }
+
+ public function testCheckExecutePermissionWriteDisabled() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'Editing of this wiki through the API is disabled. Make sure the ' .
+ '"$wgEnableWriteAPI=true;" statement is included in the wiki\'s ' .
+ '"LocalSettings.php" file.' );
+ $main = new ApiMain( new FauxRequest( [
+ 'action' => 'edit',
+ 'title' => 'Some page',
+ 'text' => 'Some text',
+ 'token' => '+\\',
+ ] ) );
+ $main->execute();
+ }
+
+ public function testCheckExecutePermissionWriteApiProhibited() {
+ $this->setExpectedException( ApiUsageException::class,
+ "You're not allowed to edit this wiki through the API." );
+ $this->setGroupPermissions( '*', 'writeapi', false );
+
+ $main = new ApiMain( new FauxRequest( [
+ 'action' => 'edit',
+ 'title' => 'Some page',
+ 'text' => 'Some text',
+ 'token' => '+\\',
+ ] ), /* enableWrite = */ true );
+ $main->execute();
+ }
+
+ public function testCheckExecutePermissionPromiseNonWrite() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'The "Promise-Non-Write-API-Action" HTTP header cannot be sent ' .
+ 'to write-mode API modules.' );
+
+ $req = new FauxRequest( [
+ 'action' => 'edit',
+ 'title' => 'Some page',
+ 'text' => 'Some text',
+ 'token' => '+\\',
+ ] );
+ $req->setHeaders( [ 'Promise-Non-Write-API-Action' => '1' ] );
+ $main = new ApiMain( $req, /* enableWrite = */ true );
+ $main->execute();
+ }
+
+ public function testCheckExecutePermissionHookAbort() {
+ $this->setExpectedException( ApiUsageException::class, 'Main Page' );
+
+ $this->setTemporaryHook( 'ApiCheckCanExecute', function ( $unused1, $unused2, &$message ) {
+ $message = 'mainpage';
+ return false;
+ } );
+
+ $main = new ApiMain( new FauxRequest( [
+ 'action' => 'edit',
+ 'title' => 'Some page',
+ 'text' => 'Some text',
+ 'token' => '+\\',
+ ] ), /* enableWrite = */ true );
+ $main->execute();
+ }
+
+ public function testGetValUnsupportedArray() {
+ $main = new ApiMain( new FauxRequest( [
+ 'action' => 'query',
+ 'meta' => 'siteinfo',
+ 'siprop' => [ 'general', 'namespaces' ],
+ ] ) );
+ $this->assertSame( 'myDefault', $main->getVal( 'siprop', 'myDefault' ) );
+ $main->execute();
+ $this->assertSame( 'Parameter "siprop" uses unsupported PHP array syntax.',
+ $main->getResult()->getResultData()['warnings']['main']['warnings'] );
+ }
+
+ public function testReportUnusedParams() {
+ $main = new ApiMain( new FauxRequest( [
+ 'action' => 'query',
+ 'meta' => 'siteinfo',
+ 'unusedparam' => 'unusedval',
+ 'anotherunusedparam' => 'anotherval',
+ ] ) );
+ $main->execute();
+ $this->assertSame( 'Unrecognized parameters: unusedparam, anotherunusedparam.',
+ $main->getResult()->getResultData()['warnings']['main']['warnings'] );
+ }
+
public function testLacksSameOriginSecurity() {
// Basic test
$main = new ApiMain( new FauxRequest( [ 'action' => 'query', 'meta' => 'siteinfo' ] ) );
@@ -309,7 +793,7 @@ class ApiMainTest extends ApiTestCase {
/**
* Test proper creation of the ApiErrorFormatter
- * @covers ApiMain::__construct
+ *
* @dataProvider provideApiErrorFormatterCreation
* @param array $request Request parameters
* @param array $expect Expected data
@@ -443,8 +927,6 @@ class ApiMainTest extends ApiTestCase {
}
/**
- * @covers ApiMain::errorMessagesFromException
- * @covers ApiMain::substituteResultWithError
* @dataProvider provideExceptionErrors
* @param Exception $exception
* @param array $expectReturn
@@ -491,7 +973,7 @@ class ApiMainTest extends ApiTestCase {
)->inLanguage( 'en' )->useDatabase( false )->text();
$dbex = new DBQueryError(
- $this->createMock( 'IDatabase' ),
+ $this->createMock( \Wikimedia\Rdbms\IDatabase::class ),
'error', 1234, 'SELECT 1', __METHOD__ );
$dbtrace = wfMessage( 'api-exception-trace',
get_class( $dbex ),
@@ -500,9 +982,9 @@ class ApiMainTest extends ApiTestCase {
MWExceptionHandler::getRedactedTraceAsString( $dbex )
)->inLanguage( 'en' )->useDatabase( false )->text();
- MediaWiki\suppressWarnings();
+ Wikimedia\suppressWarnings();
$usageEx = new UsageException( 'Usage exception!', 'ue', 0, [ 'foo' => 'bar' ] );
- MediaWiki\restoreWarnings();
+ Wikimedia\restoreWarnings();
$apiEx1 = new ApiUsageException( null,
StatusValue::newFatal( new ApiRawMessage( 'An error', 'sv-error1' ) ) );
diff --git a/www/wiki/tests/phpunit/includes/api/ApiModuleManagerTest.php b/www/wiki/tests/phpunit/includes/api/ApiModuleManagerTest.php
index be17bba2..b01b90e8 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiModuleManagerTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiModuleManagerTest.php
@@ -24,21 +24,21 @@ class ApiModuleManagerTest extends MediaWikiTestCase {
'plain class' => [
'login',
'action',
- 'ApiLogin',
+ ApiLogin::class,
null,
],
'with factory' => [
'login',
'action',
- 'ApiLogin',
+ ApiLogin::class,
[ $this, 'newApiLogin' ],
],
'with closure' => [
'logout',
'action',
- 'ApiLogout',
+ ApiLogout::class,
function ( ApiMain $main, $action ) {
return new ApiLogout( $main, $action );
},
@@ -66,8 +66,8 @@ class ApiModuleManagerTest extends MediaWikiTestCase {
'simple' => [
[
- 'login' => 'ApiLogin',
- 'logout' => 'ApiLogout',
+ 'login' => ApiLogin::class,
+ 'logout' => ApiLogout::class,
],
'action',
],
@@ -75,11 +75,11 @@ class ApiModuleManagerTest extends MediaWikiTestCase {
'with factories' => [
[
'login' => [
- 'class' => 'ApiLogin',
+ 'class' => ApiLogin::class,
'factory' => [ $this, 'newApiLogin' ],
],
'logout' => [
- 'class' => 'ApiLogout',
+ 'class' => ApiLogout::class,
'factory' => function ( ApiMain $main, $action ) {
return new ApiLogout( $main, $action );
},
@@ -107,14 +107,14 @@ class ApiModuleManagerTest extends MediaWikiTestCase {
public function getModuleProvider() {
$modules = [
- 'feedrecentchanges' => 'ApiFeedRecentChanges',
- 'feedcontributions' => [ 'class' => 'ApiFeedContributions' ],
+ 'feedrecentchanges' => ApiFeedRecentChanges::class,
+ 'feedcontributions' => [ 'class' => ApiFeedContributions::class ],
'login' => [
- 'class' => 'ApiLogin',
+ 'class' => ApiLogin::class,
'factory' => [ $this, 'newApiLogin' ],
],
'logout' => [
- 'class' => 'ApiLogout',
+ 'class' => ApiLogout::class,
'factory' => function ( ApiMain $main, $action ) {
return new ApiLogout( $main, $action );
},
@@ -125,25 +125,25 @@ class ApiModuleManagerTest extends MediaWikiTestCase {
'legacy entry' => [
$modules,
'feedrecentchanges',
- 'ApiFeedRecentChanges',
+ ApiFeedRecentChanges::class,
],
'just a class' => [
$modules,
'feedcontributions',
- 'ApiFeedContributions',
+ ApiFeedContributions::class,
],
'with factory' => [
$modules,
'login',
- 'ApiLogin',
+ ApiLogin::class,
],
'with closure' => [
$modules,
'logout',
- 'ApiLogout',
+ ApiLogout::class,
],
];
}
@@ -178,8 +178,8 @@ class ApiModuleManagerTest extends MediaWikiTestCase {
*/
public function testGetModule_null() {
$modules = [
- 'login' => 'ApiLogin',
- 'logout' => 'ApiLogout',
+ 'login' => ApiLogin::class,
+ 'logout' => ApiLogout::class,
];
$moduleManager = $this->getModuleManager();
@@ -194,13 +194,13 @@ class ApiModuleManagerTest extends MediaWikiTestCase {
*/
public function testGetNames() {
$fooModules = [
- 'login' => 'ApiLogin',
- 'logout' => 'ApiLogout',
+ 'login' => ApiLogin::class,
+ 'logout' => ApiLogout::class,
];
$barModules = [
- 'feedcontributions' => [ 'class' => 'ApiFeedContributions' ],
- 'feedrecentchanges' => [ 'class' => 'ApiFeedRecentChanges' ],
+ 'feedcontributions' => [ 'class' => ApiFeedContributions::class ],
+ 'feedrecentchanges' => [ 'class' => ApiFeedRecentChanges::class ],
];
$moduleManager = $this->getModuleManager();
@@ -220,13 +220,13 @@ class ApiModuleManagerTest extends MediaWikiTestCase {
*/
public function testGetNamesWithClasses() {
$fooModules = [
- 'login' => 'ApiLogin',
- 'logout' => 'ApiLogout',
+ 'login' => ApiLogin::class,
+ 'logout' => ApiLogout::class,
];
$barModules = [
- 'feedcontributions' => [ 'class' => 'ApiFeedContributions' ],
- 'feedrecentchanges' => [ 'class' => 'ApiFeedRecentChanges' ],
+ 'feedcontributions' => [ 'class' => ApiFeedContributions::class ],
+ 'feedrecentchanges' => [ 'class' => ApiFeedRecentChanges::class ],
];
$moduleManager = $this->getModuleManager();
@@ -238,8 +238,8 @@ class ApiModuleManagerTest extends MediaWikiTestCase {
$allNamesWithClasses = $moduleManager->getNamesWithClasses();
$allModules = array_merge( $fooModules, [
- 'feedcontributions' => 'ApiFeedContributions',
- 'feedrecentchanges' => 'ApiFeedRecentChanges',
+ 'feedcontributions' => ApiFeedContributions::class,
+ 'feedrecentchanges' => ApiFeedRecentChanges::class,
] );
$this->assertArrayEquals( $allModules, $allNamesWithClasses );
}
@@ -249,13 +249,13 @@ class ApiModuleManagerTest extends MediaWikiTestCase {
*/
public function testGetModuleGroup() {
$fooModules = [
- 'login' => 'ApiLogin',
- 'logout' => 'ApiLogout',
+ 'login' => ApiLogin::class,
+ 'logout' => ApiLogout::class,
];
$barModules = [
- 'feedcontributions' => [ 'class' => 'ApiFeedContributions' ],
- 'feedrecentchanges' => [ 'class' => 'ApiFeedRecentChanges' ],
+ 'feedcontributions' => [ 'class' => ApiFeedContributions::class ],
+ 'feedrecentchanges' => [ 'class' => ApiFeedRecentChanges::class ],
];
$moduleManager = $this->getModuleManager();
@@ -272,13 +272,13 @@ class ApiModuleManagerTest extends MediaWikiTestCase {
*/
public function testGetGroups() {
$fooModules = [
- 'login' => 'ApiLogin',
- 'logout' => 'ApiLogout',
+ 'login' => ApiLogin::class,
+ 'logout' => ApiLogout::class,
];
$barModules = [
- 'feedcontributions' => [ 'class' => 'ApiFeedContributions' ],
- 'feedrecentchanges' => [ 'class' => 'ApiFeedRecentChanges' ],
+ 'feedcontributions' => [ 'class' => ApiFeedContributions::class ],
+ 'feedrecentchanges' => [ 'class' => ApiFeedRecentChanges::class ],
];
$moduleManager = $this->getModuleManager();
@@ -294,13 +294,13 @@ class ApiModuleManagerTest extends MediaWikiTestCase {
*/
public function testGetClassName() {
$fooModules = [
- 'login' => 'ApiLogin',
- 'logout' => 'ApiLogout',
+ 'login' => ApiLogin::class,
+ 'logout' => ApiLogout::class,
];
$barModules = [
- 'feedcontributions' => [ 'class' => 'ApiFeedContributions' ],
- 'feedrecentchanges' => [ 'class' => 'ApiFeedRecentChanges' ],
+ 'feedcontributions' => [ 'class' => ApiFeedContributions::class ],
+ 'feedrecentchanges' => [ 'class' => ApiFeedRecentChanges::class ],
];
$moduleManager = $this->getModuleManager();
@@ -308,19 +308,19 @@ class ApiModuleManagerTest extends MediaWikiTestCase {
$moduleManager->addModules( $barModules, 'bar' );
$this->assertEquals(
- 'ApiLogin',
+ ApiLogin::class,
$moduleManager->getClassName( 'login' )
);
$this->assertEquals(
- 'ApiLogout',
+ ApiLogout::class,
$moduleManager->getClassName( 'logout' )
);
$this->assertEquals(
- 'ApiFeedContributions',
+ ApiFeedContributions::class,
$moduleManager->getClassName( 'feedcontributions' )
);
$this->assertEquals(
- 'ApiFeedRecentChanges',
+ ApiFeedRecentChanges::class,
$moduleManager->getClassName( 'feedrecentchanges' )
);
$this->assertFalse(
diff --git a/www/wiki/tests/phpunit/includes/api/ApiMoveTest.php b/www/wiki/tests/phpunit/includes/api/ApiMoveTest.php
new file mode 100644
index 00000000..fb697ffd
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/api/ApiMoveTest.php
@@ -0,0 +1,393 @@
+<?php
+
+/**
+ * @group API
+ * @group Database
+ * @group medium
+ *
+ * @covers ApiMove
+ */
+class ApiMoveTest extends ApiTestCase {
+ /**
+ * @param string $from Prefixed name of source
+ * @param string $to Prefixed name of destination
+ * @param string $id Page id of the page to move
+ * @param array|string|null $opts Options: 'noredirect' to expect no redirect
+ */
+ protected function assertMoved( $from, $to, $id, $opts = null ) {
+ $opts = (array)$opts;
+
+ $fromTitle = Title::newFromText( $from );
+ $toTitle = Title::newFromText( $to );
+
+ $this->assertTrue( $toTitle->exists(),
+ "Destination {$toTitle->getPrefixedText()} does not exist" );
+
+ if ( in_array( 'noredirect', $opts ) ) {
+ $this->assertFalse( $fromTitle->exists(),
+ "Source {$fromTitle->getPrefixedText()} exists" );
+ } else {
+ $this->assertTrue( $fromTitle->exists(),
+ "Source {$fromTitle->getPrefixedText()} does not exist" );
+ $this->assertTrue( $fromTitle->isRedirect(),
+ "Source {$fromTitle->getPrefixedText()} is not a redirect" );
+
+ $target = Revision::newFromTitle( $fromTitle )->getContent()->getRedirectTarget();
+ $this->assertSame( $toTitle->getPrefixedText(), $target->getPrefixedText() );
+ }
+
+ $this->assertSame( $id, $toTitle->getArticleId() );
+ }
+
+ /**
+ * Shortcut function to create a page and return its id.
+ *
+ * @param string $name Page to create
+ * @return int ID of created page
+ */
+ protected function createPage( $name ) {
+ return $this->editPage( $name, 'Content' )->value['revision']->getPage();
+ }
+
+ public function testFromWithFromid() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'The parameters "from" and "fromid" can not be used together.' );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'move',
+ 'from' => 'Some page',
+ 'fromid' => 123,
+ 'to' => 'Some other page',
+ ] );
+ }
+
+ public function testMove() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $id = $this->createPage( $name );
+
+ $res = $this->doApiRequestWithToken( [
+ 'action' => 'move',
+ 'from' => $name,
+ 'to' => "$name 2",
+ ] );
+
+ $this->assertMoved( $name, "$name 2", $id );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testMoveById() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $id = $this->createPage( $name );
+
+ $res = $this->doApiRequestWithToken( [
+ 'action' => 'move',
+ 'fromid' => $id,
+ 'to' => "$name 2",
+ ] );
+
+ $this->assertMoved( $name, "$name 2", $id );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testMoveNonexistent() {
+ $this->setExpectedException( ApiUsageException::class,
+ "The page you specified doesn't exist." );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'move',
+ 'from' => 'Nonexistent page',
+ 'to' => 'Different page'
+ ] );
+ }
+
+ public function testMoveNonexistentId() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'There is no page with ID 2147483647.' );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'move',
+ 'fromid' => pow( 2, 31 ) - 1,
+ 'to' => 'Different page',
+ ] );
+ }
+
+ public function testMoveToInvalidPageName() {
+ $this->setExpectedException( ApiUsageException::class, 'Bad title "[".' );
+
+ $name = ucfirst( __FUNCTION__ );
+ $id = $this->createPage( $name );
+
+ try {
+ $this->doApiRequestWithToken( [
+ 'action' => 'move',
+ 'from' => $name,
+ 'to' => '[',
+ ] );
+ } finally {
+ $this->assertSame( $id, Title::newFromText( $name )->getArticleId() );
+ }
+ }
+
+ // @todo File moving
+
+ public function testPingLimiter() {
+ global $wgRateLimits;
+
+ $this->setExpectedException( ApiUsageException::class,
+ "You've exceeded your rate limit. Please wait some time and try again." );
+
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->setMwGlobals( 'wgMainCacheType', 'hash' );
+
+ $this->stashMwGlobals( 'wgRateLimits' );
+ $wgRateLimits['move'] = [ '&can-bypass' => false, 'user' => [ 1, 60 ] ];
+
+ $id = $this->createPage( $name );
+
+ $res = $this->doApiRequestWithToken( [
+ 'action' => 'move',
+ 'from' => $name,
+ 'to' => "$name 2",
+ ] );
+
+ $this->assertMoved( $name, "$name 2", $id );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+
+ try {
+ $this->doApiRequestWithToken( [
+ 'action' => 'move',
+ 'from' => "$name 2",
+ 'to' => "$name 3",
+ ] );
+ } finally {
+ $this->assertSame( $id, Title::newFromText( "$name 2" )->getArticleId() );
+ $this->assertFalse( Title::newFromText( "$name 3" )->exists(),
+ "\"$name 3\" should not exist" );
+ }
+ }
+
+ public function testTagsNoPermission() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'You do not have permission to apply change tags along with your changes.' );
+
+ $name = ucfirst( __FUNCTION__ );
+
+ ChangeTags::defineTag( 'custom tag' );
+
+ $this->setGroupPermissions( 'user', 'applychangetags', false );
+
+ $id = $this->createPage( $name );
+
+ try {
+ $this->doApiRequestWithToken( [
+ 'action' => 'move',
+ 'from' => $name,
+ 'to' => "$name 2",
+ 'tags' => 'custom tag',
+ ] );
+ } finally {
+ $this->assertSame( $id, Title::newFromText( $name )->getArticleId() );
+ $this->assertFalse( Title::newFromText( "$name 2" )->exists(),
+ "\"$name 2\" should not exist" );
+ }
+ }
+
+ public function testSelfMove() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'The title is the same; cannot move a page over itself.' );
+
+ $name = ucfirst( __FUNCTION__ );
+ $this->createPage( $name );
+
+ $this->doApiRequestWithToken( [
+ 'action' => 'move',
+ 'from' => $name,
+ 'to' => $name,
+ ] );
+ }
+
+ public function testMoveTalk() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $id = $this->createPage( $name );
+ $talkId = $this->createPage( "Talk:$name" );
+
+ $res = $this->doApiRequestWithToken( [
+ 'action' => 'move',
+ 'from' => $name,
+ 'to' => "$name 2",
+ 'movetalk' => '',
+ ] );
+
+ $this->assertMoved( $name, "$name 2", $id );
+ $this->assertMoved( "Talk:$name", "Talk:$name 2", $talkId );
+
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testMoveTalkFailed() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $id = $this->createPage( $name );
+ $talkId = $this->createPage( "Talk:$name" );
+ $talkDestinationId = $this->createPage( "Talk:$name 2" );
+
+ $res = $this->doApiRequestWithToken( [
+ 'action' => 'move',
+ 'from' => $name,
+ 'to' => "$name 2",
+ 'movetalk' => '',
+ ] );
+
+ $this->assertMoved( $name, "$name 2", $id );
+ $this->assertSame( $talkId, Title::newFromText( "Talk:$name" )->getArticleId() );
+ $this->assertSame( $talkDestinationId,
+ Title::newFromText( "Talk:$name 2" )->getArticleId() );
+ $this->assertSame( [ [
+ 'message' => 'articleexists',
+ 'params' => [],
+ 'code' => 'articleexists',
+ 'type' => 'error',
+ ] ], $res[0]['move']['talkmove-errors'] );
+
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testMoveSubpages() {
+ global $wgNamespacesWithSubpages;
+
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->stashMwGlobals( 'wgNamespacesWithSubpages' );
+ $wgNamespacesWithSubpages[NS_MAIN] = true;
+
+ $pages = [ $name, "$name/1", "$name/2", "Talk:$name", "Talk:$name/1", "Talk:$name/3" ];
+ $ids = [];
+ foreach ( array_merge( $pages, [ "$name/error", "$name 2/error" ] ) as $page ) {
+ $ids[$page] = $this->createPage( $page );
+ }
+
+ $res = $this->doApiRequestWithToken( [
+ 'action' => 'move',
+ 'from' => $name,
+ 'to' => "$name 2",
+ 'movetalk' => '',
+ 'movesubpages' => '',
+ ] );
+
+ foreach ( $pages as $page ) {
+ $this->assertMoved( $page, str_replace( $name, "$name 2", $page ), $ids[$page] );
+ }
+
+ $this->assertSame( $ids["$name/error"],
+ Title::newFromText( "$name/error" )->getArticleId() );
+ $this->assertSame( $ids["$name 2/error"],
+ Title::newFromText( "$name 2/error" )->getArticleId() );
+
+ $results = array_merge( $res[0]['move']['subpages'], $res[0]['move']['subpages-talk'] );
+ foreach ( $results as $arr ) {
+ if ( $arr['from'] === "$name/error" ) {
+ $this->assertSame( [ [
+ 'message' => 'articleexists',
+ 'params' => [],
+ 'code' => 'articleexists',
+ 'type' => 'error'
+ ] ], $arr['errors'] );
+ } else {
+ $this->assertSame( str_replace( $name, "$name 2", $arr['from'] ), $arr['to'] );
+ }
+ $this->assertCount( 2, $arr );
+ }
+
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testMoveNoPermission() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'You must be a registered user and [[Special:UserLogin|logged in]] to move a page.' );
+
+ $name = ucfirst( __FUNCTION__ );
+
+ $id = $this->createPage( $name );
+
+ $user = new User();
+
+ try {
+ $this->doApiRequestWithToken( [
+ 'action' => 'move',
+ 'from' => $name,
+ 'to' => "$name 2",
+ ], null, $user );
+ } finally {
+ $this->assertSame( $id, Title::newFromText( "$name" )->getArticleId() );
+ $this->assertFalse( Title::newFromText( "$name 2" )->exists(),
+ "\"$name 2\" should not exist" );
+ }
+ }
+
+ public function testSuppressRedirect() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $id = $this->createPage( $name );
+
+ $res = $this->doApiRequestWithToken( [
+ 'action' => 'move',
+ 'from' => $name,
+ 'to' => "$name 2",
+ 'noredirect' => '',
+ ] );
+
+ $this->assertMoved( $name, "$name 2", $id, 'noredirect' );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testSuppressRedirectNoPermission() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->setGroupPermissions( 'sysop', 'suppressredirect', false );
+
+ $id = $this->createPage( $name );
+
+ $res = $this->doApiRequestWithToken( [
+ 'action' => 'move',
+ 'from' => $name,
+ 'to' => "$name 2",
+ 'noredirect' => '',
+ ] );
+
+ $this->assertMoved( $name, "$name 2", $id );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testMoveSubpagesError() {
+ $name = ucfirst( __FUNCTION__ );
+
+ // Subpages are allowed in talk but not main
+ $idBase = $this->createPage( "Talk:$name" );
+ $idSub = $this->createPage( "Talk:$name/1" );
+
+ $res = $this->doApiRequestWithToken( [
+ 'action' => 'move',
+ 'from' => "Talk:$name",
+ 'to' => $name,
+ 'movesubpages' => '',
+ ] );
+
+ $this->assertMoved( "Talk:$name", $name, $idBase );
+ $this->assertSame( $idSub, Title::newFromText( "Talk:$name/1" )->getArticleId() );
+ $this->assertFalse( Title::newFromText( "$name/1" )->exists(),
+ "\"$name/1\" should not exist" );
+
+ $this->assertSame( [ 'errors' => [ [
+ 'message' => 'namespace-nosubpages',
+ 'params' => [ '' ],
+ 'code' => 'namespace-nosubpages',
+ 'type' => 'error',
+ ] ] ], $res[0]['move']['subpages'] );
+
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiOpenSearchTest.php b/www/wiki/tests/phpunit/includes/api/ApiOpenSearchTest.php
index 23fa7bcb..209ca07b 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiOpenSearchTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiOpenSearchTest.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @covers ApiOpenSearch
+ */
class ApiOpenSearchTest extends MediaWikiTestCase {
public function testGetAllowedParams() {
$config = $this->replaceSearchEngineConfig();
@@ -33,7 +36,7 @@ class ApiOpenSearchTest extends MediaWikiTestCase {
}
private function replaceSearchEngineConfig() {
- $config = $this->getMockBuilder( 'SearchEngineConfig' )
+ $config = $this->getMockBuilder( SearchEngineConfig::class )
->disableOriginalConstructor()
->getMock();
$this->setService( 'SearchEngineConfig', $config );
@@ -42,10 +45,10 @@ class ApiOpenSearchTest extends MediaWikiTestCase {
}
private function replaceSearchEngine() {
- $engine = $this->getMockBuilder( 'SearchEngine' )
+ $engine = $this->getMockBuilder( SearchEngine::class )
->disableOriginalConstructor()
->getMock();
- $engineFactory = $this->getMockBuilder( 'SearchEngineFactory' )
+ $engineFactory = $this->getMockBuilder( SearchEngineFactory::class )
->disableOriginalConstructor()
->getMock();
$engineFactory->expects( $this->any() )
diff --git a/www/wiki/tests/phpunit/includes/api/ApiOptionsTest.php b/www/wiki/tests/phpunit/includes/api/ApiOptionsTest.php
index ef706261..c0fecf06 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiOptionsTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiOptionsTest.php
@@ -22,7 +22,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
protected function setUp() {
parent::setUp();
- $this->mUserMock = $this->getMockBuilder( 'User' )
+ $this->mUserMock = $this->getMockBuilder( User::class )
->disableOriginalConstructor()
->getMock();
@@ -278,26 +278,13 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
$this->mUserMock->expects( $this->never() )
->method( 'resetOptions' );
- $this->mUserMock->expects( $this->at( 2 ) )
- ->method( 'getOptions' );
-
- $this->mUserMock->expects( $this->at( 5 ) )
+ $this->mUserMock->expects( $this->exactly( 3 ) )
->method( 'setOption' )
- ->with( $this->equalTo( 'willBeNull' ), $this->identicalTo( null ) );
-
- $this->mUserMock->expects( $this->at( 6 ) )
- ->method( 'getOptions' );
-
- $this->mUserMock->expects( $this->at( 7 ) )
- ->method( 'setOption' )
- ->with( $this->equalTo( 'willBeEmpty' ), $this->equalTo( '' ) );
-
- $this->mUserMock->expects( $this->at( 8 ) )
- ->method( 'getOptions' );
-
- $this->mUserMock->expects( $this->at( 9 ) )
- ->method( 'setOption' )
- ->with( $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) );
+ ->withConsecutive(
+ [ $this->equalTo( 'willBeNull' ), $this->identicalTo( null ) ],
+ [ $this->equalTo( 'willBeEmpty' ), $this->equalTo( '' ) ],
+ [ $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) ]
+ );
$this->mUserMock->expects( $this->once() )
->method( 'saveSettings' );
@@ -315,19 +302,12 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
$this->mUserMock->expects( $this->once() )
->method( 'resetOptions' );
- $this->mUserMock->expects( $this->at( 5 ) )
- ->method( 'getOptions' );
-
- $this->mUserMock->expects( $this->at( 6 ) )
- ->method( 'setOption' )
- ->with( $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) );
-
- $this->mUserMock->expects( $this->at( 7 ) )
- ->method( 'getOptions' );
-
- $this->mUserMock->expects( $this->at( 8 ) )
+ $this->mUserMock->expects( $this->exactly( 2 ) )
->method( 'setOption' )
- ->with( $this->equalTo( 'name' ), $this->equalTo( 'value' ) );
+ ->withConsecutive(
+ [ $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) ],
+ [ $this->equalTo( 'name' ), $this->equalTo( 'value' ) ]
+ );
$this->mUserMock->expects( $this->once() )
->method( 'saveSettings' );
@@ -348,21 +328,14 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
$this->mUserMock->expects( $this->never() )
->method( 'resetOptions' );
- $this->mUserMock->expects( $this->at( 4 ) )
- ->method( 'setOption' )
- ->with( $this->equalTo( 'testmultiselect-opt1' ), $this->identicalTo( true ) );
-
- $this->mUserMock->expects( $this->at( 5 ) )
- ->method( 'setOption' )
- ->with( $this->equalTo( 'testmultiselect-opt2' ), $this->identicalTo( null ) );
-
- $this->mUserMock->expects( $this->at( 6 ) )
- ->method( 'setOption' )
- ->with( $this->equalTo( 'testmultiselect-opt3' ), $this->identicalTo( false ) );
-
- $this->mUserMock->expects( $this->at( 7 ) )
+ $this->mUserMock->expects( $this->exactly( 4 ) )
->method( 'setOption' )
- ->with( $this->equalTo( 'testmultiselect-opt4' ), $this->identicalTo( false ) );
+ ->withConsecutive(
+ [ $this->equalTo( 'testmultiselect-opt1' ), $this->identicalTo( true ) ],
+ [ $this->equalTo( 'testmultiselect-opt2' ), $this->identicalTo( null ) ],
+ [ $this->equalTo( 'testmultiselect-opt3' ), $this->identicalTo( false ) ],
+ [ $this->equalTo( 'testmultiselect-opt4' ), $this->identicalTo( false ) ]
+ );
$this->mUserMock->expects( $this->once() )
->method( 'saveSettings' );
diff --git a/www/wiki/tests/phpunit/includes/api/ApiPageSetTest.php b/www/wiki/tests/phpunit/includes/api/ApiPageSetTest.php
index 1aa0a133..b9e4645d 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiPageSetTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiPageSetTest.php
@@ -4,6 +4,7 @@
* @group API
* @group medium
* @group Database
+ * @covers ApiPageSet
*/
class ApiPageSetTest extends ApiTestCase {
public static function provideRedirectMergePolicy() {
@@ -107,7 +108,7 @@ class ApiPageSetTest extends ApiTestCase {
$userName = $user->getName();
$userDbkey = str_replace( ' ', '_', $userName );
$request = new FauxRequest( [
- 'titles' => join( '|', [
+ 'titles' => implode( '|', [
'Special:MyContributions',
'Special:MyPage',
'Special:MyTalk/subpage',
diff --git a/www/wiki/tests/phpunit/includes/api/ApiParseTest.php b/www/wiki/tests/phpunit/includes/api/ApiParseTest.php
index 028d3b41..a04271f6 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiParseTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiParseTest.php
@@ -13,48 +13,136 @@ class ApiParseTest extends ApiTestCase {
protected static $revIds = [];
public function addDBDataOnce() {
- $user = static::getTestSysop()->getUser();
$title = Title::newFromText( __CLASS__ );
- $page = WikiPage::factory( $title );
- $status = $page->doEditContent(
- ContentHandler::makeContent( 'Test for revdel', $title, CONTENT_MODEL_WIKITEXT ),
- __METHOD__ . ' Test for revdel', 0, false, $user
- );
- if ( !$status->isOk() ) {
- $this->fail( "Failed to create $title: " . $status->getWikiText( false, false, 'en' ) );
- }
+ $status = $this->editPage( __CLASS__, 'Test for revdel' );
self::$pageId = $status->value['revision']->getPage();
self::$revIds['revdel'] = $status->value['revision']->getId();
- $status = $page->doEditContent(
- ContentHandler::makeContent( 'Test for oldid', $title, CONTENT_MODEL_WIKITEXT ),
- __METHOD__ . ' Test for oldid', 0, false, $user
- );
- if ( !$status->isOk() ) {
- $this->fail( "Failed to edit $title: " . $status->getWikiText( false, false, 'en' ) );
- }
+ $status = $this->editPage( __CLASS__, 'Test for suppressed' );
+ self::$revIds['suppressed'] = $status->value['revision']->getId();
+
+ $status = $this->editPage( __CLASS__, 'Test for oldid' );
self::$revIds['oldid'] = $status->value['revision']->getId();
- $status = $page->doEditContent(
- ContentHandler::makeContent( 'Test for latest', $title, CONTENT_MODEL_WIKITEXT ),
- __METHOD__ . ' Test for latest', 0, false, $user
+ $status = $this->editPage( __CLASS__, 'Test for latest' );
+ self::$revIds['latest'] = $status->value['revision']->getId();
+
+ $this->revisionDelete( self::$revIds['revdel'] );
+ $this->revisionDelete(
+ self::$revIds['suppressed'],
+ [ Revision::DELETED_TEXT => 1, Revision::DELETED_RESTRICTED => 1 ]
);
- if ( !$status->isOk() ) {
- $this->fail( "Failed to edit $title: " . $status->getWikiText( false, false, 'en' ) );
+
+ Title::clearCaches(); // Otherwise it has the wrong latest revision for some reason
+ }
+
+ /**
+ * Assert that the given result of calling $this->doApiRequest() with
+ * action=parse resulted in $html, accounting for the boilerplate that the
+ * parser adds around the parsed page. Also asserts that warnings match
+ * the provided $warning.
+ *
+ * @param string $html Expected HTML
+ * @param array $res Returned from doApiRequest()
+ * @param string|null $warnings Exact value of expected warnings, null for
+ * no warnings
+ */
+ protected function assertParsedTo( $expected, array $res, $warnings = null ) {
+ $this->doAssertParsedTo( $expected, $res, $warnings, [ $this, 'assertSame' ] );
+ }
+
+ /**
+ * Same as above, but asserts that the HTML matches a regexp instead of a
+ * literal string match.
+ *
+ * @param string $html Expected HTML
+ * @param array $res Returned from doApiRequest()
+ * @param string|null $warnings Exact value of expected warnings, null for
+ * no warnings
+ */
+ protected function assertParsedToRegExp( $expected, array $res, $warnings = null ) {
+ $this->doAssertParsedTo( $expected, $res, $warnings, [ $this, 'assertRegExp' ] );
+ }
+
+ private function doAssertParsedTo( $expected, array $res, $warnings, callable $callback ) {
+ $html = $res[0]['parse']['text'];
+
+ $expectedStart = '<div class="mw-parser-output">';
+ $this->assertSame( $expectedStart, substr( $html, 0, strlen( $expectedStart ) ) );
+
+ $html = substr( $html, strlen( $expectedStart ) );
+
+ if ( $res[1]->getBool( 'disablelimitreport' ) ) {
+ $expectedEnd = "</div>";
+ $this->assertSame( $expectedEnd, substr( $html, -strlen( $expectedEnd ) ) );
+
+ $html = substr( $html, 0, strlen( $html ) - strlen( $expectedEnd ) );
+ } else {
+ $expectedEnd = '#\n<!-- \nNewPP limit report\n(?>.+?\n-->)\n' .
+ '<!--\nTransclusion expansion time report \(%,ms,calls,template\)\n(?>.*?\n-->)\n' .
+ '</div>(\n<!-- Saved in parser cache (?>.*?\n -->)\n)?$#s';
+ $this->assertRegExp( $expectedEnd, $html );
+
+ $html = preg_replace( $expectedEnd, '', $html );
}
- self::$revIds['latest'] = $status->value['revision']->getId();
- RevisionDeleter::createList(
- 'revision', RequestContext::getMain(), $title, [ self::$revIds['revdel'] ]
- )->setVisibility( [
- 'value' => [
- Revision::DELETED_TEXT => 1,
+ call_user_func( $callback, $expected, $html );
+
+ if ( $warnings === null ) {
+ $this->assertCount( 1, $res[0] );
+ } else {
+ $this->assertCount( 2, $res[0] );
+ // This deliberately fails if there are extra warnings
+ $this->assertSame( [ 'parse' => [ 'warnings' => $warnings ] ], $res[0]['warnings'] );
+ }
+ }
+
+ /**
+ * Set up an interwiki entry for testing.
+ */
+ protected function setupInterwiki() {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->insert(
+ 'interwiki',
+ [
+ 'iw_prefix' => 'madeuplanguage',
+ 'iw_url' => "https://example.com/wiki/$1",
+ 'iw_api' => '',
+ 'iw_wikiid' => '',
+ 'iw_local' => false,
],
- 'comment' => 'Test for revdel',
- ] );
+ __METHOD__,
+ 'IGNORE'
+ );
- Title::clearCaches(); // Otherwise it has the wrong latest revision for some reason
+ $this->setMwGlobals( 'wgExtraInterlanguageLinkPrefixes', [ 'madeuplanguage' ] );
+ $this->tablesUsed[] = 'interwiki';
+ }
+
+ /**
+ * Set up a skin for testing.
+ *
+ * @todo Should this code be in MediaWikiTestCase or something?
+ */
+ protected function setupSkin() {
+ $factory = new SkinFactory();
+ $factory->register( 'testing', 'Testing', function () {
+ $skin = $this->getMockBuilder( SkinFallback::class )
+ ->setMethods( [ 'getDefaultModules', 'setupSkinUserCss' ] )
+ ->getMock();
+ $skin->expects( $this->once() )->method( 'getDefaultModules' )
+ ->willReturn( [
+ 'core' => [ 'foo', 'bar' ],
+ 'content' => [ 'baz' ]
+ ] );
+ $skin->expects( $this->once() )->method( 'setupSkinUserCss' )
+ ->will( $this->returnCallback( function ( OutputPage $out ) {
+ $out->addModuleStyles( 'foo.styles' );
+ } ) );
+ return $skin;
+ } );
+ $this->setService( 'SkinFactory', $factory );
}
public function testParseByName() {
@@ -62,14 +150,14 @@ class ApiParseTest extends ApiTestCase {
'action' => 'parse',
'page' => __CLASS__,
] );
- $this->assertContains( 'Test for latest', $res[0]['parse']['text'] );
+ $this->assertParsedTo( "<p>Test for latest\n</p>", $res );
$res = $this->doApiRequest( [
'action' => 'parse',
'page' => __CLASS__,
'disablelimitreport' => 1,
] );
- $this->assertContains( 'Test for latest', $res[0]['parse']['text'] );
+ $this->assertParsedTo( "<p>Test for latest\n</p>", $res );
}
public function testParseById() {
@@ -77,7 +165,7 @@ class ApiParseTest extends ApiTestCase {
'action' => 'parse',
'pageid' => self::$pageId,
] );
- $this->assertContains( 'Test for latest', $res[0]['parse']['text'] );
+ $this->assertParsedTo( "<p>Test for latest\n</p>", $res );
}
public function testParseByOldId() {
@@ -85,36 +173,46 @@ class ApiParseTest extends ApiTestCase {
'action' => 'parse',
'oldid' => self::$revIds['oldid'],
] );
- $this->assertContains( 'Test for oldid', $res[0]['parse']['text'] );
+ $this->assertParsedTo( "<p>Test for oldid\n</p>", $res );
$this->assertArrayNotHasKey( 'textdeleted', $res[0]['parse'] );
$this->assertArrayNotHasKey( 'textsuppressed', $res[0]['parse'] );
}
- public function testParseRevDel() {
- $user = static::getTestUser()->getUser();
- $sysop = static::getTestSysop()->getUser();
-
- try {
- $this->doApiRequest( [
- 'action' => 'parse',
- 'oldid' => self::$revIds['revdel'],
- ], null, null, $user );
- $this->fail( "API did not return an error as expected" );
- } catch ( ApiUsageException $ex ) {
- $this->assertTrue( ApiTestCase::apiExceptionHasCode( $ex, 'permissiondenied' ),
- "API failed with error 'permissiondenied'" );
- }
-
+ public function testRevDel() {
$res = $this->doApiRequest( [
'action' => 'parse',
'oldid' => self::$revIds['revdel'],
- ], null, null, $sysop );
- $this->assertContains( 'Test for revdel', $res[0]['parse']['text'] );
+ ] );
+
+ $this->assertParsedTo( "<p>Test for revdel\n</p>", $res );
$this->assertArrayHasKey( 'textdeleted', $res[0]['parse'] );
$this->assertArrayNotHasKey( 'textsuppressed', $res[0]['parse'] );
}
- public function testParseNonexistentPage() {
+ public function testRevDelNoPermission() {
+ $this->setExpectedException( ApiUsageException::class,
+ "You don't have permission to view deleted revision text." );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'oldid' => self::$revIds['revdel'],
+ ], null, null, static::getTestUser()->getUser() );
+ }
+
+ public function testSuppressed() {
+ $this->setGroupPermissions( 'sysop', 'viewsuppressed', true );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'oldid' => self::$revIds['suppressed']
+ ] );
+
+ $this->assertParsedTo( "<p>Test for suppressed\n</p>", $res );
+ $this->assertArrayHasKey( 'textsuppressed', $res[0]['parse'] );
+ $this->assertArrayHasKey( 'textdeleted', $res[0]['parse'] );
+ }
+
+ public function testNonexistentPage() {
try {
$this->doApiRequest( [
'action' => 'parse',
@@ -129,4 +227,623 @@ class ApiParseTest extends ApiTestCase {
);
}
}
+
+ public function testTitleProvided() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => 'Some interesting page',
+ 'text' => '{{PAGENAME}} has attracted my attention',
+ ] );
+
+ $this->assertParsedTo( "<p>Some interesting page has attracted my attention\n</p>", $res );
+ }
+
+ public function testSection() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name,
+ "Intro\n\n== Section 1 ==\n\nContent 1\n\n== Section 2 ==\n\nContent 2" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => $name,
+ 'section' => 1,
+ ] );
+
+ $this->assertParsedToRegExp( '!<h2>.*Section 1.*</h2>\n<p>Content 1\n</p>!', $res );
+ }
+
+ public function testInvalidSection() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'The "section" parameter must be a valid section ID or "new".' );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'section' => 'T-new',
+ ] );
+ }
+
+ public function testSectionNoContent() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $status = $this->editPage( $name,
+ "Intro\n\n== Section 1 ==\n\nContent 1\n\n== Section 2 ==\n\nContent 2" );
+
+ $this->setExpectedException( ApiUsageException::class,
+ "Missing content for page ID {$status->value['revision']->getPage()}." );
+
+ $this->db->delete( 'revision', [ 'rev_id' => $status->value['revision']->getId() ] );
+
+ // Suppress warning in WikiPage::getContentModel
+ Wikimedia\suppressWarnings();
+ try {
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => $name,
+ 'section' => 1,
+ ] );
+ } finally {
+ Wikimedia\restoreWarnings();
+ }
+ }
+
+ public function testNewSectionWithPage() {
+ $this->setExpectedException( ApiUsageException::class,
+ '"section=new" cannot be combined with the "oldid", "pageid" or "page" ' .
+ 'parameters. Please use "title" and "text".' );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => __CLASS__,
+ 'section' => 'new',
+ ] );
+ }
+
+ public function testNonexistentOldId() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'There is no revision with ID 2147483647.' );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'oldid' => pow( 2, 31 ) - 1,
+ ] );
+ }
+
+ public function testUnfollowedRedirect() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, "#REDIRECT [[$name 2]]" );
+ $this->editPage( "$name 2", "Some ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => $name,
+ ] );
+
+ // Can't use assertParsedTo because the parser output is different for
+ // redirects
+ $this->assertRegExp( "/Redirect to:.*$name 2/", $res[0]['parse']['text'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testFollowedRedirect() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, "#REDIRECT [[$name 2]]" );
+ $this->editPage( "$name 2", "Some ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => $name,
+ 'redirects' => true,
+ ] );
+
+ $this->assertParsedTo( "<p>Some <i>text</i>\n</p>", $res );
+ }
+
+ public function testFollowedRedirectById() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $id = $this->editPage( $name, "#REDIRECT [[$name 2]]" )->value['revision']->getPage();
+ $this->editPage( "$name 2", "Some ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'pageid' => $id,
+ 'redirects' => true,
+ ] );
+
+ $this->assertParsedTo( "<p>Some <i>text</i>\n</p>", $res );
+ }
+
+ public function testInvalidTitle() {
+ $this->setExpectedException( ApiUsageException::class, 'Bad title "|".' );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => '|',
+ ] );
+ }
+
+ public function testTitleWithNonexistentRevId() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'There is no revision with ID 2147483647.' );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'revid' => pow( 2, 31 ) - 1,
+ ] );
+ }
+
+ public function testTitleWithNonMatchingRevId() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => $name,
+ 'revid' => self::$revIds['latest'],
+ 'text' => 'Some text',
+ ] );
+
+ $this->assertParsedTo( "<p>Some text\n</p>", $res,
+ 'r' . self::$revIds['latest'] . " is not a revision of $name." );
+ }
+
+ public function testRevId() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'revid' => self::$revIds['latest'],
+ 'text' => 'My revid is {{REVISIONID}}!',
+ ] );
+
+ $this->assertParsedTo( "<p>My revid is " . self::$revIds['latest'] . "!\n</p>", $res );
+ }
+
+ public function testTitleNoText() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => 'Special:AllPages',
+ ] );
+
+ $this->assertParsedTo( '', $res,
+ '"title" used without "text", and parsed page properties were requested. ' .
+ 'Did you mean to use "page" instead of "title"?' );
+ }
+
+ public function testRevidNoText() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'revid' => self::$revIds['latest'],
+ ] );
+
+ $this->assertParsedTo( '', $res,
+ '"revid" used without "text", and parsed page properties were requested. ' .
+ 'Did you mean to use "oldid" instead of "revid"?' );
+ }
+
+ public function testTextNoContentModel() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'text' => "Some ''text''",
+ ] );
+
+ $this->assertParsedTo( "<p>Some <i>text</i>\n</p>", $res,
+ 'No "title" or "contentmodel" was given, assuming wikitext.' );
+ }
+
+ public function testSerializationError() {
+ $this->setExpectedException( APIUsageException::class,
+ 'Content serialization failed: Could not unserialize content' );
+
+ $this->mergeMwGlobalArrayValue( 'wgContentHandlers',
+ [ 'testing-serialize-error' => 'DummySerializeErrorContentHandler' ] );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'text' => "Some ''text''",
+ 'contentmodel' => 'testing-serialize-error',
+ ] );
+ }
+
+ public function testNewSection() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'section' => 'new',
+ 'sectiontitle' => 'Title',
+ 'text' => 'Content',
+ ] );
+
+ $this->assertParsedToRegExp( '!<h2>.*Title.*</h2>\n<p>Content\n</p>!', $res );
+ }
+
+ public function testExistingSection() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'section' => 1,
+ 'text' => "Intro\n\n== Section 1 ==\n\nContent\n\n== Section 2 ==\n\nMore content",
+ ] );
+
+ $this->assertParsedToRegExp( '!<h2>.*Section 1.*</h2>\n<p>Content\n</p>!', $res );
+ }
+
+ public function testNoPst() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( "Template:$name", "Template ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'text' => "{{subst:$name}}",
+ 'contentmodel' => 'wikitext',
+ ] );
+
+ $this->assertParsedTo( "<p>{{subst:$name}}\n</p>", $res );
+ }
+
+ public function testPst() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( "Template:$name", "Template ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'pst' => '',
+ 'text' => "{{subst:$name}}",
+ 'contentmodel' => 'wikitext',
+ 'prop' => 'text|wikitext',
+ ] );
+
+ $this->assertParsedTo( "<p>Template <i>text</i>\n</p>", $res );
+ $this->assertSame( "{{subst:$name}}", $res[0]['parse']['wikitext'] );
+ }
+
+ public function testOnlyPst() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( "Template:$name", "Template ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'onlypst' => '',
+ 'text' => "{{subst:$name}}",
+ 'contentmodel' => 'wikitext',
+ 'prop' => 'text|wikitext',
+ 'summary' => 'Summary',
+ ] );
+
+ $this->assertSame(
+ [ 'parse' => [
+ 'text' => "Template ''text''",
+ 'wikitext' => "{{subst:$name}}",
+ 'parsedsummary' => 'Summary',
+ ] ],
+ $res[0]
+ );
+ }
+
+ public function testHeadHtml() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => __CLASS__,
+ 'prop' => 'headhtml',
+ ] );
+
+ // Just do a rough sanity check
+ $this->assertRegExp( '#<!DOCTYPE.*<html.*<head.*</head>.*<body#s',
+ $res[0]['parse']['headhtml'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testCategoriesHtml() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, "[[Category:$name]]" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => $name,
+ 'prop' => 'categorieshtml',
+ ] );
+
+ $this->assertRegExp( "#Category.*Category:$name.*$name#",
+ $res[0]['parse']['categorieshtml'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testEffectiveLangLinks() {
+ $hookRan = false;
+ $this->setTemporaryHook( 'LanguageLinks',
+ function () use ( &$hookRan ) {
+ $hookRan = true;
+ }
+ );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' => '[[zh:' . __CLASS__ . ']]',
+ 'effectivelanglinks' => '',
+ ] );
+
+ $this->assertTrue( $hookRan );
+ $this->assertSame( 'The parameter "effectivelanglinks" has been deprecated.',
+ $res[0]['warnings']['parse']['warnings'] );
+ }
+
+ /**
+ * @param array $arr Extra params to add to API request
+ */
+ private function doTestLangLinks( array $arr = [] ) {
+ $this->setupInterwiki();
+
+ $res = $this->doApiRequest( array_merge( [
+ 'action' => 'parse',
+ 'title' => 'Omelette',
+ 'text' => '[[madeuplanguage:Omelette]]',
+ 'prop' => 'langlinks',
+ ], $arr ) );
+
+ $langLinks = $res[0]['parse']['langlinks'];
+
+ $this->assertCount( 1, $langLinks );
+ $this->assertSame( 'madeuplanguage', $langLinks[0]['lang'] );
+ $this->assertSame( 'Omelette', $langLinks[0]['title'] );
+ $this->assertSame( 'https://example.com/wiki/Omelette', $langLinks[0]['url'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testLangLinks() {
+ $this->doTestLangLinks();
+ }
+
+ public function testLangLinksWithSkin() {
+ $this->setupSkin();
+ $this->doTestLangLinks( [ 'useskin' => 'testing' ] );
+ }
+
+ public function testHeadItems() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' => '',
+ 'prop' => 'headitems',
+ ] );
+
+ $this->assertSame( [], $res[0]['parse']['headitems'] );
+ $this->assertSame(
+ '"prop=headitems" is deprecated since MediaWiki 1.28. ' .
+ 'Use "prop=headhtml" when creating new HTML documents, ' .
+ 'or "prop=modules|jsconfigvars" when updating a document client-side.',
+ $res[0]['warnings']['parse']['warnings']
+ );
+ }
+
+ public function testHeadItemsWithSkin() {
+ $this->setupSkin();
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' => '',
+ 'prop' => 'headitems',
+ 'useskin' => 'testing',
+ ] );
+
+ $this->assertSame( [], $res[0]['parse']['headitems'] );
+ $this->assertSame(
+ '"prop=headitems" is deprecated since MediaWiki 1.28. ' .
+ 'Use "prop=headhtml" when creating new HTML documents, ' .
+ 'or "prop=modules|jsconfigvars" when updating a document client-side.',
+ $res[0]['warnings']['parse']['warnings']
+ );
+ }
+
+ public function testModules() {
+ $this->setTemporaryHook( 'ParserAfterParse',
+ function ( $parser ) {
+ $output = $parser->getOutput();
+ $output->addModules( [ 'foo', 'bar' ] );
+ $output->addModuleScripts( [ 'baz', 'quuz' ] );
+ $output->addModuleStyles( [ 'aaa', 'zzz' ] );
+ $output->addJsConfigVars( [ 'x' => 'y', 'z' => -3 ] );
+ }
+ );
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' => 'Content',
+ 'prop' => 'modules|jsconfigvars|encodedjsconfigvars',
+ ] );
+
+ $this->assertSame( [ 'foo', 'bar' ], $res[0]['parse']['modules'] );
+ $this->assertSame( [ 'baz', 'quuz' ], $res[0]['parse']['modulescripts'] );
+ $this->assertSame( [ 'aaa', 'zzz' ], $res[0]['parse']['modulestyles'] );
+ $this->assertSame( [ 'x' => 'y', 'z' => -3 ], $res[0]['parse']['jsconfigvars'] );
+ $this->assertSame( '{"x":"y","z":-3}', $res[0]['parse']['encodedjsconfigvars'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testModulesWithSkin() {
+ $this->setupSkin();
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'pageid' => self::$pageId,
+ 'useskin' => 'testing',
+ 'prop' => 'modules',
+ ] );
+ $this->assertSame(
+ [ 'foo', 'bar', 'baz' ],
+ $res[0]['parse']['modules'],
+ 'resp.parse.modules'
+ );
+ $this->assertSame(
+ [],
+ $res[0]['parse']['modulescripts'],
+ 'resp.parse.modulescripts'
+ );
+ $this->assertSame(
+ [ 'foo.styles' ],
+ $res[0]['parse']['modulestyles'],
+ 'resp.parse.modulestyles'
+ );
+ $this->assertSame(
+ [ 'parse' =>
+ [ 'warnings' =>
+ 'Property "modules" was set but not "jsconfigvars" or ' .
+ '"encodedjsconfigvars". Configuration variables are necessary for ' .
+ 'proper module usage.'
+ ]
+ ],
+ $res[0]['warnings']
+ );
+ }
+
+ public function testIndicators() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' =>
+ '<indicator name="b">BBB!</indicator>Some text<indicator name="a">aaa</indicator>',
+ 'prop' => 'indicators',
+ ] );
+
+ $this->assertSame(
+ // It seems we return in markup order and not display order
+ [ 'b' => 'BBB!', 'a' => 'aaa' ],
+ $res[0]['parse']['indicators']
+ );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testIndicatorsWithSkin() {
+ $this->setupSkin();
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' =>
+ '<indicator name="b">BBB!</indicator>Some text<indicator name="a">aaa</indicator>',
+ 'prop' => 'indicators',
+ 'useskin' => 'testing',
+ ] );
+
+ $this->assertSame(
+ // Now we return in display order rather than markup order
+ [ 'a' => 'aaa', 'b' => 'BBB!' ],
+ $res[0]['parse']['indicators']
+ );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testIwlinks() {
+ $this->setupInterwiki();
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => 'Omelette',
+ 'text' => '[[:madeuplanguage:Omelette]][[madeuplanguage:Spaghetti]]',
+ 'prop' => 'iwlinks',
+ ] );
+
+ $iwlinks = $res[0]['parse']['iwlinks'];
+
+ $this->assertCount( 1, $iwlinks );
+ $this->assertSame( 'madeuplanguage', $iwlinks[0]['prefix'] );
+ $this->assertSame( 'https://example.com/wiki/Omelette', $iwlinks[0]['url'] );
+ $this->assertSame( 'madeuplanguage:Omelette', $iwlinks[0]['title'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testLimitReports() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'pageid' => self::$pageId,
+ 'prop' => 'limitreportdata|limitreporthtml',
+ ] );
+
+ // We don't bother testing the actual values here
+ $this->assertInternalType( 'array', $res[0]['parse']['limitreportdata'] );
+ $this->assertInternalType( 'string', $res[0]['parse']['limitreporthtml'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testParseTreeNonWikitext() {
+ $this->setExpectedException( ApiUsageException::class,
+ '"prop=parsetree" is only supported for wikitext content.' );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'text' => '',
+ 'contentmodel' => 'json',
+ 'prop' => 'parsetree',
+ ] );
+ }
+
+ public function testParseTree() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'text' => "Some ''text'' is {{nice|to have|i=think}}",
+ 'contentmodel' => 'wikitext',
+ 'prop' => 'parsetree',
+ ] );
+
+ // Preprocessor_DOM and Preprocessor_Hash give different results here,
+ // so we'll accept either
+ $this->assertRegExp(
+ '#^<root>Some \'\'text\'\' is <template><title>nice</title>' .
+ '<part><name index="1"/><value>to have</value></part>' .
+ '<part><name>i</name>(?:<equals>)?=(?:</equals>)?<value>think</value></part>' .
+ '</template></root>$#',
+ $res[0]['parse']['parsetree']
+ );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testDisableTidy() {
+ $this->setMwGlobals( 'wgTidyConfig', [ 'driver' => 'RemexHtml' ] );
+
+ // Check that disabletidy doesn't have an effect just because tidying
+ // doesn't work for some other reason
+ $res1 = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'text' => "<b>Mixed <i>up</b></i>",
+ 'contentmodel' => 'wikitext',
+ ] );
+ $this->assertParsedTo( "<p><b>Mixed <i>up</i></b>\n</p>", $res1 );
+
+ $res2 = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'text' => "<b>Mixed <i>up</b></i>",
+ 'contentmodel' => 'wikitext',
+ 'disabletidy' => '',
+ ] );
+
+ $this->assertParsedTo( "<p><b>Mixed <i>up</b></i>\n</p>", $res2 );
+ }
+
+ public function testFormatCategories() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( "Category:$name", 'Content' );
+ $this->editPage( 'Category:Hidden', '__HIDDENCAT__' );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' => "[[Category:$name]][[Category:Foo|Sort me]][[Category:Hidden]]",
+ 'prop' => 'categories',
+ ] );
+
+ $this->assertSame(
+ [ [ 'sortkey' => '', 'category' => $name ],
+ [ 'sortkey' => 'Sort me', 'category' => 'Foo', 'missing' => true ],
+ [ 'sortkey' => '', 'category' => 'Hidden', 'hidden' => true ] ],
+ $res[0]['parse']['categories']
+ );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiPurgeTest.php b/www/wiki/tests/phpunit/includes/api/ApiPurgeTest.php
index 9e1d3a18..96d9a384 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiPurgeTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiPurgeTest.php
@@ -9,11 +9,6 @@
*/
class ApiPurgeTest extends ApiTestCase {
- protected function setUp() {
- parent::setUp();
- $this->doLogin();
- }
-
/**
* @group Broken
*/
diff --git a/www/wiki/tests/phpunit/includes/api/ApiQueryAllPagesTest.php b/www/wiki/tests/phpunit/includes/api/ApiQueryAllPagesTest.php
index 9f28aaf5..2d89aa54 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiQueryAllPagesTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiQueryAllPagesTest.php
@@ -4,18 +4,14 @@
* @group API
* @group Database
* @group medium
+ *
+ * @covers ApiQueryAllPages
*/
class ApiQueryAllPagesTest extends ApiTestCase {
-
- protected function setUp() {
- parent::setUp();
- $this->doLogin();
- }
-
/**
- *Test T27702
- *Prefixes of API search requests are not handled with case sensitivity and may result
- *in wrong search results
+ * Test T27702
+ * Prefixes of API search requests are not handled with case sensitivity and may result
+ * in wrong search results
*/
public function testPrefixNormalizationSearchBug() {
$title = Title::newFromText( 'Category:Template:xyz' );
diff --git a/www/wiki/tests/phpunit/includes/api/ApiQueryRecentChangesIntegrationTest.php b/www/wiki/tests/phpunit/includes/api/ApiQueryRecentChangesIntegrationTest.php
new file mode 100644
index 00000000..5b43dd1b
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/api/ApiQueryRecentChangesIntegrationTest.php
@@ -0,0 +1,976 @@
+<?php
+
+use MediaWiki\Linker\LinkTarget;
+use MediaWiki\MediaWikiServices;
+
+/**
+ * @group API
+ * @group Database
+ * @group medium
+ *
+ * @covers ApiQueryRecentChanges
+ */
+class ApiQueryRecentChangesIntegrationTest extends ApiTestCase {
+
+ public function __construct( $name = null, array $data = [], $dataName = '' ) {
+ parent::__construct( $name, $data, $dataName );
+
+ $this->tablesUsed[] = 'recentchanges';
+ $this->tablesUsed[] = 'page';
+ }
+
+ protected function setUp() {
+ parent::setUp();
+
+ self::$users['ApiQueryRecentChangesIntegrationTestUser'] = $this->getMutableTestUser();
+ wfGetDB( DB_MASTER )->delete( 'recentchanges', '*', __METHOD__ );
+ }
+
+ private function getLoggedInTestUser() {
+ return self::$users['ApiQueryRecentChangesIntegrationTestUser']->getUser();
+ }
+
+ private function doPageEdit( User $user, LinkTarget $target, $summary ) {
+ static $i = 0;
+
+ $title = Title::newFromLinkTarget( $target );
+ $page = WikiPage::factory( $title );
+ $page->doEditContent(
+ ContentHandler::makeContent( __CLASS__ . $i++, $title ),
+ $summary,
+ 0,
+ false,
+ $user
+ );
+ }
+
+ private function doMinorPageEdit( User $user, LinkTarget $target, $summary ) {
+ $title = Title::newFromLinkTarget( $target );
+ $page = WikiPage::factory( $title );
+ $page->doEditContent(
+ ContentHandler::makeContent( __CLASS__, $title ),
+ $summary,
+ EDIT_MINOR,
+ false,
+ $user
+ );
+ }
+
+ private function doBotPageEdit( User $user, LinkTarget $target, $summary ) {
+ $title = Title::newFromLinkTarget( $target );
+ $page = WikiPage::factory( $title );
+ $page->doEditContent(
+ ContentHandler::makeContent( __CLASS__, $title ),
+ $summary,
+ EDIT_FORCE_BOT,
+ false,
+ $user
+ );
+ }
+
+ private function doAnonPageEdit( LinkTarget $target, $summary ) {
+ $title = Title::newFromLinkTarget( $target );
+ $page = WikiPage::factory( $title );
+ $page->doEditContent(
+ ContentHandler::makeContent( __CLASS__, $title ),
+ $summary,
+ 0,
+ false,
+ User::newFromId( 0 )
+ );
+ }
+
+ private function deletePage( LinkTarget $target, $reason ) {
+ $title = Title::newFromLinkTarget( $target );
+ $page = WikiPage::factory( $title );
+ $page->doDeleteArticleReal( $reason );
+ }
+
+ /**
+ * Performs a batch of page edits as a specified user
+ * @param User $user
+ * @param array $editData associative array, keys:
+ * - target => LinkTarget page to edit
+ * - summary => string edit summary
+ * - minorEdit => bool mark as minor edit if true (defaults to false)
+ * - botEdit => bool mark as bot edit if true (defaults to false)
+ */
+ private function doPageEdits( User $user, array $editData ) {
+ foreach ( $editData as $singleEditData ) {
+ if ( array_key_exists( 'minorEdit', $singleEditData ) && $singleEditData['minorEdit'] ) {
+ $this->doMinorPageEdit(
+ $user,
+ $singleEditData['target'],
+ $singleEditData['summary']
+ );
+ continue;
+ }
+ if ( array_key_exists( 'botEdit', $singleEditData ) && $singleEditData['botEdit'] ) {
+ $this->doBotPageEdit(
+ $user,
+ $singleEditData['target'],
+ $singleEditData['summary']
+ );
+ continue;
+ }
+ $this->doPageEdit(
+ $user,
+ $singleEditData['target'],
+ $singleEditData['summary']
+ );
+ }
+ }
+
+ private function doListRecentChangesRequest( array $params = [] ) {
+ return $this->doApiRequest(
+ array_merge(
+ [ 'action' => 'query', 'list' => 'recentchanges' ],
+ $params
+ ),
+ null,
+ false,
+ $this->getLoggedInTestUser()
+ );
+ }
+
+ private function doGeneratorRecentChangesRequest( array $params = [] ) {
+ return $this->doApiRequest(
+ array_merge(
+ [ 'action' => 'query', 'generator' => 'recentchanges' ],
+ $params
+ ),
+ null,
+ false,
+ $this->getLoggedInTestUser()
+ );
+ }
+
+ private function getItemsFromApiResponse( array $response ) {
+ return $response[0]['query']['recentchanges'];
+ }
+
+ private function getTitleFormatter() {
+ return new MediaWikiTitleCodec(
+ Language::factory( 'en' ),
+ MediaWikiServices::getInstance()->getGenderCache()
+ );
+ }
+
+ private function getPrefixedText( LinkTarget $target ) {
+ $formatter = $this->getTitleFormatter();
+ return $formatter->getPrefixedText( $target );
+ }
+
+ public function testListRecentChanges_returnsRCInfo() {
+ $target = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->doPageEdit( $this->getLoggedInTestUser(), $target, 'Create the page' );
+
+ $result = $this->doListRecentChangesRequest();
+
+ $this->assertArrayHasKey( 'query', $result[0] );
+ $this->assertArrayHasKey( 'recentchanges', $result[0]['query'] );
+
+ $items = $this->getItemsFromApiResponse( $result );
+ $this->assertCount( 1, $items );
+ $item = $items[0];
+ $this->assertArraySubset(
+ [
+ 'type' => 'new',
+ 'ns' => $target->getNamespace(),
+ 'title' => $this->getPrefixedText( $target ),
+ ],
+ $item
+ );
+ $this->assertArrayNotHasKey( 'bot', $item );
+ $this->assertArrayNotHasKey( 'new', $item );
+ $this->assertArrayNotHasKey( 'minor', $item );
+ $this->assertArrayHasKey( 'pageid', $item );
+ $this->assertArrayHasKey( 'revid', $item );
+ $this->assertArrayHasKey( 'old_revid', $item );
+ }
+
+ public function testIdsPropParameter() {
+ $target = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->doPageEdit( $this->getLoggedInTestUser(), $target, 'Create the page' );
+
+ $result = $this->doListRecentChangesRequest( [ 'rcprop' => 'ids', ] );
+ $items = $this->getItemsFromApiResponse( $result );
+
+ $this->assertCount( 1, $items );
+ $this->assertArrayHasKey( 'pageid', $items[0] );
+ $this->assertArrayHasKey( 'revid', $items[0] );
+ $this->assertArrayHasKey( 'old_revid', $items[0] );
+ $this->assertEquals( 'new', $items[0]['type'] );
+ }
+
+ public function testTitlePropParameter() {
+ $subjectTarget = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $talkTarget = new TitleValue( 1, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->doPageEdits(
+ $this->getLoggedInTestUser(),
+ [
+ [
+ 'target' => $subjectTarget,
+ 'summary' => 'Create the page',
+ ],
+ [
+ 'target' => $talkTarget,
+ 'summary' => 'Create Talk page',
+ ],
+ ]
+ );
+
+ $result = $this->doListRecentChangesRequest( [ 'rcprop' => 'title', ] );
+
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'new',
+ 'ns' => $talkTarget->getNamespace(),
+ 'title' => $this->getPrefixedText( $talkTarget ),
+ ],
+ [
+ 'type' => 'new',
+ 'ns' => $subjectTarget->getNamespace(),
+ 'title' => $this->getPrefixedText( $subjectTarget ),
+ ],
+ ],
+ $this->getItemsFromApiResponse( $result )
+ );
+ }
+
+ public function testFlagsPropParameter() {
+ $normalEditTarget = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $minorEditTarget = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPageM' );
+ $botEditTarget = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPageB' );
+ $this->doPageEdits(
+ $this->getLoggedInTestUser(),
+ [
+ [
+ 'target' => $normalEditTarget,
+ 'summary' => 'Create the page',
+ ],
+ [
+ 'target' => $minorEditTarget,
+ 'summary' => 'Create the page',
+ ],
+ [
+ 'target' => $minorEditTarget,
+ 'summary' => 'Change content',
+ 'minorEdit' => true,
+ ],
+ [
+ 'target' => $botEditTarget,
+ 'summary' => 'Create the page with a bot',
+ 'botEdit' => true,
+ ],
+ ]
+ );
+
+ $result = $this->doListRecentChangesRequest( [ 'rcprop' => 'flags', ] );
+
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'new',
+ 'new' => true,
+ 'minor' => false,
+ 'bot' => true,
+ ],
+ [
+ 'type' => 'edit',
+ 'new' => false,
+ 'minor' => true,
+ 'bot' => false,
+ ],
+ [
+ 'type' => 'new',
+ 'new' => true,
+ 'minor' => false,
+ 'bot' => false,
+ ],
+ [
+ 'type' => 'new',
+ 'new' => true,
+ 'minor' => false,
+ 'bot' => false,
+ ],
+ ],
+ $this->getItemsFromApiResponse( $result )
+ );
+ }
+
+ public function testUserPropParameter() {
+ $userEditTarget = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $anonEditTarget = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPageA' );
+ $this->doPageEdit( $this->getLoggedInTestUser(), $userEditTarget, 'Create the page' );
+ $this->doAnonPageEdit( $anonEditTarget, 'Create the page' );
+
+ $result = $this->doListRecentChangesRequest( [ 'rcprop' => 'user', ] );
+
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'new',
+ 'anon' => true,
+ 'user' => User::newFromId( 0 )->getName(),
+ ],
+ [
+ 'type' => 'new',
+ 'user' => $this->getLoggedInTestUser()->getName(),
+ ],
+ ],
+ $this->getItemsFromApiResponse( $result )
+ );
+ }
+
+ public function testUserIdPropParameter() {
+ $user = $this->getLoggedInTestUser();
+ $userEditTarget = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $anonEditTarget = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPageA' );
+ $this->doPageEdit( $user, $userEditTarget, 'Create the page' );
+ $this->doAnonPageEdit( $anonEditTarget, 'Create the page' );
+
+ $result = $this->doListRecentChangesRequest( [ 'rcprop' => 'userid', ] );
+
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'new',
+ 'anon' => true,
+ 'userid' => 0,
+ ],
+ [
+ 'type' => 'new',
+ 'userid' => $user->getId(),
+ ],
+ ],
+ $this->getItemsFromApiResponse( $result )
+ );
+ }
+
+ public function testCommentPropParameter() {
+ $target = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->doPageEdit( $this->getLoggedInTestUser(), $target, 'Create the <b>page</b>' );
+
+ $result = $this->doListRecentChangesRequest( [ 'rcprop' => 'comment', ] );
+
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'new',
+ 'comment' => 'Create the <b>page</b>',
+ ],
+ ],
+ $this->getItemsFromApiResponse( $result )
+ );
+ }
+
+ public function testParsedCommentPropParameter() {
+ $target = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->doPageEdit( $this->getLoggedInTestUser(), $target, 'Create the <b>page</b>' );
+
+ $result = $this->doListRecentChangesRequest( [ 'rcprop' => 'parsedcomment', ] );
+
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'new',
+ 'parsedcomment' => 'Create the &lt;b&gt;page&lt;/b&gt;',
+ ],
+ ],
+ $this->getItemsFromApiResponse( $result )
+ );
+ }
+
+ public function testTimestampPropParameter() {
+ $target = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->doPageEdit( $this->getLoggedInTestUser(), $target, 'Create the page' );
+
+ $result = $this->doListRecentChangesRequest( [ 'rcprop' => 'timestamp', ] );
+ $items = $this->getItemsFromApiResponse( $result );
+
+ $this->assertCount( 1, $items );
+ $this->assertArrayHasKey( 'timestamp', $items[0] );
+ $this->assertInternalType( 'string', $items[0]['timestamp'] );
+ }
+
+ public function testSizesPropParameter() {
+ $target = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->doPageEdit( $this->getLoggedInTestUser(), $target, 'Create the page' );
+
+ $result = $this->doListRecentChangesRequest( [ 'rcprop' => 'sizes', ] );
+
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'new',
+ 'oldlen' => 0,
+ 'newlen' => 38,
+ ],
+ ],
+ $this->getItemsFromApiResponse( $result )
+ );
+ }
+
+ private function createPageAndDeleteIt( LinkTarget $target ) {
+ $this->doPageEdit( $this->getLoggedInTestUser(),
+ $target,
+ 'Create the page that will be deleted'
+ );
+ $this->deletePage( $target, 'Important Reason' );
+ }
+
+ public function testLoginfoPropParameter() {
+ $target = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->createPageAndDeleteIt( $target );
+
+ $result = $this->doListRecentChangesRequest( [ 'rcprop' => 'loginfo', ] );
+
+ $items = $this->getItemsFromApiResponse( $result );
+ $this->assertCount( 1, $items );
+ $this->assertArraySubset(
+ [
+ 'type' => 'log',
+ 'logtype' => 'delete',
+ 'logaction' => 'delete',
+ 'logparams' => [],
+ ],
+ $items[0]
+ );
+ $this->assertArrayHasKey( 'logid', $items[0] );
+ }
+
+ public function testEmptyPropParameter() {
+ $user = $this->getLoggedInTestUser();
+ $target = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->doPageEdit( $user, $target, 'Create the page' );
+
+ $result = $this->doListRecentChangesRequest( [ 'rcprop' => '', ] );
+
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'new',
+ ]
+ ],
+ $this->getItemsFromApiResponse( $result )
+ );
+ }
+
+ public function testNamespaceParam() {
+ $subjectTarget = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $talkTarget = new TitleValue( 1, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->doPageEdits(
+ $this->getLoggedInTestUser(),
+ [
+ [
+ 'target' => $subjectTarget,
+ 'summary' => 'Create the page',
+ ],
+ [
+ 'target' => $talkTarget,
+ 'summary' => 'Create the talk page',
+ ],
+ ]
+ );
+
+ $result = $this->doListRecentChangesRequest( [ 'rcnamespace' => '0', ] );
+
+ $items = $this->getItemsFromApiResponse( $result );
+ $this->assertCount( 1, $items );
+ $this->assertArraySubset(
+ [
+ 'ns' => 0,
+ 'title' => $this->getPrefixedText( $subjectTarget ),
+ ],
+ $items[0]
+ );
+ }
+
+ public function testShowAnonParams() {
+ $target = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->doAnonPageEdit( $target, 'Create the page' );
+
+ $resultAnon = $this->doListRecentChangesRequest( [
+ 'rcprop' => 'user',
+ 'rcshow' => WatchedItemQueryService::FILTER_ANON
+ ] );
+ $resultNotAnon = $this->doListRecentChangesRequest( [
+ 'rcprop' => 'user',
+ 'rcshow' => WatchedItemQueryService::FILTER_NOT_ANON
+ ] );
+
+ $items = $this->getItemsFromApiResponse( $resultAnon );
+ $this->assertCount( 1, $items );
+ $this->assertArraySubset( [ 'anon' => true ], $items[0] );
+ $this->assertEmpty( $this->getItemsFromApiResponse( $resultNotAnon ) );
+ }
+
+ public function testNewAndEditTypeParameters() {
+ $subjectTarget = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $talkTarget = new TitleValue( 1, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->doPageEdits(
+ $this->getLoggedInTestUser(),
+ [
+ [
+ 'target' => $subjectTarget,
+ 'summary' => 'Create the page',
+ ],
+ [
+ 'target' => $subjectTarget,
+ 'summary' => 'Change the content',
+ ],
+ [
+ 'target' => $talkTarget,
+ 'summary' => 'Create Talk page',
+ ],
+ ]
+ );
+
+ $resultNew = $this->doListRecentChangesRequest( [ 'rcprop' => 'title', 'rctype' => 'new' ] );
+ $resultEdit = $this->doListRecentChangesRequest( [ 'rcprop' => 'title', 'rctype' => 'edit' ] );
+
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'new',
+ 'ns' => $talkTarget->getNamespace(),
+ 'title' => $this->getPrefixedText( $talkTarget ),
+ ],
+ [
+ 'type' => 'new',
+ 'ns' => $subjectTarget->getNamespace(),
+ 'title' => $this->getPrefixedText( $subjectTarget ),
+ ],
+ ],
+ $this->getItemsFromApiResponse( $resultNew )
+ );
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'edit',
+ 'ns' => $subjectTarget->getNamespace(),
+ 'title' => $this->getPrefixedText( $subjectTarget ),
+ ],
+ ],
+ $this->getItemsFromApiResponse( $resultEdit )
+ );
+ }
+
+ public function testLogTypeParameters() {
+ $subjectTarget = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $talkTarget = new TitleValue( 1, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->createPageAndDeleteIt( $subjectTarget );
+ $this->doPageEdit( $this->getLoggedInTestUser(), $talkTarget, 'Create Talk page' );
+
+ $result = $this->doListRecentChangesRequest( [ 'rcprop' => 'title', 'rctype' => 'log' ] );
+
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'log',
+ 'ns' => $subjectTarget->getNamespace(),
+ 'title' => $this->getPrefixedText( $subjectTarget ),
+ ],
+ ],
+ $this->getItemsFromApiResponse( $result )
+ );
+ }
+
+ private function getExternalRC( LinkTarget $target ) {
+ $title = Title::newFromLinkTarget( $target );
+
+ $rc = new RecentChange;
+ $rc->mTitle = $title;
+ $rc->mAttribs = [
+ 'rc_timestamp' => wfTimestamp( TS_MW ),
+ 'rc_namespace' => $title->getNamespace(),
+ 'rc_title' => $title->getDBkey(),
+ 'rc_type' => RC_EXTERNAL,
+ 'rc_source' => 'foo',
+ 'rc_minor' => 0,
+ 'rc_cur_id' => $title->getArticleID(),
+ 'rc_user' => 0,
+ 'rc_user_text' => 'm>External User',
+ 'rc_comment' => '',
+ 'rc_comment_text' => '',
+ 'rc_comment_data' => null,
+ 'rc_this_oldid' => $title->getLatestRevID(),
+ 'rc_last_oldid' => $title->getLatestRevID(),
+ 'rc_bot' => 0,
+ 'rc_ip' => '',
+ 'rc_patrolled' => 0,
+ 'rc_new' => 0,
+ 'rc_old_len' => $title->getLength(),
+ 'rc_new_len' => $title->getLength(),
+ 'rc_deleted' => 0,
+ 'rc_logid' => 0,
+ 'rc_log_type' => null,
+ 'rc_log_action' => '',
+ 'rc_params' => '',
+ ];
+ $rc->mExtra = [
+ 'prefixedDBkey' => $title->getPrefixedDBkey(),
+ 'lastTimestamp' => 0,
+ 'oldSize' => $title->getLength(),
+ 'newSize' => $title->getLength(),
+ 'pageStatus' => 'changed'
+ ];
+
+ return $rc;
+ }
+
+ public function testExternalTypeParameters() {
+ $user = $this->getLoggedInTestUser();
+ $subjectTarget = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $talkTarget = new TitleValue( 1, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->doPageEdit( $user, $subjectTarget, 'Create the page' );
+ $this->doPageEdit( $user, $talkTarget, 'Create Talk page' );
+
+ $rc = $this->getExternalRC( $subjectTarget );
+ $rc->save();
+
+ $result = $this->doListRecentChangesRequest( [ 'rcprop' => 'title', 'rctype' => 'external' ] );
+
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'external',
+ 'ns' => $subjectTarget->getNamespace(),
+ 'title' => $this->getPrefixedText( $subjectTarget ),
+ ],
+ ],
+ $this->getItemsFromApiResponse( $result )
+ );
+ }
+
+ public function testCategorizeTypeParameter() {
+ $user = $this->getLoggedInTestUser();
+ $subjectTarget = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $categoryTarget = new TitleValue( NS_CATEGORY, 'ApiQueryRecentChangesIntegrationTestCategory' );
+ $this->doPageEdits(
+ $user,
+ [
+ [
+ 'target' => $categoryTarget,
+ 'summary' => 'Create the category',
+ ],
+ [
+ 'target' => $subjectTarget,
+ 'summary' => 'Create the page and add it to the category',
+ ],
+ ]
+ );
+ $title = Title::newFromLinkTarget( $subjectTarget );
+ $revision = Revision::newFromTitle( $title );
+
+ $rc = RecentChange::newForCategorization(
+ $revision->getTimestamp(),
+ Title::newFromLinkTarget( $categoryTarget ),
+ $user,
+ $revision->getComment(),
+ $title,
+ 0,
+ $revision->getId(),
+ null,
+ false
+ );
+ $rc->save();
+
+ $result = $this->doListRecentChangesRequest( [ 'rcprop' => 'title', 'rctype' => 'categorize' ] );
+
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'categorize',
+ 'ns' => $categoryTarget->getNamespace(),
+ 'title' => $this->getPrefixedText( $categoryTarget ),
+ ],
+ ],
+ $this->getItemsFromApiResponse( $result )
+ );
+ }
+
+ public function testLimitParam() {
+ $target1 = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $target2 = new TitleValue( 1, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $target3 = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage2' );
+ $this->doPageEdits(
+ $this->getLoggedInTestUser(),
+ [
+ [
+ 'target' => $target1,
+ 'summary' => 'Create the page',
+ ],
+ [
+ 'target' => $target2,
+ 'summary' => 'Create Talk page',
+ ],
+ [
+ 'target' => $target3,
+ 'summary' => 'Create the page',
+ ],
+ ]
+ );
+
+ $resultWithoutLimit = $this->doListRecentChangesRequest( [ 'rcprop' => 'title' ] );
+ $resultWithLimit = $this->doListRecentChangesRequest( [ 'rclimit' => 2, 'rcprop' => 'title' ] );
+
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'new',
+ 'ns' => $target3->getNamespace(),
+ 'title' => $this->getPrefixedText( $target3 )
+ ],
+ [
+ 'type' => 'new',
+ 'ns' => $target2->getNamespace(),
+ 'title' => $this->getPrefixedText( $target2 )
+ ],
+ [
+ 'type' => 'new',
+ 'ns' => $target1->getNamespace(),
+ 'title' => $this->getPrefixedText( $target1 )
+ ],
+ ],
+ $this->getItemsFromApiResponse( $resultWithoutLimit )
+ );
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'new',
+ 'ns' => $target3->getNamespace(),
+ 'title' => $this->getPrefixedText( $target3 )
+ ],
+ [
+ 'type' => 'new',
+ 'ns' => $target2->getNamespace(),
+ 'title' => $this->getPrefixedText( $target2 )
+ ],
+ ],
+ $this->getItemsFromApiResponse( $resultWithLimit )
+ );
+ $this->assertArrayHasKey( 'continue', $resultWithLimit[0] );
+ $this->assertArrayHasKey( 'rccontinue', $resultWithLimit[0]['continue'] );
+ }
+
+ public function testAllRevParam() {
+ $target = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->doPageEdits(
+ $this->getLoggedInTestUser(),
+ [
+ [
+ 'target' => $target,
+ 'summary' => 'Create the page',
+ ],
+ [
+ 'target' => $target,
+ 'summary' => 'Change the content',
+ ],
+ ]
+ );
+
+ $resultAllRev = $this->doListRecentChangesRequest( [ 'rcprop' => 'title', 'rcallrev' => '', ] );
+ $resultNoAllRev = $this->doListRecentChangesRequest( [ 'rcprop' => 'title' ] );
+
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'edit',
+ 'ns' => $target->getNamespace(),
+ 'title' => $this->getPrefixedText( $target ),
+ ],
+ [
+ 'type' => 'new',
+ 'ns' => $target->getNamespace(),
+ 'title' => $this->getPrefixedText( $target ),
+ ],
+ ],
+ $this->getItemsFromApiResponse( $resultNoAllRev )
+ );
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'edit',
+ 'ns' => $target->getNamespace(),
+ 'title' => $this->getPrefixedText( $target ),
+ ],
+ [
+ 'type' => 'new',
+ 'ns' => $target->getNamespace(),
+ 'title' => $this->getPrefixedText( $target ),
+ ],
+ ],
+ $this->getItemsFromApiResponse( $resultAllRev )
+ );
+ }
+
+ public function testDirParams() {
+ $subjectTarget = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $talkTarget = new TitleValue( 1, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->doPageEdits(
+ $this->getLoggedInTestUser(),
+ [
+ [
+ 'target' => $subjectTarget,
+ 'summary' => 'Create the page',
+ ],
+ [
+ 'target' => $talkTarget,
+ 'summary' => 'Create Talk page',
+ ],
+ ]
+ );
+
+ $resultDirOlder = $this->doListRecentChangesRequest(
+ [ 'rcdir' => 'older', 'rcprop' => 'title' ]
+ );
+ $resultDirNewer = $this->doListRecentChangesRequest(
+ [ 'rcdir' => 'newer', 'rcprop' => 'title' ]
+ );
+
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'new',
+ 'ns' => $talkTarget->getNamespace(),
+ 'title' => $this->getPrefixedText( $talkTarget )
+ ],
+ [
+ 'type' => 'new',
+ 'ns' => $subjectTarget->getNamespace(),
+ 'title' => $this->getPrefixedText( $subjectTarget )
+ ],
+ ],
+ $this->getItemsFromApiResponse( $resultDirOlder )
+ );
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'new',
+ 'ns' => $subjectTarget->getNamespace(),
+ 'title' => $this->getPrefixedText( $subjectTarget )
+ ],
+ [
+ 'type' => 'new',
+ 'ns' => $talkTarget->getNamespace(),
+ 'title' => $this->getPrefixedText( $talkTarget )
+ ],
+ ],
+ $this->getItemsFromApiResponse( $resultDirNewer )
+ );
+ }
+
+ public function testStartEndParams() {
+ $target = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->doPageEdit( $this->getLoggedInTestUser(), $target, 'Create the page' );
+
+ $resultStart = $this->doListRecentChangesRequest( [
+ 'rcstart' => '20010115000000',
+ 'rcdir' => 'newer',
+ 'rcprop' => 'title',
+ ] );
+ $resultEnd = $this->doListRecentChangesRequest( [
+ 'rcend' => '20010115000000',
+ 'rcdir' => 'newer',
+ 'rcprop' => 'title',
+ ] );
+
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'new',
+ 'ns' => $target->getNamespace(),
+ 'title' => $this->getPrefixedText( $target ),
+ ]
+ ],
+ $this->getItemsFromApiResponse( $resultStart )
+ );
+ $this->assertEmpty( $this->getItemsFromApiResponse( $resultEnd ) );
+ }
+
+ public function testContinueParam() {
+ $target1 = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $target2 = new TitleValue( 1, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $target3 = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage2' );
+ $this->doPageEdits(
+ $this->getLoggedInTestUser(),
+ [
+ [
+ 'target' => $target1,
+ 'summary' => 'Create the page',
+ ],
+ [
+ 'target' => $target2,
+ 'summary' => 'Create Talk page',
+ ],
+ [
+ 'target' => $target3,
+ 'summary' => 'Create the page',
+ ],
+ ]
+ );
+
+ $firstResult = $this->doListRecentChangesRequest( [ 'rclimit' => 2, 'rcprop' => 'title' ] );
+ $this->assertArrayHasKey( 'continue', $firstResult[0] );
+ $this->assertArrayHasKey( 'rccontinue', $firstResult[0]['continue'] );
+
+ $continuationParam = $firstResult[0]['continue']['rccontinue'];
+
+ $continuedResult = $this->doListRecentChangesRequest(
+ [ 'rccontinue' => $continuationParam, 'rcprop' => 'title' ]
+ );
+
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'new',
+ 'ns' => $target3->getNamespace(),
+ 'title' => $this->getPrefixedText( $target3 ),
+ ],
+ [
+ 'type' => 'new',
+ 'ns' => $target2->getNamespace(),
+ 'title' => $this->getPrefixedText( $target2 ),
+ ],
+ ],
+ $this->getItemsFromApiResponse( $firstResult )
+ );
+ $this->assertEquals(
+ [
+ [
+ 'type' => 'new',
+ 'ns' => $target1->getNamespace(),
+ 'title' => $this->getPrefixedText( $target1 )
+ ]
+ ],
+ $this->getItemsFromApiResponse( $continuedResult )
+ );
+ }
+
+ public function testGeneratorRecentChangesPropInfo_returnsRCPages() {
+ $target = new TitleValue( 0, 'ApiQueryRecentChangesIntegrationTestPage' );
+ $this->doPageEdit( $this->getLoggedInTestUser(), $target, 'Create the page' );
+
+ $result = $this->doGeneratorRecentChangesRequest( [ 'prop' => 'info' ] );
+
+ $this->assertArrayHasKey( 'query', $result[0] );
+ $this->assertArrayHasKey( 'pages', $result[0]['query'] );
+
+ // $result[0]['query']['pages'] uses page ids as keys. Page ids don't matter here, so drop them
+ $pages = array_values( $result[0]['query']['pages'] );
+
+ $this->assertCount( 1, $pages );
+ $this->assertArraySubset(
+ [
+ 'ns' => $target->getNamespace(),
+ 'title' => $this->getPrefixedText( $target ),
+ 'new' => true,
+ ],
+ $pages[0]
+ );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php b/www/wiki/tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php
index fdbedede..5f59d6fb 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php
@@ -4,9 +4,9 @@ use MediaWiki\Linker\LinkTarget;
use MediaWiki\MediaWikiServices;
/**
+ * @group medium
* @group API
* @group Database
- * @group medium
*
* @covers ApiQueryWatchlist
*/
@@ -23,7 +23,6 @@ class ApiQueryWatchlistIntegrationTest extends ApiTestCase {
parent::setUp();
self::$users['ApiQueryWatchlistIntegrationTestUser'] = $this->getMutableTestUser();
self::$users['ApiQueryWatchlistIntegrationTestUser2'] = $this->getMutableTestUser();
- $this->doLogin( 'ApiQueryWatchlistIntegrationTestUser' );
}
private function getLoggedInTestUser() {
@@ -163,6 +162,9 @@ class ApiQueryWatchlistIntegrationTest extends ApiTestCase {
}
private function doListWatchlistRequest( array $params = [], $user = null ) {
+ if ( $user === null ) {
+ $user = $this->getLoggedInTestUser();
+ }
return $this->doApiRequest(
array_merge(
[ 'action' => 'query', 'list' => 'watchlist' ],
@@ -176,7 +178,7 @@ class ApiQueryWatchlistIntegrationTest extends ApiTestCase {
array_merge(
[ 'action' => 'query', 'generator' => 'watchlist' ],
$params
- )
+ ), null, false, $this->getLoggedInTestUser()
);
}
@@ -629,6 +631,7 @@ class ApiQueryWatchlistIntegrationTest extends ApiTestCase {
'type' => 'new',
'patrolled' => true,
'unpatrolled' => false,
+ 'autopatrolled' => false,
]
],
$this->getItemsFromApiResponse( $result )
@@ -973,6 +976,7 @@ class ApiQueryWatchlistIntegrationTest extends ApiTestCase {
'type' => 'new',
'patrolled' => true,
'unpatrolled' => false,
+ 'autopatrolled' => false,
]
],
$this->getItemsFromApiResponse( $resultPatrolled )
@@ -1072,7 +1076,7 @@ class ApiQueryWatchlistIntegrationTest extends ApiTestCase {
'rc_minor' => 0,
'rc_cur_id' => $title->getArticleID(),
'rc_user' => 0,
- 'rc_user_text' => 'External User',
+ 'rc_user_text' => 'ext>External User',
'rc_comment' => '',
'rc_comment_text' => '',
'rc_comment_data' => null,
diff --git a/www/wiki/tests/phpunit/includes/api/ApiQueryWatchlistRawIntegrationTest.php b/www/wiki/tests/phpunit/includes/api/ApiQueryWatchlistRawIntegrationTest.php
index 0f01664e..2af63c49 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiQueryWatchlistRawIntegrationTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiQueryWatchlistRawIntegrationTest.php
@@ -17,7 +17,6 @@ class ApiQueryWatchlistRawIntegrationTest extends ApiTestCase {
= $this->getMutableTestUser();
self::$users['ApiQueryWatchlistRawIntegrationTestUser2']
= $this->getMutableTestUser();
- $this->doLogin( 'ApiQueryWatchlistRawIntegrationTestUser' );
}
private function getLoggedInTestUser() {
@@ -36,14 +35,14 @@ class ApiQueryWatchlistRawIntegrationTest extends ApiTestCase {
return $this->doApiRequest( array_merge(
[ 'action' => 'query', 'list' => 'watchlistraw' ],
$params
- ) );
+ ), null, false, $this->getLoggedInTestUser() );
}
private function doGeneratorWatchlistRawRequest( array $params = [] ) {
return $this->doApiRequest( array_merge(
[ 'action' => 'query', 'generator' => 'watchlistraw' ],
$params
- ) );
+ ), null, false, $this->getLoggedInTestUser() );
}
private function getItemsFromApiResponse( array $response ) {
diff --git a/www/wiki/tests/phpunit/includes/api/ApiSetNotificationTimestampIntegrationTest.php b/www/wiki/tests/phpunit/includes/api/ApiSetNotificationTimestampIntegrationTest.php
index ef4f5139..dacd48f6 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiSetNotificationTimestampIntegrationTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiSetNotificationTimestampIntegrationTest.php
@@ -13,7 +13,6 @@ class ApiSetNotificationTimestampIntegrationTest extends ApiTestCase {
protected function setUp() {
parent::setUp();
self::$users[__CLASS__] = new TestUser( __CLASS__ );
- $this->doLogin( __CLASS__ );
}
public function testStuff() {
diff --git a/www/wiki/tests/phpunit/includes/api/ApiStashEditTest.php b/www/wiki/tests/phpunit/includes/api/ApiStashEditTest.php
index e2462c61..60cda090 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiStashEditTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiStashEditTest.php
@@ -9,7 +9,6 @@
class ApiStashEditTest extends ApiTestCase {
public function testBasicEdit() {
- $this->doLogin();
$apiResult = $this->doApiRequestWithToken(
[
'action' => 'stashedit',
diff --git a/www/wiki/tests/phpunit/includes/api/ApiTestCase.php b/www/wiki/tests/phpunit/includes/api/ApiTestCase.php
index abef1c92..974e9a2d 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiTestCase.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiTestCase.php
@@ -1,5 +1,7 @@
<?php
+use MediaWiki\Session\SessionManager;
+
abstract class ApiTestCase extends MediaWikiLangTestCase {
protected static $apiUrl;
@@ -55,6 +57,28 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
}
/**
+ * Revision-deletes a revision.
+ *
+ * @param Revision|int $rev Revision to delete
+ * @param array $value Keys are Revision::DELETED_* flags. Values are 1 to set the bit, 0 to
+ * clear, -1 to leave alone. (All other values also clear the bit.)
+ * @param string $comment Deletion comment
+ */
+ protected function revisionDelete(
+ $rev, array $value = [ Revision::DELETED_TEXT => 1 ], $comment = ''
+ ) {
+ if ( is_int( $rev ) ) {
+ $rev = Revision::newFromId( $rev );
+ }
+ RevisionDeleter::createList(
+ 'revision', RequestContext::getMain(), $rev->getTitle(), [ $rev->getId() ]
+ )->setVisibility( [
+ 'value' => $value,
+ 'comment' => $comment,
+ ] );
+ }
+
+ /**
* Does the API request and returns the result.
*
* The returned value is an array containing
@@ -67,11 +91,14 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
* @param array|null $session
* @param bool $appendModule
* @param User|null $user
+ * @param string|null $tokenType Set to a string like 'csrf' to send an
+ * appropriate token
*
+ * @throws ApiUsageException
* @return array
*/
protected function doApiRequest( array $params, array $session = null,
- $appendModule = false, User $user = null
+ $appendModule = false, User $user = null, $tokenType = null
) {
global $wgRequest, $wgUser;
@@ -80,12 +107,30 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
$session = $wgRequest->getSessionArray();
}
+ $sessionObj = SessionManager::singleton()->getEmptySession();
+
+ if ( $session !== null ) {
+ foreach ( $session as $key => $value ) {
+ $sessionObj->set( $key, $value );
+ }
+ }
+
// set up global environment
if ( $user ) {
$wgUser = $user;
}
- $wgRequest = new FauxRequest( $params, true, $session );
+ if ( $tokenType !== null ) {
+ if ( $tokenType === 'auto' ) {
+ $tokenType = ( new ApiMain() )->getModuleManager()
+ ->getModule( $params['action'], 'action' )->needsToken();
+ }
+ $params['token'] = ApiQueryTokens::getToken(
+ $wgUser, $sessionObj, ApiQueryTokens::getTokenTypeSalts()[$tokenType]
+ )->toString();
+ }
+
+ $wgRequest = new FauxRequest( $params, true, $sessionObj );
RequestContext::getMain()->setRequest( $wgRequest );
RequestContext::getMain()->setUser( $wgUser );
MediaWiki\Auth\AuthManager::resetCache();
@@ -113,76 +158,44 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
}
/**
- * Add an edit token to the API request
- * This is cheating a bit -- we grab a token in the correct format and then
- * add it to the pseudo-session and to the request, without actually
- * requesting a "real" edit token.
+ * Convenience function to access the token parameter of doApiRequest()
+ * more succinctly.
*
* @param array $params Key-value API params
* @param array|null $session Session array
* @param User|null $user A User object for the context
+ * @param string $tokenType Which token type to pass
* @return array Result of the API call
- * @throws Exception In case wsToken is not set in the session
*/
protected function doApiRequestWithToken( array $params, array $session = null,
- User $user = null
+ User $user = null, $tokenType = 'auto'
) {
- global $wgRequest;
-
- if ( $session === null ) {
- $session = $wgRequest->getSessionArray();
- }
-
- if ( isset( $session['wsToken'] ) && $session['wsToken'] ) {
- // @todo Why does this directly mess with the session? Fix that.
- // add edit token to fake session
- $session['wsTokenSecrets']['default'] = $session['wsToken'];
- // add token to request parameters
- $timestamp = wfTimestamp();
- $params['token'] = hash_hmac( 'md5', $timestamp, $session['wsToken'] ) .
- dechex( $timestamp ) .
- MediaWiki\Session\Token::SUFFIX;
-
- return $this->doApiRequest( $params, $session, false, $user );
- } else {
- throw new Exception( "Session token not available" );
- }
+ return $this->doApiRequest( $params, $session, false, $user, $tokenType );
}
- protected function doLogin( $testUser = 'sysop' ) {
+ /**
+ * Previously this would do API requests to log in, as well as setting $wgUser and the request
+ * context's user. The API requests are unnecessary, and the global-setting is unwanted, so
+ * this method should not be called. Instead, pass appropriate User values directly to
+ * functions that need them. For functions that still rely on $wgUser, set that directly. If
+ * you just want to log in the test sysop user, don't do anything -- that's the default.
+ *
+ * @param TestUser|string $testUser Object, or key to self::$users such as 'sysop' or 'uploader'
+ * @deprecated since 1.31
+ */
+ protected function doLogin( $testUser = null ) {
+ global $wgUser;
+
if ( $testUser === null ) {
$testUser = static::getTestSysop();
} elseif ( is_string( $testUser ) && array_key_exists( $testUser, self::$users ) ) {
- $testUser = self::$users[ $testUser ];
+ $testUser = self::$users[$testUser];
} elseif ( !$testUser instanceof TestUser ) {
- throw new MWException( "Can not log in to undefined user $testUser" );
+ throw new MWException( "Can't log in to undefined user $testUser" );
}
- $data = $this->doApiRequest( [
- 'action' => 'login',
- 'lgname' => $testUser->getUser()->getName(),
- 'lgpassword' => $testUser->getPassword() ] );
-
- $token = $data[0]['login']['token'];
-
- $data = $this->doApiRequest(
- [
- 'action' => 'login',
- 'lgtoken' => $token,
- 'lgname' => $testUser->getUser()->getName(),
- 'lgpassword' => $testUser->getPassword(),
- ],
- $data[2]
- );
-
- if ( $data[0]['login']['result'] === 'Success' ) {
- // DWIM
- global $wgUser;
- $wgUser = $testUser->getUser();
- RequestContext::getMain()->setUser( $wgUser );
- }
-
- return $data;
+ $wgUser = $testUser->getUser();
+ RequestContext::getMain()->setUser( $wgUser );
}
protected function getTokenList( TestUser $user, $session = null ) {
@@ -218,6 +231,9 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
);
}
+ /**
+ * @coversNothing
+ */
public function testApiTestGroup() {
$groups = PHPUnit_Util_Test::getGroups( static::class );
$constraint = PHPUnit_Framework_Assert::logicalOr(
@@ -228,4 +244,17 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
'ApiTestCase::setUp can be slow, tests must be "medium" or "large"'
);
}
+
+ /**
+ * Expect an ApiUsageException to be thrown with the given parameters, which are the same as
+ * ApiUsageException::newWithMessage()'s parameters. This allows checking for an exception
+ * whose text is given by a message key instead of text, so as not to hard-code the message's
+ * text into test code.
+ */
+ protected function setExpectedApiException(
+ $msg, $code = null, array $data = null, $httpCode = 0
+ ) {
+ $expected = ApiUsageException::newWithMessage( null, $msg, $code, $data, $httpCode );
+ $this->setExpectedException( ApiUsageException::class, $expected->getMessage() );
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiTestCaseUpload.php b/www/wiki/tests/phpunit/includes/api/ApiTestCaseUpload.php
index f15da2ee..3670fad8 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiTestCaseUpload.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiTestCaseUpload.php
@@ -1,153 +1,8 @@
<?php
/**
- * Abstract class to support upload tests
+ * For backward compatibility since 1.31
*/
-abstract class ApiTestCaseUpload extends ApiTestCase {
- /**
- * Fixture -- run before every test
- */
- protected function setUp() {
- parent::setUp();
+abstract class ApiTestCaseUpload extends ApiUploadTestCase {
- $this->setMwGlobals( [
- 'wgEnableUploads' => true,
- 'wgEnableAPI' => true,
- ] );
-
- $this->clearFakeUploads();
- }
-
- /**
- * Helper function -- remove files and associated articles by Title
- *
- * @param Title $title Title to be removed
- *
- * @return bool
- */
- public function deleteFileByTitle( $title ) {
- if ( $title->exists() ) {
- $file = wfFindFile( $title, [ 'ignoreRedirect' => true ] );
- $noOldArchive = ""; // yes this really needs to be set this way
- $comment = "removing for test";
- $restrictDeletedVersions = false;
- $status = FileDeleteForm::doDelete(
- $title,
- $file,
- $noOldArchive,
- $comment,
- $restrictDeletedVersions
- );
-
- if ( !$status->isGood() ) {
- return false;
- }
-
- $page = WikiPage::factory( $title );
- $page->doDeleteArticle( "removing for test" );
-
- // see if it now doesn't exist; reload
- $title = Title::newFromText( $title->getText(), NS_FILE );
- }
-
- return !( $title && $title instanceof Title && $title->exists() );
- }
-
- /**
- * Helper function -- remove files and associated articles with a particular filename
- *
- * @param string $fileName Filename to be removed
- *
- * @return bool
- */
- public function deleteFileByFileName( $fileName ) {
- return $this->deleteFileByTitle( Title::newFromText( $fileName, NS_FILE ) );
- }
-
- /**
- * Helper function -- given a file on the filesystem, find matching
- * content in the db (and associated articles) and remove them.
- *
- * @param string $filePath Path to file on the filesystem
- *
- * @return bool
- */
- public function deleteFileByContent( $filePath ) {
- $hash = FSFile::getSha1Base36FromPath( $filePath );
- $dupes = RepoGroup::singleton()->findBySha1( $hash );
- $success = true;
- foreach ( $dupes as $dupe ) {
- $success &= $this->deleteFileByTitle( $dupe->getTitle() );
- }
-
- return $success;
- }
-
- /**
- * Fake an upload by dumping the file into temp space, and adding info to $_FILES.
- * (This is what PHP would normally do).
- *
- * @param string $fieldName Name this would have in the upload form
- * @param string $fileName Name to title this
- * @param string $type MIME type
- * @param string $filePath Path where to find file contents
- *
- * @throws Exception
- * @return bool
- */
- function fakeUploadFile( $fieldName, $fileName, $type, $filePath ) {
- $tmpName = $this->getNewTempFile();
- if ( !file_exists( $filePath ) ) {
- throw new Exception( "$filePath doesn't exist!" );
- }
-
- if ( !copy( $filePath, $tmpName ) ) {
- throw new Exception( "couldn't copy $filePath to $tmpName" );
- }
-
- clearstatcache();
- $size = filesize( $tmpName );
- if ( $size === false ) {
- throw new Exception( "couldn't stat $tmpName" );
- }
-
- $_FILES[$fieldName] = [
- 'name' => $fileName,
- 'type' => $type,
- 'tmp_name' => $tmpName,
- 'size' => $size,
- 'error' => null
- ];
-
- return true;
- }
-
- function fakeUploadChunk( $fieldName, $fileName, $type, & $chunkData ) {
- $tmpName = $this->getNewTempFile();
- // copy the chunk data to temp location:
- if ( !file_put_contents( $tmpName, $chunkData ) ) {
- throw new Exception( "couldn't copy chunk data to $tmpName" );
- }
-
- clearstatcache();
- $size = filesize( $tmpName );
- if ( $size === false ) {
- throw new Exception( "couldn't stat $tmpName" );
- }
-
- $_FILES[$fieldName] = [
- 'name' => $fileName,
- 'type' => $type,
- 'tmp_name' => $tmpName,
- 'size' => $size,
- 'error' => null
- ];
- }
-
- /**
- * Remove traces of previous fake uploads
- */
- function clearFakeUploads() {
- $_FILES = [];
- }
}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiUnblockTest.php b/www/wiki/tests/phpunit/includes/api/ApiUnblockTest.php
index 971b63c3..d20de0dc 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiUnblockTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiUnblockTest.php
@@ -8,11 +8,6 @@
* @covers ApiUnblock
*/
class ApiUnblockTest extends ApiTestCase {
- protected function setUp() {
- parent::setUp();
- $this->doLogin();
- }
-
/**
* @expectedException ApiUsageException
*/
@@ -22,10 +17,7 @@ class ApiUnblockTest extends ApiTestCase {
'action' => 'unblock',
'user' => 'UTApiBlockee',
'reason' => 'Some reason',
- ],
- null,
- false,
- self::$users['sysop']->getUser()
+ ]
);
}
}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiUploadTest.php b/www/wiki/tests/phpunit/includes/api/ApiUploadTest.php
index 9b79e6c5..41c9aed4 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiUploadTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiUploadTest.php
@@ -19,8 +19,10 @@
* @group Database
* @group medium
* @group Broken
+ *
+ * @covers ApiUpload
*/
-class ApiUploadTest extends ApiTestCaseUpload {
+class ApiUploadTest extends ApiUploadTestCase {
/**
* Testing login
* XXX this is a funny way of getting session context
@@ -51,7 +53,6 @@ class ApiUploadTest extends ApiTestCaseUpload {
$this->assertArrayHasKey( "login", $result );
$this->assertArrayHasKey( "result", $result['login'] );
$this->assertEquals( "Success", $result['login']['result'] );
- $this->assertArrayHasKey( 'lgtoken', $result['login'] );
$this->assertNotEmpty( $session, 'API Login must return a session' );
@@ -69,7 +70,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
] );
} catch ( ApiUsageException $e ) {
$exception = true;
- $this->assertEquals( 'The "token" parameter must be set', $e->getMessage() );
+ $this->assertContains( 'The "token" parameter must be set', $e->getMessage() );
}
$this->assertTrue( $exception, "Got exception" );
}
@@ -85,8 +86,10 @@ class ApiUploadTest extends ApiTestCaseUpload {
], $session, self::$users['uploader']->getUser() );
} catch ( ApiUsageException $e ) {
$exception = true;
- $this->assertEquals( "One of the parameters filekey, file, url is required",
- $e->getMessage() );
+ $this->assertEquals(
+ 'One of the parameters "filekey", "file" and "url" is required.',
+ $e->getMessage()
+ );
}
$this->assertTrue( $exception, "Got exception" );
}
@@ -453,9 +456,9 @@ class ApiUploadTest extends ApiTestCaseUpload {
$chunkSessionKey = false;
$resultOffset = 0;
// Open the file:
- MediaWiki\suppressWarnings();
+ Wikimedia\suppressWarnings();
$handle = fopen( $filePath, "r" );
- MediaWiki\restoreWarnings();
+ Wikimedia\restoreWarnings();
if ( $handle === false ) {
$this->markTestIncomplete( "could not open file: $filePath" );
@@ -463,9 +466,9 @@ class ApiUploadTest extends ApiTestCaseUpload {
while ( !feof( $handle ) ) {
// Get the current chunk
- MediaWiki\suppressWarnings();
+ Wikimedia\suppressWarnings();
$chunkData = fread( $handle, $chunkSize );
- MediaWiki\restoreWarnings();
+ Wikimedia\restoreWarnings();
// Upload the current chunk into the $_FILE object:
$this->fakeUploadChunk( 'chunk', 'blob', $mimeType, $chunkData );
diff --git a/www/wiki/tests/phpunit/includes/api/ApiUploadTestCase.php b/www/wiki/tests/phpunit/includes/api/ApiUploadTestCase.php
new file mode 100644
index 00000000..3c7efd57
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/api/ApiUploadTestCase.php
@@ -0,0 +1,153 @@
+<?php
+
+/**
+ * Abstract class to support upload tests
+ */
+abstract class ApiUploadTestCase extends ApiTestCase {
+ /**
+ * Fixture -- run before every test
+ */
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( [
+ 'wgEnableUploads' => true,
+ 'wgEnableAPI' => true,
+ ] );
+
+ $this->clearFakeUploads();
+ }
+
+ /**
+ * Helper function -- remove files and associated articles by Title
+ *
+ * @param Title $title Title to be removed
+ *
+ * @return bool
+ */
+ public function deleteFileByTitle( $title ) {
+ if ( $title->exists() ) {
+ $file = wfFindFile( $title, [ 'ignoreRedirect' => true ] );
+ $noOldArchive = ""; // yes this really needs to be set this way
+ $comment = "removing for test";
+ $restrictDeletedVersions = false;
+ $status = FileDeleteForm::doDelete(
+ $title,
+ $file,
+ $noOldArchive,
+ $comment,
+ $restrictDeletedVersions
+ );
+
+ if ( !$status->isGood() ) {
+ return false;
+ }
+
+ $page = WikiPage::factory( $title );
+ $page->doDeleteArticle( "removing for test" );
+
+ // see if it now doesn't exist; reload
+ $title = Title::newFromText( $title->getText(), NS_FILE );
+ }
+
+ return !( $title && $title instanceof Title && $title->exists() );
+ }
+
+ /**
+ * Helper function -- remove files and associated articles with a particular filename
+ *
+ * @param string $fileName Filename to be removed
+ *
+ * @return bool
+ */
+ public function deleteFileByFileName( $fileName ) {
+ return $this->deleteFileByTitle( Title::newFromText( $fileName, NS_FILE ) );
+ }
+
+ /**
+ * Helper function -- given a file on the filesystem, find matching
+ * content in the db (and associated articles) and remove them.
+ *
+ * @param string $filePath Path to file on the filesystem
+ *
+ * @return bool
+ */
+ public function deleteFileByContent( $filePath ) {
+ $hash = FSFile::getSha1Base36FromPath( $filePath );
+ $dupes = RepoGroup::singleton()->findBySha1( $hash );
+ $success = true;
+ foreach ( $dupes as $dupe ) {
+ $success &= $this->deleteFileByTitle( $dupe->getTitle() );
+ }
+
+ return $success;
+ }
+
+ /**
+ * Fake an upload by dumping the file into temp space, and adding info to $_FILES.
+ * (This is what PHP would normally do).
+ *
+ * @param string $fieldName Name this would have in the upload form
+ * @param string $fileName Name to title this
+ * @param string $type MIME type
+ * @param string $filePath Path where to find file contents
+ *
+ * @throws Exception
+ * @return bool
+ */
+ function fakeUploadFile( $fieldName, $fileName, $type, $filePath ) {
+ $tmpName = $this->getNewTempFile();
+ if ( !file_exists( $filePath ) ) {
+ throw new Exception( "$filePath doesn't exist!" );
+ }
+
+ if ( !copy( $filePath, $tmpName ) ) {
+ throw new Exception( "couldn't copy $filePath to $tmpName" );
+ }
+
+ clearstatcache();
+ $size = filesize( $tmpName );
+ if ( $size === false ) {
+ throw new Exception( "couldn't stat $tmpName" );
+ }
+
+ $_FILES[$fieldName] = [
+ 'name' => $fileName,
+ 'type' => $type,
+ 'tmp_name' => $tmpName,
+ 'size' => $size,
+ 'error' => null
+ ];
+
+ return true;
+ }
+
+ function fakeUploadChunk( $fieldName, $fileName, $type, & $chunkData ) {
+ $tmpName = $this->getNewTempFile();
+ // copy the chunk data to temp location:
+ if ( !file_put_contents( $tmpName, $chunkData ) ) {
+ throw new Exception( "couldn't copy chunk data to $tmpName" );
+ }
+
+ clearstatcache();
+ $size = filesize( $tmpName );
+ if ( $size === false ) {
+ throw new Exception( "couldn't stat $tmpName" );
+ }
+
+ $_FILES[$fieldName] = [
+ 'name' => $fileName,
+ 'type' => $type,
+ 'tmp_name' => $tmpName,
+ 'size' => $size,
+ 'error' => null
+ ];
+ }
+
+ /**
+ * Remove traces of previous fake uploads
+ */
+ function clearFakeUploads() {
+ $_FILES = [];
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiUsageExceptionTest.php b/www/wiki/tests/phpunit/includes/api/ApiUsageExceptionTest.php
new file mode 100644
index 00000000..bb720211
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/api/ApiUsageExceptionTest.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @covers ApiUsageException
+ */
+class ApiUsageExceptionTest extends MediaWikiTestCase {
+
+ public function testCreateWithStatusValue_CanGetAMessageObject() {
+ $messageKey = 'some-message-key';
+ $messageParameter = 'some-parameter';
+ $statusValue = new StatusValue();
+ $statusValue->fatal( $messageKey, $messageParameter );
+
+ $apiUsageException = new ApiUsageException( null, $statusValue );
+ /** @var \Message $gotMessage */
+ $gotMessage = $apiUsageException->getMessageObject();
+
+ $this->assertInstanceOf( \Message::class, $gotMessage );
+ $this->assertEquals( $messageKey, $gotMessage->getKey() );
+ $this->assertEquals( [ $messageParameter ], $gotMessage->getParams() );
+ }
+
+ public function testNewWithMessage_ThenGetMessageObject_ReturnsApiMessageWithProvidedData() {
+ $expectedMessage = new Message( 'some-message-key', [ 'some message parameter' ] );
+ $expectedCode = 'some-error-code';
+ $expectedData = [ 'some-error-data' ];
+
+ $apiUsageException = ApiUsageException::newWithMessage(
+ null,
+ $expectedMessage,
+ $expectedCode,
+ $expectedData
+ );
+ /** @var \ApiMessage $gotMessage */
+ $gotMessage = $apiUsageException->getMessageObject();
+
+ $this->assertInstanceOf( \ApiMessage::class, $gotMessage );
+ $this->assertEquals( $expectedMessage->getKey(), $gotMessage->getKey() );
+ $this->assertEquals( $expectedMessage->getParams(), $gotMessage->getParams() );
+ $this->assertEquals( $expectedCode, $gotMessage->getApiCode() );
+ $this->assertEquals( $expectedData, $gotMessage->getApiData() );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiUserrightsTest.php b/www/wiki/tests/phpunit/includes/api/ApiUserrightsTest.php
new file mode 100644
index 00000000..0229e767
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/api/ApiUserrightsTest.php
@@ -0,0 +1,358 @@
+<?php
+
+/**
+ * @group API
+ * @group Database
+ * @group medium
+ *
+ * @covers ApiUserrights
+ */
+class ApiUserrightsTest extends ApiTestCase {
+ /**
+ * Unsets $wgGroupPermissions['bureaucrat']['userrights'], and sets
+ * $wgAddGroups['bureaucrat'] and $wgRemoveGroups['bureaucrat'] to the
+ * specified values.
+ *
+ * @param array|bool $add Groups bureaucrats should be allowed to add, true for all
+ * @param array|bool $remove Groups bureaucrats should be allowed to remove, true for all
+ */
+ protected function setPermissions( $add = [], $remove = [] ) {
+ global $wgAddGroups, $wgRemoveGroups;
+
+ $this->setGroupPermissions( 'bureaucrat', 'userrights', false );
+
+ if ( $add ) {
+ $this->stashMwGlobals( 'wgAddGroups' );
+ $wgAddGroups['bureaucrat'] = $add;
+ }
+ if ( $remove ) {
+ $this->stashMwGlobals( 'wgRemoveGroups' );
+ $wgRemoveGroups['bureaucrat'] = $remove;
+ }
+ }
+
+ /**
+ * Perform an API userrights request that's expected to be successful.
+ *
+ * @param array|string $expectedGroups Group(s) that the user is expected
+ * to have after the API request
+ * @param array $params Array to pass to doApiRequestWithToken(). 'action'
+ * => 'userrights' is implicit. If no 'user' or 'userid' is specified,
+ * we add a 'user' parameter. If no 'add' or 'remove' is specified, we
+ * add 'add' => 'sysop'.
+ * @param User|null $user The user that we're modifying. The user must be
+ * mutable, because we're going to change its groups! null means that
+ * we'll make up our own user to modify, and doesn't make sense if 'user'
+ * or 'userid' is specified in $params.
+ */
+ protected function doSuccessfulRightsChange(
+ $expectedGroups = 'sysop', array $params = [], User $user = null
+ ) {
+ $expectedGroups = (array)$expectedGroups;
+ $params['action'] = 'userrights';
+
+ if ( !$user ) {
+ $user = $this->getMutableTestUser()->getUser();
+ }
+
+ $this->assertTrue( TestUserRegistry::isMutable( $user ),
+ 'Immutable user passed to doSuccessfulRightsChange!' );
+
+ if ( !isset( $params['user'] ) && !isset( $params['userid'] ) ) {
+ $params['user'] = $user->getName();
+ }
+ if ( !isset( $params['add'] ) && !isset( $params['remove'] ) ) {
+ $params['add'] = 'sysop';
+ }
+
+ $res = $this->doApiRequestWithToken( $params );
+
+ $user->clearInstanceCache();
+ $this->assertSame( $expectedGroups, $user->getGroups() );
+
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ /**
+ * Perform an API userrights request that's expected to fail.
+ *
+ * @param string $expectedException Expected exception text
+ * @param array $params As for doSuccessfulRightsChange()
+ * @param User|null $user As for doSuccessfulRightsChange(). If there's no
+ * user who will possibly be affected (such as if an invalid username is
+ * provided in $params), pass null.
+ */
+ protected function doFailedRightsChange(
+ $expectedException, array $params = [], User $user = null
+ ) {
+ $params['action'] = 'userrights';
+
+ $this->setExpectedException( ApiUsageException::class, $expectedException );
+
+ if ( !$user ) {
+ // If 'user' or 'userid' is specified and $user was not specified,
+ // the user we're creating now will have nothing to do with the API
+ // request, but that's okay, since we're just testing that it has
+ // no groups.
+ $user = $this->getMutableTestUser()->getUser();
+ }
+
+ $this->assertTrue( TestUserRegistry::isMutable( $user ),
+ 'Immutable user passed to doFailedRightsChange!' );
+
+ if ( !isset( $params['user'] ) && !isset( $params['userid'] ) ) {
+ $params['user'] = $user->getName();
+ }
+ if ( !isset( $params['add'] ) && !isset( $params['remove'] ) ) {
+ $params['add'] = 'sysop';
+ }
+ $expectedGroups = $user->getGroups();
+
+ try {
+ $this->doApiRequestWithToken( $params );
+ } finally {
+ $user->clearInstanceCache();
+ $this->assertSame( $expectedGroups, $user->getGroups() );
+ }
+ }
+
+ public function testAdd() {
+ $this->doSuccessfulRightsChange();
+ }
+
+ public function testBlockedWithUserrights() {
+ global $wgUser;
+
+ $block = new Block( [ 'address' => $wgUser, 'by' => $wgUser->getId(), ] );
+ $block->insert();
+
+ try {
+ $this->doSuccessfulRightsChange();
+ } finally {
+ $block->delete();
+ $wgUser->clearInstanceCache();
+ }
+ }
+
+ public function testBlockedWithoutUserrights() {
+ $user = $this->getTestSysop()->getUser();
+
+ $this->setPermissions( true, true );
+
+ $block = new Block( [ 'address' => $user, 'by' => $user->getId() ] );
+ $block->insert();
+
+ try {
+ $this->doFailedRightsChange( 'You have been blocked from editing.' );
+ } finally {
+ $block->delete();
+ $user->clearInstanceCache();
+ }
+ }
+
+ public function testAddMultiple() {
+ $this->doSuccessfulRightsChange(
+ [ 'bureaucrat', 'sysop' ],
+ [ 'add' => 'bureaucrat|sysop' ]
+ );
+ }
+
+ public function testTooFewExpiries() {
+ $this->doFailedRightsChange(
+ '2 expiry timestamps were provided where 3 were needed.',
+ [ 'add' => 'sysop|bureaucrat|bot', 'expiry' => 'infinity|tomorrow' ]
+ );
+ }
+
+ public function testTooManyExpiries() {
+ $this->doFailedRightsChange(
+ '3 expiry timestamps were provided where 2 were needed.',
+ [ 'add' => 'sysop|bureaucrat', 'expiry' => 'infinity|tomorrow|never' ]
+ );
+ }
+
+ public function testInvalidExpiry() {
+ $this->doFailedRightsChange( 'Invalid expiry time', [ 'expiry' => 'yummy lollipops!' ] );
+ }
+
+ public function testMultipleInvalidExpiries() {
+ $this->doFailedRightsChange(
+ 'Invalid expiry time "foo".',
+ [ 'add' => 'sysop|bureaucrat', 'expiry' => 'foo|bar' ]
+ );
+ }
+
+ public function testWithTag() {
+ ChangeTags::defineTag( 'custom tag' );
+
+ $user = $this->getMutableTestUser()->getUser();
+
+ $this->doSuccessfulRightsChange( 'sysop', [ 'tags' => 'custom tag' ], $user );
+
+ $dbr = wfGetDB( DB_REPLICA );
+ $this->assertSame(
+ 'custom tag',
+ $dbr->selectField(
+ [ 'change_tag', 'logging' ],
+ 'ct_tag',
+ [
+ 'ct_log_id = log_id',
+ 'log_namespace' => NS_USER,
+ 'log_title' => strtr( $user->getName(), ' ', '_' )
+ ],
+ __METHOD__
+ )
+ );
+ }
+
+ public function testWithoutTagPermission() {
+ global $wgGroupPermissions;
+
+ ChangeTags::defineTag( 'custom tag' );
+
+ $this->stashMwGlobals( 'wgGroupPermissions' );
+ $wgGroupPermissions['user']['applychangetags'] = false;
+
+ $this->doFailedRightsChange(
+ 'You do not have permission to apply change tags along with your changes.',
+ [ 'tags' => 'custom tag' ]
+ );
+ }
+
+ public function testNonexistentUser() {
+ $this->doFailedRightsChange(
+ 'There is no user by the name "Nonexistent user". Check your spelling.',
+ [ 'user' => 'Nonexistent user' ]
+ );
+ }
+
+ public function testWebToken() {
+ $sysop = $this->getTestSysop()->getUser();
+ $user = $this->getMutableTestUser()->getUser();
+
+ $token = $sysop->getEditToken( $user->getName() );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'userrights',
+ 'user' => $user->getName(),
+ 'add' => 'sysop',
+ 'token' => $token,
+ ] );
+
+ $user->clearInstanceCache();
+ $this->assertSame( [ 'sysop' ], $user->getGroups() );
+
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ /**
+ * Helper for testCanProcessExpiries that returns a mock ApiUserrights that either can or cannot
+ * process expiries. Although the regular page can process expiries, we use a mock here to
+ * ensure that it's the result of canProcessExpiries() that makes a difference, and not some
+ * error in the way we construct the mock.
+ *
+ * @param bool $canProcessExpiries
+ */
+ private function getMockForProcessingExpiries( $canProcessExpiries ) {
+ $sysop = $this->getTestSysop()->getUser();
+ $user = $this->getMutableTestUser()->getUser();
+
+ $token = $sysop->getEditToken( 'userrights' );
+
+ $main = new ApiMain( new FauxRequest( [
+ 'action' => 'userrights',
+ 'user' => $user->getName(),
+ 'add' => 'sysop',
+ 'token' => $token,
+ ] ) );
+
+ $mockUserRightsPage = $this->getMockBuilder( UserrightsPage::class )
+ ->setMethods( [ 'canProcessExpiries' ] )
+ ->getMock();
+ $mockUserRightsPage->method( 'canProcessExpiries' )->willReturn( $canProcessExpiries );
+
+ $mockApi = $this->getMockBuilder( ApiUserrights::class )
+ ->setConstructorArgs( [ $main, 'userrights' ] )
+ ->setMethods( [ 'getUserRightsPage' ] )
+ ->getMock();
+ $mockApi->method( 'getUserRightsPage' )->willReturn( $mockUserRightsPage );
+
+ return $mockApi;
+ }
+
+ public function testCanProcessExpiries() {
+ $mock1 = $this->getMockForProcessingExpiries( true );
+ $this->assertArrayHasKey( 'expiry', $mock1->getAllowedParams() );
+
+ $mock2 = $this->getMockForProcessingExpiries( false );
+ $this->assertArrayNotHasKey( 'expiry', $mock2->getAllowedParams() );
+ }
+
+ /**
+ * Tests adding and removing various groups with various permissions.
+ *
+ * @dataProvider addAndRemoveGroupsProvider
+ * @param array|null $permissions [ [ $wgAddGroups, $wgRemoveGroups ] ] or null for 'userrights'
+ * to be set in $wgGroupPermissions
+ * @param array $groupsToChange [ [ groups to add ], [ groups to remove ] ]
+ * @param array $expectedGroups Array of expected groups
+ */
+ public function testAddAndRemoveGroups(
+ array $permissions = null, array $groupsToChange, array $expectedGroups
+ ) {
+ if ( $permissions !== null ) {
+ $this->setPermissions( $permissions[0], $permissions[1] );
+ }
+
+ $params = [
+ 'add' => implode( '|', $groupsToChange[0] ),
+ 'remove' => implode( '|', $groupsToChange[1] ),
+ ];
+
+ // We'll take a bot so we have a group to remove
+ $user = $this->getMutableTestUser( [ 'bot' ] )->getUser();
+
+ $this->doSuccessfulRightsChange( $expectedGroups, $params, $user );
+ }
+
+ public function addAndRemoveGroupsProvider() {
+ return [
+ 'Simple add' => [
+ [ [ 'sysop' ], [] ],
+ [ [ 'sysop' ], [] ],
+ [ 'bot', 'sysop' ]
+ ], 'Add with only remove permission' => [
+ [ [], [ 'sysop' ] ],
+ [ [ 'sysop' ], [] ],
+ [ 'bot' ],
+ ], 'Add with global remove permission' => [
+ [ [], true ],
+ [ [ 'sysop' ], [] ],
+ [ 'bot' ],
+ ], 'Simple remove' => [
+ [ [], [ 'bot' ] ],
+ [ [], [ 'bot' ] ],
+ [],
+ ], 'Remove with only add permission' => [
+ [ [ 'bot' ], [] ],
+ [ [], [ 'bot' ] ],
+ [ 'bot' ],
+ ], 'Remove with global add permission' => [
+ [ true, [] ],
+ [ [], [ 'bot' ] ],
+ [ 'bot' ],
+ ], 'Add and remove same new group' => [
+ null,
+ [ [ 'sysop' ], [ 'sysop' ] ],
+ // The userrights code does removals before adds, so it doesn't remove the sysop
+ // group here and only adds it.
+ [ 'bot', 'sysop' ],
+ ], 'Add and remove same existing group' => [
+ null,
+ [ [ 'bot' ], [ 'bot' ] ],
+ // But here it first removes the existing group and then re-adds it.
+ [ 'bot' ],
+ ],
+ ];
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/api/ApiWatchTest.php b/www/wiki/tests/phpunit/includes/api/ApiWatchTest.php
index 7b91094b..6d64a178 100644
--- a/www/wiki/tests/phpunit/includes/api/ApiWatchTest.php
+++ b/www/wiki/tests/phpunit/includes/api/ApiWatchTest.php
@@ -5,13 +5,10 @@
* @group Database
* @group medium
* @todo This test suite is severly broken and need a full review
+ *
+ * @covers ApiWatch
*/
class ApiWatchTest extends ApiTestCase {
- protected function setUp() {
- parent::setUp();
- $this->doLogin();
- }
-
function getTokens() {
return $this->getTokenList( self::$users['sysop'] );
}
diff --git a/www/wiki/tests/phpunit/includes/api/format/ApiFormatBaseTest.php b/www/wiki/tests/phpunit/includes/api/format/ApiFormatBaseTest.php
new file mode 100644
index 00000000..55f760f6
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/api/format/ApiFormatBaseTest.php
@@ -0,0 +1,388 @@
+<?php
+
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @group API
+ * @covers ApiFormatBase
+ */
+class ApiFormatBaseTest extends ApiFormatTestBase {
+
+ protected $printerName = 'mockbase';
+
+ public function getMockFormatter( ApiMain $main = null, $format, $methods = [] ) {
+ if ( $main === null ) {
+ $context = new RequestContext;
+ $context->setRequest( new FauxRequest( [], true ) );
+ $main = new ApiMain( $context );
+ }
+
+ $mock = $this->getMockBuilder( ApiFormatBase::class )
+ ->setConstructorArgs( [ $main, $format ] )
+ ->setMethods( array_unique( array_merge( $methods, [ 'getMimeType', 'execute' ] ) ) )
+ ->getMock();
+ if ( !in_array( 'getMimeType', $methods, true ) ) {
+ $mock->method( 'getMimeType' )->willReturn( 'text/x-mock' );
+ }
+ return $mock;
+ }
+
+ protected function encodeData( array $params, array $data, $options = [] ) {
+ $options += [
+ 'name' => 'mock',
+ 'class' => ApiFormatBase::class,
+ 'factory' => function ( ApiMain $main, $format ) use ( $options ) {
+ $mock = $this->getMockFormatter( $main, $format );
+ $mock->expects( $this->once() )->method( 'execute' )
+ ->willReturnCallback( function () use ( $mock ) {
+ $mock->printText( "Format {$mock->getFormat()}: " );
+ $mock->printText( "<b>ok</b>" );
+ } );
+
+ if ( isset( $options['status'] ) ) {
+ $mock->setHttpStatus( $options['status'] );
+ }
+
+ return $mock;
+ },
+ 'returnPrinter' => true,
+ ];
+
+ $this->setMwGlobals( [
+ 'wgApiFrameOptions' => 'DENY',
+ ] );
+
+ $ret = parent::encodeData( $params, $data, $options );
+ $printer = TestingAccessWrapper::newFromObject( $ret['printer'] );
+ $text = $ret['text'];
+
+ if ( $options['name'] !== 'mockfm' ) {
+ $ct = 'text/x-mock';
+ $file = 'api-result.mock';
+ $status = isset( $options['status'] ) ? $options['status'] : null;
+ } elseif ( isset( $params['wrappedhtml'] ) ) {
+ $ct = 'text/mediawiki-api-prettyprint-wrapped';
+ $file = 'api-result-wrapped.json';
+ $status = null;
+
+ // Replace varying field
+ $text = preg_replace( '/"time":\d+/', '"time":1234', $text );
+ } else {
+ $ct = 'text/html';
+ $file = 'api-result.html';
+ $status = null;
+
+ // Strip OutputPage-generated HTML
+ if ( preg_match( '!<pre class="api-pretty-content">.*</pre>!s', $text, $m ) ) {
+ $text = $m[0];
+ }
+ }
+
+ $response = $printer->getMain()->getRequest()->response();
+ $this->assertSame( "$ct; charset=utf-8", strtolower( $response->getHeader( 'Content-Type' ) ) );
+ $this->assertSame( 'DENY', $response->getHeader( 'X-Frame-Options' ) );
+ $this->assertSame( $file, $printer->getFilename() );
+ $this->assertSame( "inline; filename=$file", $response->getHeader( 'Content-Disposition' ) );
+ $this->assertSame( $status, $response->getStatusCode() );
+
+ return $text;
+ }
+
+ public static function provideGeneralEncoding() {
+ return [
+ 'normal' => [
+ [],
+ "Format MOCK: <b>ok</b>",
+ [],
+ [ 'name' => 'mock' ]
+ ],
+ 'normal ignores wrappedhtml' => [
+ [],
+ "Format MOCK: <b>ok</b>",
+ [ 'wrappedhtml' => 1 ],
+ [ 'name' => 'mock' ]
+ ],
+ 'HTML format' => [
+ [],
+ '<pre class="api-pretty-content">Format MOCK: &lt;b>ok&lt;/b></pre>',
+ [],
+ [ 'name' => 'mockfm' ]
+ ],
+ 'wrapped HTML format' => [
+ [],
+ // phpcs:ignore Generic.Files.LineLength.TooLong
+ '{"status":200,"statustext":"OK","html":"<pre class=\"api-pretty-content\">Format MOCK: &lt;b>ok&lt;/b></pre>","modules":["mediawiki.apipretty"],"continue":null,"time":1234}',
+ [ 'wrappedhtml' => 1 ],
+ [ 'name' => 'mockfm' ]
+ ],
+ 'normal, with set status' => [
+ [],
+ "Format MOCK: <b>ok</b>",
+ [],
+ [ 'name' => 'mock', 'status' => 400 ]
+ ],
+ 'HTML format, with set status' => [
+ [],
+ '<pre class="api-pretty-content">Format MOCK: &lt;b>ok&lt;/b></pre>',
+ [],
+ [ 'name' => 'mockfm', 'status' => 400 ]
+ ],
+ 'wrapped HTML format, with set status' => [
+ [],
+ // phpcs:ignore Generic.Files.LineLength.TooLong
+ '{"status":400,"statustext":"Bad Request","html":"<pre class=\"api-pretty-content\">Format MOCK: &lt;b>ok&lt;/b></pre>","modules":["mediawiki.apipretty"],"continue":null,"time":1234}',
+ [ 'wrappedhtml' => 1 ],
+ [ 'name' => 'mockfm', 'status' => 400 ]
+ ],
+ 'wrapped HTML format, cross-domain-policy' => [
+ [ 'continue' => '< CrOsS-DoMaIn-PoLiCy >' ],
+ // phpcs:ignore Generic.Files.LineLength.TooLong
+ '{"status":200,"statustext":"OK","html":"<pre class=\"api-pretty-content\">Format MOCK: &lt;b>ok&lt;/b></pre>","modules":["mediawiki.apipretty"],"continue":"\u003C CrOsS-DoMaIn-PoLiCy \u003E","time":1234}',
+ [ 'wrappedhtml' => 1 ],
+ [ 'name' => 'mockfm' ]
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideFilenameEncoding
+ */
+ public function testFilenameEncoding( $filename, $expect ) {
+ $ret = parent::encodeData( [], [], [
+ 'name' => 'mock',
+ 'class' => ApiFormatBase::class,
+ 'factory' => function ( ApiMain $main, $format ) use ( $filename ) {
+ $mock = $this->getMockFormatter( $main, $format, [ 'getFilename' ] );
+ $mock->method( 'getFilename' )->willReturn( $filename );
+ return $mock;
+ },
+ 'returnPrinter' => true,
+ ] );
+ $response = $ret['printer']->getMain()->getRequest()->response();
+
+ $this->assertSame( "inline; $expect", $response->getHeader( 'Content-Disposition' ) );
+ }
+
+ public static function provideFilenameEncoding() {
+ return [
+ 'something simple' => [
+ 'foo.xyz', 'filename=foo.xyz'
+ ],
+ 'more complicated, but still simple' => [
+ 'foo.!#$%&\'*+-^_`|~', 'filename=foo.!#$%&\'*+-^_`|~'
+ ],
+ 'Needs quoting' => [
+ 'foo\\bar.xyz', 'filename="foo\\\\bar.xyz"'
+ ],
+ 'Needs quoting (2)' => [
+ 'foo (bar).xyz', 'filename="foo (bar).xyz"'
+ ],
+ 'Needs quoting (3)' => [
+ "foo\t\"b\x5car\"\0.xyz", "filename=\"foo\x5c\t\x5c\"b\x5c\x5car\x5c\"\x5c\0.xyz\""
+ ],
+ 'Non-ASCII characters' => [
+ 'fóo bár.🙌!',
+ "filename=\"f\xF3o b\xE1r.?!\"; filename*=UTF-8''f%C3%B3o%20b%C3%A1r.%F0%9F%99%8C!"
+ ]
+ ];
+ }
+
+ public function testBasics() {
+ $printer = $this->getMockFormatter( null, 'mock' );
+ $this->assertTrue( $printer->canPrintErrors() );
+ $this->assertSame(
+ 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Data_formats',
+ $printer->getHelpUrls()
+ );
+ }
+
+ public function testDisable() {
+ $this->setMwGlobals( [
+ 'wgApiFrameOptions' => 'DENY',
+ ] );
+
+ $printer = $this->getMockFormatter( null, 'mock' );
+ $printer->method( 'execute' )->willReturnCallback( function () use ( $printer ) {
+ $printer->printText( 'Foo' );
+ } );
+ $this->assertFalse( $printer->isDisabled() );
+ $printer->disable();
+ $this->assertTrue( $printer->isDisabled() );
+
+ $printer->setHttpStatus( 400 );
+ $printer->initPrinter();
+ $printer->execute();
+ ob_start();
+ $printer->closePrinter();
+ $this->assertSame( '', ob_get_clean() );
+ $response = $printer->getMain()->getRequest()->response();
+ $this->assertNull( $response->getHeader( 'Content-Type' ) );
+ $this->assertNull( $response->getHeader( 'X-Frame-Options' ) );
+ $this->assertNull( $response->getHeader( 'Content-Disposition' ) );
+ $this->assertNull( $response->getStatusCode() );
+ }
+
+ public function testNullMimeType() {
+ $this->setMwGlobals( [
+ 'wgApiFrameOptions' => 'DENY',
+ ] );
+
+ $printer = $this->getMockFormatter( null, 'mock', [ 'getMimeType' ] );
+ $printer->method( 'execute' )->willReturnCallback( function () use ( $printer ) {
+ $printer->printText( 'Foo' );
+ } );
+ $printer->method( 'getMimeType' )->willReturn( null );
+ $this->assertNull( $printer->getMimeType(), 'sanity check' );
+
+ $printer->initPrinter();
+ $printer->execute();
+ ob_start();
+ $printer->closePrinter();
+ $this->assertSame( 'Foo', ob_get_clean() );
+ $response = $printer->getMain()->getRequest()->response();
+ $this->assertNull( $response->getHeader( 'Content-Type' ) );
+ $this->assertNull( $response->getHeader( 'X-Frame-Options' ) );
+ $this->assertNull( $response->getHeader( 'Content-Disposition' ) );
+
+ $printer = $this->getMockFormatter( null, 'mockfm', [ 'getMimeType' ] );
+ $printer->method( 'execute' )->willReturnCallback( function () use ( $printer ) {
+ $printer->printText( 'Foo' );
+ } );
+ $printer->method( 'getMimeType' )->willReturn( null );
+ $this->assertNull( $printer->getMimeType(), 'sanity check' );
+ $this->assertTrue( $printer->getIsHtml(), 'sanity check' );
+
+ $printer->initPrinter();
+ $printer->execute();
+ ob_start();
+ $printer->closePrinter();
+ $this->assertSame( 'Foo', ob_get_clean() );
+ $response = $printer->getMain()->getRequest()->response();
+ $this->assertSame(
+ 'text/html; charset=utf-8', strtolower( $response->getHeader( 'Content-Type' ) )
+ );
+ $this->assertSame( 'DENY', $response->getHeader( 'X-Frame-Options' ) );
+ $this->assertSame(
+ 'inline; filename=api-result.html', $response->getHeader( 'Content-Disposition' )
+ );
+ }
+
+ public function testApiFrameOptions() {
+ $this->setMwGlobals( [ 'wgApiFrameOptions' => 'DENY' ] );
+ $printer = $this->getMockFormatter( null, 'mock' );
+ $printer->initPrinter();
+ $this->assertSame(
+ 'DENY',
+ $printer->getMain()->getRequest()->response()->getHeader( 'X-Frame-Options' )
+ );
+
+ $this->setMwGlobals( [ 'wgApiFrameOptions' => 'SAMEORIGIN' ] );
+ $printer = $this->getMockFormatter( null, 'mock' );
+ $printer->initPrinter();
+ $this->assertSame(
+ 'SAMEORIGIN',
+ $printer->getMain()->getRequest()->response()->getHeader( 'X-Frame-Options' )
+ );
+
+ $this->setMwGlobals( [ 'wgApiFrameOptions' => false ] );
+ $printer = $this->getMockFormatter( null, 'mock' );
+ $printer->initPrinter();
+ $this->assertNull(
+ $printer->getMain()->getRequest()->response()->getHeader( 'X-Frame-Options' )
+ );
+ }
+
+ public function testForceDefaultParams() {
+ $context = new RequestContext;
+ $context->setRequest( new FauxRequest( [ 'foo' => '1', 'bar' => '2', 'baz' => '3' ], true ) );
+ $main = new ApiMain( $context );
+ $allowedParams = [
+ 'foo' => [],
+ 'bar' => [ ApiBase::PARAM_DFLT => 'bar?' ],
+ 'baz' => 'baz!',
+ ];
+
+ $printer = $this->getMockFormatter( $main, 'mock', [ 'getAllowedParams' ] );
+ $printer->method( 'getAllowedParams' )->willReturn( $allowedParams );
+ $this->assertEquals(
+ [ 'foo' => '1', 'bar' => '2', 'baz' => '3' ],
+ $printer->extractRequestParams(),
+ 'sanity check'
+ );
+
+ $printer = $this->getMockFormatter( $main, 'mock', [ 'getAllowedParams' ] );
+ $printer->method( 'getAllowedParams' )->willReturn( $allowedParams );
+ $printer->forceDefaultParams();
+ $this->assertEquals(
+ [ 'foo' => null, 'bar' => 'bar?', 'baz' => 'baz!' ],
+ $printer->extractRequestParams()
+ );
+ }
+
+ public function testGetAllowedParams() {
+ $printer = $this->getMockFormatter( null, 'mock' );
+ $this->assertSame( [], $printer->getAllowedParams() );
+
+ $printer = $this->getMockFormatter( null, 'mockfm' );
+ $this->assertSame( [
+ 'wrappedhtml' => [
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-format-param-wrappedhtml',
+ ]
+ ], $printer->getAllowedParams() );
+ }
+
+ public function testGetExamplesMessages() {
+ $printer = TestingAccessWrapper::newFromObject( $this->getMockFormatter( null, 'mock' ) );
+ $this->assertSame( [
+ 'action=query&meta=siteinfo&siprop=namespaces&format=mock'
+ => [ 'apihelp-format-example-generic', 'MOCK' ]
+ ], $printer->getExamplesMessages() );
+
+ $printer = TestingAccessWrapper::newFromObject( $this->getMockFormatter( null, 'mockfm' ) );
+ $this->assertSame( [
+ 'action=query&meta=siteinfo&siprop=namespaces&format=mockfm'
+ => [ 'apihelp-format-example-generic', 'MOCK' ]
+ ], $printer->getExamplesMessages() );
+ }
+
+ /**
+ * @dataProvider provideHtmlHeader
+ */
+ public function testHtmlHeader( $post, $registerNonHtml, $expect ) {
+ $context = new RequestContext;
+ $request = new FauxRequest( [ 'a' => 1, 'b' => 2 ], $post );
+ $request->setRequestURL( 'http://example.org/wx/api.php' );
+ $context->setRequest( $request );
+ $context->setLanguage( 'qqx' );
+ $main = new ApiMain( $context );
+ $printer = $this->getMockFormatter( $main, 'mockfm' );
+ $mm = $printer->getMain()->getModuleManager();
+ $mm->addModule( 'mockfm', 'format', ApiFormatBase::class, function () {
+ return $mock;
+ } );
+ if ( $registerNonHtml ) {
+ $mm->addModule( 'mock', 'format', ApiFormatBase::class, function () {
+ return $mock;
+ } );
+ }
+
+ $printer->initPrinter();
+ $printer->execute();
+ ob_start();
+ $printer->closePrinter();
+ $text = ob_get_clean();
+ $this->assertContains( $expect, $text );
+ }
+
+ public static function provideHtmlHeader() {
+ return [
+ [ false, false, '(api-format-prettyprint-header-only-html: MOCK)' ],
+ [ true, false, '(api-format-prettyprint-header-only-html: MOCK)' ],
+ // phpcs:ignore Generic.Files.LineLength.TooLong
+ [ false, true, '(api-format-prettyprint-header-hyperlinked: MOCK, mock, <a rel="nofollow" class="external free" href="http://example.org/wx/api.php?a=1&amp;b=2&amp;format=mock">http://example.org/wx/api.php?a=1&amp;b=2&amp;format=mock</a>)' ],
+ [ true, true, '(api-format-prettyprint-header: MOCK, mock)' ],
+ ];
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/api/format/ApiFormatPhpTest.php b/www/wiki/tests/phpunit/includes/api/format/ApiFormatPhpTest.php
index 3aa1db30..66e620e8 100644
--- a/www/wiki/tests/phpunit/includes/api/format/ApiFormatPhpTest.php
+++ b/www/wiki/tests/phpunit/includes/api/format/ApiFormatPhpTest.php
@@ -20,7 +20,7 @@ class ApiFormatPhpTest extends ApiFormatTestBase {
}
public static function provideGeneralEncoding() {
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return array_merge(
self::addFormatVersion( 1, [
// Basic types
@@ -97,7 +97,7 @@ class ApiFormatPhpTest extends ApiFormatTestBase {
'a:1:{s:3:"foo";s:3:"foo";}' ],
] )
);
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
public function testCrossDomainMangling() {
@@ -110,14 +110,8 @@ class ApiFormatPhpTest extends ApiFormatTestBase {
$main = new ApiMain( $context );
$main->getResult()->addValue( null, null, '< Cross-Domain-Policy >' );
- if ( !function_exists( 'wfOutputHandler' ) ) {
- function wfOutputHandler( $s ) {
- return $s;
- }
- }
-
$printer = $main->createPrinterByName( 'php' );
- ob_start( 'wfOutputHandler' );
+ ob_start( 'MediaWiki\\OutputHandler::handle' );
$printer->initPrinter();
$printer->execute();
$printer->closePrinter();
@@ -126,7 +120,7 @@ class ApiFormatPhpTest extends ApiFormatTestBase {
$config->set( 'MangleFlashPolicy', true );
$printer = $main->createPrinterByName( 'php' );
- ob_start( 'wfOutputHandler' );
+ ob_start( 'MediaWiki\\OutputHandler::handle' );
try {
$printer->initPrinter();
$printer->execute();
diff --git a/www/wiki/tests/phpunit/includes/api/format/ApiFormatRawTest.php b/www/wiki/tests/phpunit/includes/api/format/ApiFormatRawTest.php
new file mode 100644
index 00000000..f64af6d3
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/api/format/ApiFormatRawTest.php
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * @group API
+ * @covers ApiFormatRaw
+ */
+class ApiFormatRawTest extends ApiFormatTestBase {
+
+ protected $printerName = 'raw';
+
+ /**
+ * Test basic encoding and missing mime and text exceptions
+ * @return array datasets
+ */
+ public static function provideGeneralEncoding() {
+ $options = [
+ 'class' => ApiFormatRaw::class,
+ 'factory' => function ( ApiMain $main ) {
+ return new ApiFormatRaw( $main, new ApiFormatJson( $main, 'json' ) );
+ }
+ ];
+
+ return [
+ [
+ [ 'mime' => 'text/plain', 'text' => 'foo' ],
+ 'foo',
+ [],
+ $options
+ ],
+ [
+ [ 'mime' => 'text/plain', 'text' => 'fóo' ],
+ 'fóo',
+ [],
+ $options
+ ],
+ [
+ [ 'text' => 'some text' ],
+ new MWException( 'No MIME type set for raw formatter' ),
+ [],
+ $options
+ ],
+ [
+ [ 'mime' => 'text/plain' ],
+ new MWException( 'No text given for raw formatter' ),
+ [],
+ $options
+ ],
+ 'test error fallback' => [
+ [ 'mime' => 'text/plain', 'text' => 'some text', 'error' => 'some error' ],
+ '{"mime":"text/plain","text":"some text","error":"some error"}',
+ [],
+ $options
+ ]
+ ];
+ }
+
+ /**
+ * Test specifying filename
+ */
+ public function testFilename() {
+ $printer = new ApiFormatRaw( new ApiMain );
+ $printer->getResult()->addValue( null, 'filename', 'whatever.raw' );
+ $this->assertSame( 'whatever.raw', $printer->getFilename() );
+ }
+
+ /**
+ * Test specifying filename with error fallback printer
+ */
+ public function testErrorFallbackFilename() {
+ $apiMain = new ApiMain;
+ $printer = new ApiFormatRaw( $apiMain, new ApiFormatJson( $apiMain, 'json' ) );
+ $printer->getResult()->addValue( null, 'error', 'some error' );
+ $printer->getResult()->addValue( null, 'filename', 'whatever.raw' );
+ $this->assertSame( 'api-result.json', $printer->getFilename() );
+ }
+
+ /**
+ * Test specifying mime
+ */
+ public function testMime() {
+ $printer = new ApiFormatRaw( new ApiMain );
+ $printer->getResult()->addValue( null, 'mime', 'text/plain' );
+ $this->assertSame( 'text/plain', $printer->getMimeType() );
+ }
+
+ /**
+ * Test specifying mime with error fallback printer
+ */
+ public function testErrorFallbackMime() {
+ $apiMain = new ApiMain;
+ $printer = new ApiFormatRaw( $apiMain, new ApiFormatJson( $apiMain, 'json' ) );
+ $printer->getResult()->addValue( null, 'error', 'some error' );
+ $printer->getResult()->addValue( null, 'mime', 'text/plain' );
+ $this->assertSame( 'application/json', $printer->getMimeType() );
+ }
+
+ /**
+ * Check that setting failWithHTTPError to true will result in 400 response status code
+ */
+ public function testFailWithHTTPError() {
+ $apiMain = null;
+
+ $this->testGeneralEncoding(
+ [ 'mime' => 'text/plain', 'text' => 'some text', 'error' => 'some error' ],
+ '{"mime":"text/plain","text":"some text","error":"some error"}',
+ [],
+ [
+ 'class' => ApiFormatRaw::class,
+ 'factory' => function ( ApiMain $main ) use ( &$apiMain ) {
+ $apiMain = $main;
+ $printer = new ApiFormatRaw( $main, new ApiFormatJson( $main, 'json' ) );
+ $printer->setFailWithHTTPError( true );
+ return $printer;
+ }
+ ]
+ );
+ $this->assertEquals( 400, $apiMain->getRequest()->response()->getStatusCode() );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/api/format/ApiFormatTestBase.php b/www/wiki/tests/phpunit/includes/api/format/ApiFormatTestBase.php
index fb086e99..4169dab2 100644
--- a/www/wiki/tests/phpunit/includes/api/format/ApiFormatTestBase.php
+++ b/www/wiki/tests/phpunit/includes/api/format/ApiFormatTestBase.php
@@ -11,26 +11,40 @@ abstract class ApiFormatTestBase extends MediaWikiTestCase {
/**
* Return general data to be encoded for testing
* @return array See self::testGeneralEncoding
- * @throws Exception
+ * @throws BadMethodCallException
*/
public static function provideGeneralEncoding() {
- throw new Exception( 'Subclass must implement ' . __METHOD__ );
+ throw new BadMethodCallException( static::class . ' must implement ' . __METHOD__ );
}
/**
* Get the formatter output for the given input data
* @param array $params Query parameters
* @param array $data Data to encode
- * @param string $class Printer class to use instead of the normal one
- * @return string
+ * @param array $options Options. If passed a string, the string is treated
+ * as the 'class' option.
+ * - name: Format name, rather than $this->printerName
+ * - class: If set, register 'name' with this class (and 'factory', if that's set)
+ * - factory: Used with 'class' to register at runtime
+ * - returnPrinter: Return the printer object
+ * @param callable|null $factory Factory to use instead of the normal one
+ * @return string|array The string if $options['returnPrinter'] isn't set, or an array if it is:
+ * - text: Output text string
+ * - printer: ApiFormatBase
* @throws Exception
*/
- protected function encodeData( array $params, array $data, $class = null ) {
+ protected function encodeData( array $params, array $data, $options = [] ) {
+ if ( is_string( $options ) ) {
+ $options = [ 'class' => $options ];
+ }
+ $printerName = isset( $options['name'] ) ? $options['name'] : $this->printerName;
+
$context = new RequestContext;
$context->setRequest( new FauxRequest( $params, true ) );
$main = new ApiMain( $context );
- if ( $class !== null ) {
- $main->getModuleManager()->addModule( $this->printerName, 'format', $class );
+ if ( isset( $options['class'] ) ) {
+ $factory = isset( $options['factory'] ) ? $options['factory'] : null;
+ $main->getModuleManager()->addModule( $printerName, 'format', $options['class'], $factory );
}
$result = $main->getResult();
$result->addArrayType( null, 'default' );
@@ -38,27 +52,42 @@ abstract class ApiFormatTestBase extends MediaWikiTestCase {
$result->addValue( null, $k, $v );
}
- $printer = $main->createPrinterByName( $this->printerName );
+ $ret = [];
+ $printer = $main->createPrinterByName( $printerName );
$printer->initPrinter();
$printer->execute();
ob_start();
try {
$printer->closePrinter();
- return ob_get_clean();
+ $ret['text'] = ob_get_clean();
} catch ( Exception $ex ) {
ob_end_clean();
throw $ex;
}
+
+ if ( !empty( $options['returnPrinter'] ) ) {
+ $ret['printer'] = $printer;
+ }
+
+ return count( $ret ) === 1 ? $ret['text'] : $ret;
}
/**
* @dataProvider provideGeneralEncoding
+ * @param array $data Data to be encoded
+ * @param string|Exception $expect String to expect, or exception expected to be thrown
+ * @param array $params Query parameters to set in the FauxRequest
+ * @param array $options Options to pass to self::encodeData()
*/
- public function testGeneralEncoding( array $data, $expect, array $params = [] ) {
- if ( isset( $params['SKIP'] ) ) {
- $this->markTestSkipped( $expect );
+ public function testGeneralEncoding(
+ array $data, $expect, array $params = [], array $options = []
+ ) {
+ if ( $expect instanceof Exception ) {
+ $this->setExpectedException( get_class( $expect ), $expect->getMessage() );
+ $this->encodeData( $params, $data, $options ); // Should throw
+ } else {
+ $this->assertSame( $expect, $this->encodeData( $params, $data, $options ) );
}
- $this->assertSame( $expect, $this->encodeData( $params, $data ) );
}
}
diff --git a/www/wiki/tests/phpunit/includes/api/format/ApiFormatXmlTest.php b/www/wiki/tests/phpunit/includes/api/format/ApiFormatXmlTest.php
index 0f8c8ee6..915fb5c5 100644
--- a/www/wiki/tests/phpunit/includes/api/format/ApiFormatXmlTest.php
+++ b/www/wiki/tests/phpunit/includes/api/format/ApiFormatXmlTest.php
@@ -12,11 +12,11 @@ class ApiFormatXmlTest extends ApiFormatTestBase {
public static function setUpBeforeClass() {
parent::setUpBeforeClass();
$page = WikiPage::factory( Title::newFromText( 'MediaWiki:ApiFormatXmlTest.xsl' ) );
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
$page->doEditContent( new WikitextContent(
'<?xml version="1.0"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" />'
), 'Summary' );
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
$page = WikiPage::factory( Title::newFromText( 'MediaWiki:ApiFormatXmlTest' ) );
$page->doEditContent( new WikitextContent( 'Bogus' ), 'Summary' );
$page = WikiPage::factory( Title::newFromText( 'ApiFormatXmlTest' ) );
@@ -24,7 +24,7 @@ class ApiFormatXmlTest extends ApiFormatTestBase {
}
public static function provideGeneralEncoding() {
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return [
// Basic types
[ [ null, 'a' => null ], '<?xml version="1.0"?><api><_v _idx="0" /></api>' ],
@@ -117,7 +117,7 @@ class ApiFormatXmlTest extends ApiFormatTestBase {
'" type="text/xsl" ?><api />',
[ 'xslt' => 'MediaWiki:ApiFormatXmlTest.xsl' ] ],
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
}
diff --git a/www/wiki/tests/phpunit/includes/api/query/ApiQueryBasicTest.php b/www/wiki/tests/phpunit/includes/api/query/ApiQueryBasicTest.php
index c612f265..e49e1d8b 100644
--- a/www/wiki/tests/phpunit/includes/api/query/ApiQueryBasicTest.php
+++ b/www/wiki/tests/phpunit/includes/api/query/ApiQueryBasicTest.php
@@ -1,8 +1,5 @@
<?php
/**
- *
- * Created on Feb 6, 2013
- *
* Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
*
* This program is free software; you can redistribute it and/or modify
@@ -134,7 +131,7 @@ class ApiQueryBasicTest extends ApiQueryTestBase {
private static $allcategories = [
[ 'list' => 'allcategories', 'acprefix' => 'AQBT-' ],
[ 'allcategories' => [
- [ '*' => 'AQBT-Cat' ],
+ [ 'category' => 'AQBT-Cat' ],
] ]
];
@@ -236,9 +233,7 @@ class ApiQueryBasicTest extends ApiQueryTestBase {
$this->check( self::$allpages );
$this->check( self::$alllinks );
$this->check( self::$alltransclusions );
- // This test is temporarily disabled until a sqlite bug is fixed
- // Confirmed still broken 15-nov-2013
- // $this->check( self::$allcategories );
+ $this->check( self::$allcategories );
$this->check( self::$backlinks );
$this->check( self::$embeddedin );
$this->check( self::$categorymembers );
diff --git a/www/wiki/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php b/www/wiki/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php
index 944d31c8..334fd5da 100644
--- a/www/wiki/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php
+++ b/www/wiki/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php
@@ -46,7 +46,7 @@ class ApiQueryContinue2Test extends ApiQueryContinueTestBase {
}
/**
- * @medium
+ * @group medium
*/
public function testA() {
$this->mVerbose = false;
diff --git a/www/wiki/tests/phpunit/includes/api/query/ApiQueryContinueTest.php b/www/wiki/tests/phpunit/includes/api/query/ApiQueryContinueTest.php
index b31627b4..7259bb81 100644
--- a/www/wiki/tests/phpunit/includes/api/query/ApiQueryContinueTest.php
+++ b/www/wiki/tests/phpunit/includes/api/query/ApiQueryContinueTest.php
@@ -56,7 +56,7 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase {
/**
* Test smart continue - list=allpages
- * @medium
+ * @group medium
*/
public function test1List() {
$this->mVerbose = false;
@@ -80,7 +80,7 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase {
/**
* Test smart continue - list=allpages|alltransclusions
- * @medium
+ * @group medium
*/
public function test2Lists() {
$this->mVerbose = false;
@@ -106,7 +106,7 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase {
/**
* Test smart continue - generator=allpages, prop=links
- * @medium
+ * @group medium
*/
public function testGen1Prop() {
$this->mVerbose = false;
@@ -131,7 +131,7 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase {
/**
* Test smart continue - generator=allpages, prop=links|templates
- * @medium
+ * @group medium
*/
public function testGen2Prop() {
$this->mVerbose = false;
@@ -162,7 +162,7 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase {
/**
* Test smart continue - generator=allpages, prop=links, list=alltransclusions
- * @medium
+ * @group medium
*/
public function testGen1Prop1List() {
$this->mVerbose = false;
@@ -194,7 +194,7 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase {
/**
* Test smart continue - generator=allpages, prop=links|templates,
* list=alllinks|alltransclusions, meta=siteinfo
- * @medium
+ * @group medium
*/
public function testGen2Prop2List1Meta() {
$this->mVerbose = false;
@@ -233,7 +233,7 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase {
/**
* Test smart continue - generator=templates, prop=templates
- * @medium
+ * @group medium
*/
public function testSameGenAndProp() {
$this->mVerbose = false;
@@ -279,7 +279,7 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase {
/**
* Test smart continue - generator=allpages, list=allpages
- * @medium
+ * @group medium
*/
public function testSameGenList() {
$this->mVerbose = false;
diff --git a/www/wiki/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php b/www/wiki/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php
index 704c4172..d2bdb496 100644
--- a/www/wiki/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php
+++ b/www/wiki/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php
@@ -1,7 +1,5 @@
<?php
/**
- * Created on Jan 1, 2013
- *
* Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
*
* This program is free software; you can redistribute it and/or modify
@@ -159,7 +157,7 @@ abstract class ApiQueryContinueTestBase extends ApiQueryTestBase {
/**
* Recursively merge the new result returned from the query to the previous results.
- * @param mixed $results
+ * @param mixed &$results
* @param mixed $newResult
* @param bool $numericIds If true, treat keys as ids to be merged instead of appending
*/
diff --git a/www/wiki/tests/phpunit/includes/api/query/ApiQueryTest.php b/www/wiki/tests/phpunit/includes/api/query/ApiQueryTest.php
index 8026e544..de8d8156 100644
--- a/www/wiki/tests/phpunit/includes/api/query/ApiQueryTest.php
+++ b/www/wiki/tests/phpunit/includes/api/query/ApiQueryTest.php
@@ -9,7 +9,6 @@
class ApiQueryTest extends ApiTestCase {
protected function setUp() {
parent::setUp();
- $this->doLogin();
// Setup apiquerytestiw: as interwiki prefix
$this->setMwGlobals( 'wgHooks', [
@@ -81,6 +80,19 @@ class ApiQueryTest extends ApiTestCase {
$this->assertArrayHasKey( 'invalid', $data[0]['query']['pages'][-1] );
}
+ public function testTitlesWithWhitespaces() {
+ $data = $this->doApiRequest( [
+ 'action' => 'query',
+ 'titles' => ' '
+ ] );
+
+ $this->assertArrayHasKey( 'query', $data[0] );
+ $this->assertArrayHasKey( 'pages', $data[0]['query'] );
+ $this->assertEquals( 1, count( $data[0]['query']['pages'] ) );
+ $this->assertArrayHasKey( -1, $data[0]['query']['pages'] );
+ $this->assertArrayHasKey( 'invalid', $data[0]['query']['pages'][-1] );
+ }
+
/**
* Test the ApiBase::titlePartToKey function
*
diff --git a/www/wiki/tests/phpunit/includes/api/query/ApiQueryTestBase.php b/www/wiki/tests/phpunit/includes/api/query/ApiQueryTestBase.php
index f3d7cb6e..e7588cb5 100644
--- a/www/wiki/tests/phpunit/includes/api/query/ApiQueryTestBase.php
+++ b/www/wiki/tests/phpunit/includes/api/query/ApiQueryTestBase.php
@@ -1,7 +1,5 @@
<?php
/**
- * Created on Feb 10, 2013
- *
* Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
*
* This program is free software; you can redistribute it and/or modify
diff --git a/www/wiki/tests/phpunit/includes/api/query/ApiQueryUserContributionsTest.php b/www/wiki/tests/phpunit/includes/api/query/ApiQueryUserContributionsTest.php
new file mode 100644
index 00000000..ca6a929a
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/api/query/ApiQueryUserContributionsTest.php
@@ -0,0 +1,194 @@
+<?php
+
+/**
+ * @group API
+ * @group Database
+ * @group medium
+ * @covers ApiQueryContributions
+ */
+class ApiQueryContributionsTest extends ApiTestCase {
+ public function addDBDataOnce() {
+ global $wgActorTableSchemaMigrationStage;
+
+ $reset = new \Wikimedia\ScopedCallback( function ( $v ) {
+ global $wgActorTableSchemaMigrationStage;
+ $wgActorTableSchemaMigrationStage = $v;
+ $this->overrideMwServices();
+ }, [ $wgActorTableSchemaMigrationStage ] );
+ $wgActorTableSchemaMigrationStage = MIGRATION_WRITE_BOTH;
+ $this->overrideMwServices();
+
+ $users = [
+ User::newFromName( '192.168.2.2', false ),
+ User::newFromName( '192.168.2.1', false ),
+ User::newFromName( '192.168.2.3', false ),
+ User::createNew( __CLASS__ . ' B' ),
+ User::createNew( __CLASS__ . ' A' ),
+ User::createNew( __CLASS__ . ' C' ),
+ User::newFromName( 'IW>' . __CLASS__, false ),
+ ];
+
+ $title = Title::newFromText( __CLASS__ );
+ $page = WikiPage::factory( $title );
+ for ( $i = 0; $i < 3; $i++ ) {
+ foreach ( array_reverse( $users ) as $user ) {
+ $status = $page->doEditContent(
+ ContentHandler::makeContent( "Test revision $user #$i", $title ), 'Test edit', 0, false, $user
+ );
+ if ( !$status->isOK() ) {
+ $this->fail( "Failed to edit $title: " . $status->getWikiText( false, false, 'en' ) );
+ }
+ }
+ }
+ }
+
+ /**
+ * @dataProvider provideSorting
+ * @param int $stage One of the MIGRATION_* constants for $wgActorTableSchemaMigrationStage
+ * @param array $params Extra parameters for the query
+ * @param bool $reverse Reverse order?
+ * @param int $revs Number of revisions to expect
+ */
+ public function testSorting( $stage, $params, $reverse, $revs ) {
+ if ( isset( $params['ucuserprefix'] ) &&
+ ( $stage === MIGRATION_WRITE_BOTH || $stage === MIGRATION_WRITE_NEW ) &&
+ $this->db->getType() === 'mysql' && $this->usesTemporaryTables()
+ ) {
+ // https://bugs.mysql.com/bug.php?id=10327
+ $this->markTestSkipped( 'MySQL bug 10327 - can\'t reopen temporary tables' );
+ }
+
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', $stage );
+ $this->overrideMwServices();
+
+ if ( isset( $params['ucuserids'] ) ) {
+ $params['ucuserids'] = implode( '|', array_map( 'User::idFromName', $params['ucuserids'] ) );
+ }
+ if ( isset( $params['ucuser'] ) ) {
+ $params['ucuser'] = implode( '|', $params['ucuser'] );
+ }
+
+ $sort = 'rsort';
+ if ( $reverse ) {
+ $params['ucdir'] = 'newer';
+ $sort = 'sort';
+ }
+
+ $params += [
+ 'action' => 'query',
+ 'list' => 'usercontribs',
+ 'ucprop' => 'ids',
+ ];
+
+ $apiResult = $this->doApiRequest( $params + [ 'uclimit' => 500 ] );
+ $this->assertArrayNotHasKey( 'continue', $apiResult[0] );
+ $this->assertArrayHasKey( 'query', $apiResult[0] );
+ $this->assertArrayHasKey( 'usercontribs', $apiResult[0]['query'] );
+
+ $count = 0;
+ $ids = [];
+ foreach ( $apiResult[0]['query']['usercontribs'] as $page ) {
+ $count++;
+ $ids[$page['user']][] = $page['revid'];
+ }
+ $this->assertSame( $revs, $count, 'Expected number of revisions' );
+ foreach ( $ids as $user => $revids ) {
+ $sorted = $revids;
+ call_user_func_array( $sort, [ &$sorted ] );
+ $this->assertSame( $sorted, $revids, "IDs for $user are sorted" );
+ }
+
+ for ( $limit = 1; $limit < $revs; $limit++ ) {
+ $continue = [];
+ $count = 0;
+ $batchedIds = [];
+ while ( $continue !== null ) {
+ $apiResult = $this->doApiRequest( $params + [ 'uclimit' => $limit ] + $continue );
+ $this->assertArrayHasKey( 'query', $apiResult[0], "Batching with limit $limit" );
+ $this->assertArrayHasKey( 'usercontribs', $apiResult[0]['query'],
+ "Batching with limit $limit" );
+ $continue = isset( $apiResult[0]['continue'] ) ? $apiResult[0]['continue'] : null;
+ foreach ( $apiResult[0]['query']['usercontribs'] as $page ) {
+ $count++;
+ $batchedIds[$page['user']][] = $page['revid'];
+ }
+ $this->assertLessThanOrEqual( $revs, $count, "Batching with limit $limit" );
+ }
+ $this->assertSame( $ids, $batchedIds, "Result set is the same when batching with limit $limit" );
+ }
+ }
+
+ public static function provideSorting() {
+ $users = [ __CLASS__ . ' A', __CLASS__ . ' B', __CLASS__ . ' C' ];
+ $users2 = [ __CLASS__ . ' A', __CLASS__ . ' B', __CLASS__ . ' D' ];
+ $ips = [ '192.168.2.1', '192.168.2.2', '192.168.2.3', '192.168.2.4' ];
+
+ foreach (
+ [
+ 'old' => MIGRATION_OLD,
+ 'write both' => MIGRATION_WRITE_BOTH,
+ 'write new' => MIGRATION_WRITE_NEW,
+ 'new' => MIGRATION_NEW,
+ ] as $stageName => $stage
+ ) {
+ foreach ( [ false, true ] as $reverse ) {
+ $name = $stageName . ( $reverse ? ', reverse' : '' );
+ yield "Named users, $name" => [ $stage, [ 'ucuser' => $users ], $reverse, 9 ];
+ yield "Named users including a no-edit user, $name" => [
+ $stage, [ 'ucuser' => $users2 ], $reverse, 6
+ ];
+ yield "IP users, $name" => [ $stage, [ 'ucuser' => $ips ], $reverse, 9 ];
+ yield "All users, $name" => [
+ $stage, [ 'ucuser' => array_merge( $users, $ips ) ], $reverse, 18
+ ];
+ yield "User IDs, $name" => [ $stage, [ 'ucuserids' => $users ], $reverse, 9 ];
+ yield "Users by prefix, $name" => [ $stage, [ 'ucuserprefix' => __CLASS__ ], $reverse, 9 ];
+ yield "IPs by prefix, $name" => [ $stage, [ 'ucuserprefix' => '192.168.2.' ], $reverse, 9 ];
+ }
+ }
+ }
+
+ /**
+ * @dataProvider provideInterwikiUser
+ * @param int $stage One of the MIGRATION_* constants for $wgActorTableSchemaMigrationStage
+ */
+ public function testInterwikiUser( $stage ) {
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', $stage );
+ $this->overrideMwServices();
+
+ $params = [
+ 'action' => 'query',
+ 'list' => 'usercontribs',
+ 'ucuser' => 'IW>' . __CLASS__,
+ 'ucprop' => 'ids',
+ 'uclimit' => 'max',
+ ];
+
+ $apiResult = $this->doApiRequest( $params );
+ $this->assertArrayNotHasKey( 'continue', $apiResult[0] );
+ $this->assertArrayHasKey( 'query', $apiResult[0] );
+ $this->assertArrayHasKey( 'usercontribs', $apiResult[0]['query'] );
+
+ $count = 0;
+ $ids = [];
+ foreach ( $apiResult[0]['query']['usercontribs'] as $page ) {
+ $count++;
+ $this->assertSame( 'IW>' . __CLASS__, $page['user'], 'Correct user returned' );
+ $ids[] = $page['revid'];
+ }
+ $this->assertSame( 3, $count, 'Expected number of revisions' );
+ $sorted = $ids;
+ rsort( $sorted );
+ $this->assertSame( $sorted, $ids, "IDs are sorted" );
+ }
+
+ public static function provideInterwikiUser() {
+ return [
+ 'old' => [ MIGRATION_OLD ],
+ 'write both' => [ MIGRATION_WRITE_BOTH ],
+ 'write new' => [ MIGRATION_WRITE_NEW ],
+ 'new' => [ MIGRATION_NEW ],
+ ];
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/auth/AbstractAuthenticationProviderTest.php b/www/wiki/tests/phpunit/includes/auth/AbstractAuthenticationProviderTest.php
index a3b0df52..b271b701 100644
--- a/www/wiki/tests/phpunit/includes/auth/AbstractAuthenticationProviderTest.php
+++ b/www/wiki/tests/phpunit/includes/auth/AbstractAuthenticationProviderTest.php
@@ -13,7 +13,7 @@ class AbstractAuthenticationProviderTest extends \MediaWikiTestCase {
$provider = $this->getMockForAbstractClass( AbstractAuthenticationProvider::class );
$providerPriv = TestingAccessWrapper::newFromObject( $provider );
- $obj = $this->getMockForAbstractClass( 'Psr\Log\LoggerInterface' );
+ $obj = $this->getMockForAbstractClass( \Psr\Log\LoggerInterface::class );
$provider->setLogger( $obj );
$this->assertSame( $obj, $providerPriv->logger, 'setLogger' );
@@ -21,7 +21,7 @@ class AbstractAuthenticationProviderTest extends \MediaWikiTestCase {
$provider->setManager( $obj );
$this->assertSame( $obj, $providerPriv->manager, 'setManager' );
- $obj = $this->getMockForAbstractClass( 'Config' );
+ $obj = $this->getMockForAbstractClass( \Config::class );
$provider->setConfig( $obj );
$this->assertSame( $obj, $providerPriv->config, 'setConfig' );
diff --git a/www/wiki/tests/phpunit/includes/auth/AbstractPasswordPrimaryAuthenticationProviderTest.php b/www/wiki/tests/phpunit/includes/auth/AbstractPasswordPrimaryAuthenticationProviderTest.php
index 76d8ee93..cb015df6 100644
--- a/www/wiki/tests/phpunit/includes/auth/AbstractPasswordPrimaryAuthenticationProviderTest.php
+++ b/www/wiki/tests/phpunit/includes/auth/AbstractPasswordPrimaryAuthenticationProviderTest.php
@@ -33,7 +33,7 @@ class AbstractPasswordPrimaryAuthenticationProviderTest extends \MediaWikiTestCa
$providerPriv = TestingAccessWrapper::newFromObject( $provider );
$obj = $providerPriv->getPasswordFactory();
- $this->assertInstanceOf( 'PasswordFactory', $obj );
+ $this->assertInstanceOf( \PasswordFactory::class, $obj );
$this->assertSame( $obj, $providerPriv->getPasswordFactory() );
}
@@ -46,10 +46,10 @@ class AbstractPasswordPrimaryAuthenticationProviderTest extends \MediaWikiTestCa
$providerPriv = TestingAccessWrapper::newFromObject( $provider );
$obj = $providerPriv->getPassword( null );
- $this->assertInstanceOf( 'Password', $obj );
+ $this->assertInstanceOf( \Password::class, $obj );
$obj = $providerPriv->getPassword( 'invalid' );
- $this->assertInstanceOf( 'Password', $obj );
+ $this->assertInstanceOf( \Password::class, $obj );
}
public function testGetNewPasswordExpiry() {
diff --git a/www/wiki/tests/phpunit/includes/auth/AuthManagerTest.php b/www/wiki/tests/phpunit/includes/auth/AuthManagerTest.php
index c18af8b3..cc162487 100644
--- a/www/wiki/tests/phpunit/includes/auth/AuthManagerTest.php
+++ b/www/wiki/tests/phpunit/includes/auth/AuthManagerTest.php
@@ -2,10 +2,13 @@
namespace MediaWiki\Auth;
+use Config;
use MediaWiki\Session\SessionInfo;
use MediaWiki\Session\UserInfo;
+use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use StatusValue;
+use WebRequest;
use Wikimedia\ScopedCallback;
use Wikimedia\TestingAccessWrapper;
@@ -19,7 +22,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
protected $request;
/** @var Config */
protected $config;
- /** @var \\Psr\\Log\\LoggerInterface */
+ /** @var LoggerInterface */
protected $logger;
protected $preauthMocks = [];
@@ -149,7 +152,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
if ( $canChangeUser !== null ) {
$methods[] = 'canChangeUser';
}
- $provider = $this->getMockBuilder( 'DummySessionProvider' )
+ $provider = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods( $methods )
->getMock();
$provider->expects( $this->any() )->method( '__toString' )
@@ -876,14 +879,10 @@ class AuthManagerTest extends \MediaWikiTestCase {
);
$mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key ) );
- $mocks[$key . '2'] = $this->getMockForAbstractClass(
- "MediaWiki\\Auth\\$class", [], "Mock$class"
- );
+ $mocks[$key . '2'] = $this->getMockForAbstractClass( "MediaWiki\\Auth\\$class" );
$mocks[$key . '2']->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key . '2' ) );
- $mocks[$key . '3'] = $this->getMockForAbstractClass(
- "MediaWiki\\Auth\\$class", [], "Mock$class"
- );
+ $mocks[$key . '3'] = $this->getMockForAbstractClass( "MediaWiki\\Auth\\$class" );
$mocks[$key . '3']->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key . '3' ) );
}
@@ -968,7 +967,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
$p->expects( $this->atMost( 1 ) )->method( 'postAuthentication' )
->willReturnCallback( function ( $user, $response ) use ( $constraint, $p ) {
if ( $user !== null ) {
- $this->assertInstanceOf( 'User', $user );
+ $this->assertInstanceOf( \User::class, $user );
$this->assertSame( 'UTSysop', $user->getName() );
}
$this->assertInstanceOf( AuthenticationResponse::class, $response );
@@ -1433,6 +1432,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
$blockOptions = [
'address' => 'UTBlockee',
'user' => $user->getID(),
+ 'by' => $this->getTestSysop()->getUser()->getId(),
'reason' => __METHOD__,
'expiry' => time() + 100500,
'createAccount' => true,
@@ -1445,6 +1445,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
$blockOptions = [
'address' => '127.0.0.0/24',
+ 'by' => $this->getTestSysop()->getUser()->getId(),
'reason' => __METHOD__,
'expiry' => time() + 100500,
'createAccount' => true,
@@ -1896,9 +1897,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
) );
for ( $i = 2; $i <= 3; $i++ ) {
- $mocks[$key . $i] = $this->getMockForAbstractClass(
- "MediaWiki\\Auth\\$class", [], "Mock$class"
- );
+ $mocks[$key . $i] = $this->getMockForAbstractClass( "MediaWiki\\Auth\\$class" );
$mocks[$key . $i]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key . $i ) );
$mocks[$key . $i]->expects( $this->any() )->method( 'testUserForCreation' )
@@ -1998,7 +1997,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
->willReturnCallback( function ( $user, $creator, $response )
use ( $constraint, $p, $username )
{
- $this->assertInstanceOf( 'User', $user );
+ $this->assertInstanceOf( \User::class, $user );
$this->assertSame( $username, $user->getName() );
$this->assertSame( 'UTSysop', $creator->getName() );
$this->assertInstanceOf( AuthenticationResponse::class, $response );
@@ -2262,7 +2261,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
// Set up lots of mocks...
$mock = $this->getMockForAbstractClass(
- "MediaWiki\\Auth\\PrimaryAuthenticationProvider", []
+ \MediaWiki\Auth\PrimaryAuthenticationProvider::class, []
);
$mock->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( 'primary' ) );
@@ -2363,9 +2362,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
$mocks = [];
foreach ( [ 'pre', 'primary', 'secondary' ] as $key ) {
$class = ucfirst( $key ) . 'AuthenticationProvider';
- $mocks[$key] = $this->getMockForAbstractClass(
- "MediaWiki\\Auth\\$class", [], "Mock$class"
- );
+ $mocks[$key] = $this->getMockForAbstractClass( "MediaWiki\\Auth\\$class" );
$mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key ) );
}
@@ -2668,7 +2665,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
// Test addToDatabase fails
$session->clear();
- $user = $this->getMockBuilder( 'User' )
+ $user = $this->getMockBuilder( \User::class )
->setMethods( [ 'addToDatabase' ] )->getMock();
$user->expects( $this->once() )->method( 'addToDatabase' )
->will( $this->returnValue( \Status::newFatal( 'because' ) ) );
@@ -2690,7 +2687,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
$backoffKey = wfMemcKey( 'AuthManager', 'autocreate-failed', md5( $username ) );
$this->assertFalse( $cache->get( $backoffKey ), 'sanity check' );
$session->clear();
- $user = $this->getMockBuilder( 'User' )
+ $user = $this->getMockBuilder( \User::class )
->setMethods( [ 'addToDatabase' ] )->getMock();
$user->expects( $this->once() )->method( 'addToDatabase' )
->will( $this->throwException( new \Exception( 'Excepted' ) ) );
@@ -2714,7 +2711,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
// Test addToDatabase fails because the user already exists.
$session->clear();
- $user = $this->getMockBuilder( 'User' )
+ $user = $this->getMockBuilder( \User::class )
->setMethods( [ 'addToDatabase' ] )->getMock();
$user->expects( $this->once() )->method( 'addToDatabase' )
->will( $this->returnCallback( function () use ( $username, &$user ) {
@@ -2843,9 +2840,11 @@ class AuthManagerTest extends \MediaWikiTestCase {
$mocks = [];
foreach ( [ 'pre', 'primary', 'secondary' ] as $key ) {
$class = ucfirst( $key ) . 'AuthenticationProvider';
- $mocks[$key] = $this->getMockForAbstractClass(
- "MediaWiki\\Auth\\$class", [], "Mock$class"
- );
+ $mocks[$key] = $this->getMockBuilder( "MediaWiki\\Auth\\$class" )
+ ->setMethods( [
+ 'getUniqueId', 'getAuthenticationRequests', 'providerAllowsAuthenticationDataChange',
+ ] )
+ ->getMockForAbstractClass();
$mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key ) );
$mocks[$key]->expects( $this->any() )->method( 'getAuthenticationRequests' )
@@ -2863,9 +2862,12 @@ class AuthManagerTest extends \MediaWikiTestCase {
PrimaryAuthenticationProvider::TYPE_LINK
] as $type ) {
$class = 'PrimaryAuthenticationProvider';
- $mocks["primary-$type"] = $this->getMockForAbstractClass(
- "MediaWiki\\Auth\\$class", [], "Mock$class"
- );
+ $mocks["primary-$type"] = $this->getMockBuilder( "MediaWiki\\Auth\\$class" )
+ ->setMethods( [
+ 'getUniqueId', 'accountCreationType', 'getAuthenticationRequests',
+ 'providerAllowsAuthenticationDataChange',
+ ] )
+ ->getMockForAbstractClass();
$mocks["primary-$type"]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( "primary-$type" ) );
$mocks["primary-$type"]->expects( $this->any() )->method( 'accountCreationType' )
@@ -2880,9 +2882,12 @@ class AuthManagerTest extends \MediaWikiTestCase {
$this->primaryauthMocks[] = $mocks["primary-$type"];
}
- $mocks['primary2'] = $this->getMockForAbstractClass(
- PrimaryAuthenticationProvider::class, [], "MockPrimaryAuthenticationProvider"
- );
+ $mocks['primary2'] = $this->getMockBuilder( PrimaryAuthenticationProvider::class )
+ ->setMethods( [
+ 'getUniqueId', 'accountCreationType', 'getAuthenticationRequests',
+ 'providerAllowsAuthenticationDataChange',
+ ] )
+ ->getMockForAbstractClass();
$mocks['primary2']->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( 'primary2' ) );
$mocks['primary2']->expects( $this->any() )->method( 'accountCreationType' )
@@ -3133,9 +3138,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
$mocks = [];
foreach ( [ 'primary', 'secondary' ] as $key ) {
$class = ucfirst( $key ) . 'AuthenticationProvider';
- $mocks[$key] = $this->getMockForAbstractClass(
- "MediaWiki\\Auth\\$class", [], "Mock$class"
- );
+ $mocks[$key] = $this->getMockForAbstractClass( "MediaWiki\\Auth\\$class" );
$mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key ) );
$mocks[$key]->expects( $this->any() )->method( 'providerAllowsPropertyChange' )
@@ -3219,8 +3222,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
public function testAutoCreateFailOnLogin() {
$username = self::usernameForCreation();
- $mock = $this->getMockForAbstractClass(
- PrimaryAuthenticationProvider::class, [], "MockPrimaryAuthenticationProvider" );
+ $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'primary' ) );
$mock->expects( $this->any() )->method( 'beginPrimaryAuthentication' )
->will( $this->returnValue( AuthenticationResponse::newPass( $username ) ) );
@@ -3474,7 +3476,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
$p->postCalled = false;
$p->expects( $this->atMost( 1 ) )->method( 'postAccountLink' )
->willReturnCallback( function ( $user, $response ) use ( $constraint, $p ) {
- $this->assertInstanceOf( 'User', $user );
+ $this->assertInstanceOf( \User::class, $user );
$this->assertSame( 'UTSysop', $user->getName() );
$this->assertInstanceOf( AuthenticationResponse::class, $response );
$this->assertThat( $response->status, $constraint );
diff --git a/www/wiki/tests/phpunit/includes/auth/AuthPluginPrimaryAuthenticationProviderTest.php b/www/wiki/tests/phpunit/includes/auth/AuthPluginPrimaryAuthenticationProviderTest.php
index 69703134..57c3e7eb 100644
--- a/www/wiki/tests/phpunit/includes/auth/AuthPluginPrimaryAuthenticationProviderTest.php
+++ b/www/wiki/tests/phpunit/includes/auth/AuthPluginPrimaryAuthenticationProviderTest.php
@@ -20,7 +20,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
);
}
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
@@ -51,7 +51,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
public function testOnUserSaveSettings() {
$user = \User::newFromName( 'UTSysop' );
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->once() )->method( 'updateExternalDB' )
->with( $this->identicalTo( $user ) );
@@ -63,7 +63,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
public function testOnUserGroupsChanged() {
$user = \User::newFromName( 'UTSysop' );
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->once() )->method( 'updateExternalDBGroups' )
->with(
@@ -73,20 +73,20 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
);
$provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
- \Hooks::run( 'UserGroupsChanged', [ $user, [ 'added' ], [ 'removed' ] ] );
+ \Hooks::run( 'UserGroupsChanged', [ $user, [ 'added' ], [ 'removed' ], false, false, [], [] ] );
}
public function testOnUserLoggedIn() {
$user = \User::newFromName( 'UTSysop' );
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->exactly( 2 ) )->method( 'updateUser' )
->with( $this->identicalTo( $user ) );
$provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
\Hooks::run( 'UserLoggedIn', [ $user ] );
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->once() )->method( 'updateUser' )
->will( $this->returnCallback( function ( &$user ) {
@@ -107,14 +107,14 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
public function testOnLocalUserCreated() {
$user = \User::newFromName( 'UTSysop' );
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->exactly( 2 ) )->method( 'initUser' )
->with( $this->identicalTo( $user ), $this->identicalTo( false ) );
$provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
\Hooks::run( 'LocalUserCreated', [ $user, false ] );
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->once() )->method( 'initUser' )
->will( $this->returnCallback( function ( &$user ) {
@@ -133,7 +133,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
}
public function testGetUniqueId() {
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
$this->assertSame(
@@ -149,7 +149,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
* @param bool $allowPasswordChange
*/
public function testGetAuthenticationRequests( $action, $response, $allowPasswordChange ) {
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->any() )->method( 'allowPasswordChange' )
->will( $this->returnValue( $allowPasswordChange ) );
@@ -178,7 +178,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$req->action = AuthManager::ACTION_LOGIN;
$reqs = [ PasswordAuthenticationRequest::class => $req ];
- $plugin = $this->getMockBuilder( 'AuthPlugin' )
+ $plugin = $this->getMockBuilder( \AuthPlugin::class )
->setMethods( [ 'authenticate' ] )
->getMock();
$plugin->expects( $this->never() )->method( 'authenticate' );
@@ -206,7 +206,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$req->username = 'foo';
$req->password = 'bar';
- $plugin = $this->getMockBuilder( 'AuthPlugin' )
+ $plugin = $this->getMockBuilder( \AuthPlugin::class )
->setMethods( [ 'userExists', 'authenticate' ] )
->getMock();
$plugin->expects( $this->once() )->method( 'userExists' )
@@ -220,7 +220,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$provider->beginPrimaryAuthentication( $reqs )
);
- $plugin = $this->getMockBuilder( 'AuthPlugin' )
+ $plugin = $this->getMockBuilder( \AuthPlugin::class )
->setMethods( [ 'userExists', 'authenticate' ] )
->getMock();
$plugin->expects( $this->once() )->method( 'userExists' )
@@ -232,13 +232,13 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$provider->beginPrimaryAuthentication( $reqs )
);
- $pluginUser = $this->getMockBuilder( 'AuthPluginUser' )
+ $pluginUser = $this->getMockBuilder( \AuthPluginUser::class )
->setMethods( [ 'isLocked' ] )
->disableOriginalConstructor()
->getMock();
$pluginUser->expects( $this->once() )->method( 'isLocked' )
->will( $this->returnValue( true ) );
- $plugin = $this->getMockBuilder( 'AuthPlugin' )
+ $plugin = $this->getMockBuilder( \AuthPlugin::class )
->setMethods( [ 'userExists', 'getUserInstance', 'authenticate' ] )
->getMock();
$plugin->expects( $this->once() )->method( 'userExists' )
@@ -252,7 +252,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$provider->beginPrimaryAuthentication( $reqs )
);
- $plugin = $this->getMockBuilder( 'AuthPlugin' )
+ $plugin = $this->getMockBuilder( \AuthPlugin::class )
->setMethods( [ 'userExists', 'authenticate' ] )
->getMock();
$plugin->expects( $this->once() )->method( 'userExists' )
@@ -266,7 +266,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$provider->beginPrimaryAuthentication( $reqs )
);
- $plugin = $this->getMockBuilder( 'AuthPlugin' )
+ $plugin = $this->getMockBuilder( \AuthPlugin::class )
->setMethods( [ 'userExists', 'authenticate', 'strict' ] )
->getMock();
$plugin->expects( $this->once() )->method( 'userExists' )
@@ -280,7 +280,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'wrongpassword', $ret->message->getKey() );
- $plugin = $this->getMockBuilder( 'AuthPlugin' )
+ $plugin = $this->getMockBuilder( \AuthPlugin::class )
->setMethods( [ 'userExists', 'authenticate', 'strictUserAuth' ] )
->getMock();
$plugin->expects( $this->once() )->method( 'userExists' )
@@ -296,7 +296,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'wrongpassword', $ret->message->getKey() );
- $plugin = $this->getMockBuilder( 'AuthPlugin' )
+ $plugin = $this->getMockBuilder( \AuthPlugin::class )
->setMethods( [ 'domainList', 'validDomain', 'setDomain', 'userExists', 'authenticate' ] )
->getMock();
$plugin->expects( $this->any() )->method( 'domainList' )
@@ -321,7 +321,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
}
public function testTestUserExists() {
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->once() )->method( 'userExists' )
->with( $this->equalTo( 'Foo' ) )
@@ -330,7 +330,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$this->assertTrue( $provider->testUserExists( 'foo' ) );
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->once() )->method( 'userExists' )
->with( $this->equalTo( 'Foo' ) )
@@ -341,7 +341,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
}
public function testTestUserCanAuthenticate() {
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->once() )->method( 'userExists' )
->with( $this->equalTo( 'Foo' ) )
@@ -350,19 +350,19 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
$this->assertFalse( $provider->testUserCanAuthenticate( 'foo' ) );
- $pluginUser = $this->getMockBuilder( 'AuthPluginUser' )
+ $pluginUser = $this->getMockBuilder( \AuthPluginUser::class )
->disableOriginalConstructor()
->getMock();
$pluginUser->expects( $this->once() )->method( 'isLocked' )
->will( $this->returnValue( true ) );
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->once() )->method( 'userExists' )
->with( $this->equalTo( 'Foo' ) )
->will( $this->returnValue( true ) );
$plugin->expects( $this->once() )->method( 'getUserInstance' )
->with( $this->callback( function ( $user ) {
- $this->assertInstanceOf( 'User', $user );
+ $this->assertInstanceOf( \User::class, $user );
$this->assertEquals( 'Foo', $user->getName() );
return true;
} ) )
@@ -370,19 +370,19 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
$this->assertFalse( $provider->testUserCanAuthenticate( 'foo' ) );
- $pluginUser = $this->getMockBuilder( 'AuthPluginUser' )
+ $pluginUser = $this->getMockBuilder( \AuthPluginUser::class )
->disableOriginalConstructor()
->getMock();
$pluginUser->expects( $this->once() )->method( 'isLocked' )
->will( $this->returnValue( false ) );
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->once() )->method( 'userExists' )
->with( $this->equalTo( 'Foo' ) )
->will( $this->returnValue( true ) );
$plugin->expects( $this->once() )->method( 'getUserInstance' )
->with( $this->callback( function ( $user ) {
- $this->assertInstanceOf( 'User', $user );
+ $this->assertInstanceOf( \User::class, $user );
$this->assertEquals( 'Foo', $user->getName() );
return true;
} ) )
@@ -392,7 +392,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
}
public function testProviderRevokeAccessForUser() {
- $plugin = $this->getMockBuilder( 'AuthPlugin' )
+ $plugin = $this->getMockBuilder( \AuthPlugin::class )
->setMethods( [ 'userExists', 'setPassword' ] )
->getMock();
$plugin->expects( $this->once() )->method( 'userExists' )->willReturn( true );
@@ -404,7 +404,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
$provider->providerRevokeAccessForUser( 'foo' );
- $plugin = $this->getMockBuilder( 'AuthPlugin' )
+ $plugin = $this->getMockBuilder( \AuthPlugin::class )
->setMethods( [ 'domainList', 'userExists', 'setPassword' ] )
->getMock();
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [ 'D1', 'D2', 'D3' ] );
@@ -433,7 +433,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
}
public function testProviderAllowsPropertyChange() {
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->any() )->method( 'allowPropChange' )
->will( $this->returnCallback( function ( $prop ) {
@@ -453,7 +453,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
*/
public function testProviderAllowsAuthenticationDataChange( $type, $allow, $expect ) {
$domains = $type instanceof PasswordDomainAuthenticationRequest ? [ 'foo', 'bar' ] : [];
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( $domains );
$plugin->expects( $allow === null ? $this->never() : $this->once() )
->method( 'allowPasswordChange' )->will( $this->returnValue( $allow ) );
@@ -502,7 +502,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
}
public function testProviderChangeAuthenticationData() {
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->never() )->method( 'setPassword' );
$provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
@@ -515,7 +515,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$req->username = 'foo';
$req->password = 'bar';
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->once() )->method( 'setPassword' )
->with( $this->callback( function ( $u ) {
@@ -525,7 +525,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
$provider->providerChangeAuthenticationData( $req );
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->once() )->method( 'setPassword' )
->with( $this->callback( function ( $u ) {
@@ -541,7 +541,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$this->assertSame( 'authmanager-authplugin-setpass-failed-message', $e->msg );
}
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )
->will( $this->returnValue( [ 'Domain1', 'Domain2' ] ) );
$plugin->expects( $this->any() )->method( 'validDomain' )
@@ -569,7 +569,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
* @param string $expect
*/
public function testAccountCreationType( $can, $expect ) {
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->once() )
->method( 'canCreateAccounts' )->will( $this->returnValue( $can ) );
@@ -588,7 +588,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
public function testTestForAccountCreation() {
$user = \User::newFromName( 'foo' );
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
$this->assertEquals(
@@ -606,7 +606,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$req->action = AuthManager::ACTION_CREATE;
$reqs = [ PasswordAuthenticationRequest::class => $req ];
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->any() )->method( 'canCreateAccounts' )
->will( $this->returnValue( false ) );
@@ -621,7 +621,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
);
}
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->any() )->method( 'canCreateAccounts' )
->will( $this->returnValue( true ) );
@@ -650,7 +650,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$req->username = 'foo';
$req->password = 'bar';
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->any() )->method( 'canCreateAccounts' )
->will( $this->returnValue( true ) );
@@ -670,7 +670,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$provider->beginPrimaryAccountCreation( $user, $user, $reqs )
);
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
$plugin->expects( $this->any() )->method( 'canCreateAccounts' )
->will( $this->returnValue( true ) );
@@ -689,7 +689,7 @@ class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'authmanager-authplugin-create-fail', $ret->message->getKey() );
- $plugin = $this->createMock( 'AuthPlugin' );
+ $plugin = $this->createMock( \AuthPlugin::class );
$plugin->expects( $this->any() )->method( 'canCreateAccounts' )
->will( $this->returnValue( true ) );
$plugin->expects( $this->any() )->method( 'domainList' )
diff --git a/www/wiki/tests/phpunit/includes/auth/AuthenticationRequestTest.php b/www/wiki/tests/phpunit/includes/auth/AuthenticationRequestTest.php
index 0e549a5c..1bc0f31f 100644
--- a/www/wiki/tests/phpunit/includes/auth/AuthenticationRequestTest.php
+++ b/www/wiki/tests/phpunit/includes/auth/AuthenticationRequestTest.php
@@ -17,9 +17,9 @@ class AuthenticationRequestTest extends \MediaWikiTestCase {
$ret = $mock->describeCredentials();
$this->assertInternalType( 'array', $ret );
$this->assertArrayHasKey( 'provider', $ret );
- $this->assertInstanceOf( 'Message', $ret['provider'] );
+ $this->assertInstanceOf( \Message::class, $ret['provider'] );
$this->assertArrayHasKey( 'account', $ret );
- $this->assertInstanceOf( 'Message', $ret['account'] );
+ $this->assertInstanceOf( \Message::class, $ret['account'] );
}
public function testLoadRequestsFromSubmission() {
diff --git a/www/wiki/tests/phpunit/includes/auth/AuthenticationRequestTestCase.php b/www/wiki/tests/phpunit/includes/auth/AuthenticationRequestTestCase.php
index b5c8a36c..f483b9b6 100644
--- a/www/wiki/tests/phpunit/includes/auth/AuthenticationRequestTestCase.php
+++ b/www/wiki/tests/phpunit/includes/auth/AuthenticationRequestTestCase.php
@@ -19,11 +19,11 @@ abstract class AuthenticationRequestTestCase extends \MediaWikiTestCase {
$this->assertType( 'array', $data, "Field $field" );
$this->assertArrayHasKey( 'type', $data, "Field $field" );
$this->assertArrayHasKey( 'label', $data, "Field $field" );
- $this->assertInstanceOf( 'Message', $data['label'], "Field $field, label" );
+ $this->assertInstanceOf( \Message::class, $data['label'], "Field $field, label" );
if ( $data['type'] !== 'null' ) {
$this->assertArrayHasKey( 'help', $data, "Field $field" );
- $this->assertInstanceOf( 'Message', $data['help'], "Field $field, help" );
+ $this->assertInstanceOf( \Message::class, $data['help'], "Field $field, help" );
}
if ( isset( $data['optional'] ) ) {
@@ -50,7 +50,7 @@ abstract class AuthenticationRequestTestCase extends \MediaWikiTestCase {
$this->assertArrayHasKey( 'options', $data, "Field $field" );
$this->assertType( 'array', $data['options'], "Field $field, options" );
foreach ( $data['options'] as $val => $msg ) {
- $this->assertInstanceOf( 'Message', $msg, "Field $field, option $val" );
+ $this->assertInstanceOf( \Message::class, $msg, "Field $field, option $val" );
}
break;
case 'checkbox':
diff --git a/www/wiki/tests/phpunit/includes/auth/CheckBlocksSecondaryAuthenticationProviderTest.php b/www/wiki/tests/phpunit/includes/auth/CheckBlocksSecondaryAuthenticationProviderTest.php
index 111c8554..e8b61c59 100644
--- a/www/wiki/tests/phpunit/includes/auth/CheckBlocksSecondaryAuthenticationProviderTest.php
+++ b/www/wiki/tests/phpunit/includes/auth/CheckBlocksSecondaryAuthenticationProviderTest.php
@@ -76,6 +76,7 @@ class CheckBlocksSecondaryAuthenticationProviderTest extends \MediaWikiTestCase
$blockOptions = [
'address' => 'UTBlockee',
'user' => $user->getID(),
+ 'by' => $this->getTestSysop()->getUser()->getId(),
'reason' => __METHOD__,
'expiry' => time() + 100500,
'createAccount' => true,
@@ -135,12 +136,12 @@ class CheckBlocksSecondaryAuthenticationProviderTest extends \MediaWikiTestCase
);
$status = $provider->testUserForCreation( $blockedUser, AuthManager::AUTOCREATE_SOURCE_SESSION );
- $this->assertInstanceOf( 'StatusValue', $status );
+ $this->assertInstanceOf( \StatusValue::class, $status );
$this->assertFalse( $status->isOK() );
$this->assertTrue( $status->hasMessage( 'cantcreateaccount-text' ) );
$status = $provider->testUserForCreation( $blockedUser, false );
- $this->assertInstanceOf( 'StatusValue', $status );
+ $this->assertInstanceOf( \StatusValue::class, $status );
$this->assertFalse( $status->isOK() );
$this->assertTrue( $status->hasMessage( 'cantcreateaccount-text' ) );
}
@@ -149,6 +150,7 @@ class CheckBlocksSecondaryAuthenticationProviderTest extends \MediaWikiTestCase
$blockOptions = [
'address' => '127.0.0.0/24',
'reason' => __METHOD__,
+ 'by' => $this->getTestSysop()->getUser()->getId(),
'expiry' => time() + 100500,
'createAccount' => true,
];
@@ -163,6 +165,7 @@ class CheckBlocksSecondaryAuthenticationProviderTest extends \MediaWikiTestCase
$user->saveSettings();
}
$this->setMwGlobals( [ 'wgUser' => $user ] );
+ \RequestContext::getMain()->setUser( $user );
$newuser = \User::newFromName( 'RandomUser' );
$provider = new CheckBlocksSecondaryAuthenticationProvider(
@@ -176,12 +179,12 @@ class CheckBlocksSecondaryAuthenticationProviderTest extends \MediaWikiTestCase
$this->assertEquals( AuthenticationResponse::FAIL, $ret->status );
$status = $provider->testUserForCreation( $newuser, AuthManager::AUTOCREATE_SOURCE_SESSION );
- $this->assertInstanceOf( 'StatusValue', $status );
+ $this->assertInstanceOf( \StatusValue::class, $status );
$this->assertFalse( $status->isOK() );
$this->assertTrue( $status->hasMessage( 'cantcreateaccount-range-text' ) );
$status = $provider->testUserForCreation( $newuser, false );
- $this->assertInstanceOf( 'StatusValue', $status );
+ $this->assertInstanceOf( \StatusValue::class, $status );
$this->assertFalse( $status->isOK() );
$this->assertTrue( $status->hasMessage( 'cantcreateaccount-range-text' ) );
}
diff --git a/www/wiki/tests/phpunit/includes/auth/EmailNotificationSecondaryAuthenticationProviderTest.php b/www/wiki/tests/phpunit/includes/auth/EmailNotificationSecondaryAuthenticationProviderTest.php
index 3757069e..1a7ed12d 100644
--- a/www/wiki/tests/phpunit/includes/auth/EmailNotificationSecondaryAuthenticationProviderTest.php
+++ b/www/wiki/tests/phpunit/includes/auth/EmailNotificationSecondaryAuthenticationProviderTest.php
@@ -5,7 +5,7 @@ namespace MediaWiki\Auth;
use Psr\Log\LoggerInterface;
use Wikimedia\TestingAccessWrapper;
-class EmailNotificationSecondaryAuthenticationProviderTest extends \PHPUnit_Framework_TestCase {
+class EmailNotificationSecondaryAuthenticationProviderTest extends \PHPUnit\Framework\TestCase {
public function testConstructor() {
$config = new \HashConfig( [
'EnableEmail' => true,
@@ -58,24 +58,24 @@ class EmailNotificationSecondaryAuthenticationProviderTest extends \PHPUnit_Fram
public function testBeginSecondaryAccountCreation() {
$authManager = new AuthManager( new \FauxRequest(), new \HashConfig() );
- $creator = $this->getMockBuilder( 'User' )->getMock();
- $userWithoutEmail = $this->getMockBuilder( 'User' )->getMock();
+ $creator = $this->getMockBuilder( \User::class )->getMock();
+ $userWithoutEmail = $this->getMockBuilder( \User::class )->getMock();
$userWithoutEmail->expects( $this->any() )->method( 'getEmail' )->willReturn( '' );
$userWithoutEmail->expects( $this->any() )->method( 'getInstanceForUpdate' )->willReturnSelf();
$userWithoutEmail->expects( $this->never() )->method( 'sendConfirmationMail' );
- $userWithEmailError = $this->getMockBuilder( 'User' )->getMock();
+ $userWithEmailError = $this->getMockBuilder( \User::class )->getMock();
$userWithEmailError->expects( $this->any() )->method( 'getEmail' )->willReturn( 'foo@bar.baz' );
$userWithEmailError->expects( $this->any() )->method( 'getInstanceForUpdate' )->willReturnSelf();
$userWithEmailError->expects( $this->any() )->method( 'sendConfirmationMail' )
->willReturn( \Status::newFatal( 'fail' ) );
- $userExpectsConfirmation = $this->getMockBuilder( 'User' )->getMock();
+ $userExpectsConfirmation = $this->getMockBuilder( \User::class )->getMock();
$userExpectsConfirmation->expects( $this->any() )->method( 'getEmail' )
->willReturn( 'foo@bar.baz' );
$userExpectsConfirmation->expects( $this->any() )->method( 'getInstanceForUpdate' )
->willReturnSelf();
$userExpectsConfirmation->expects( $this->once() )->method( 'sendConfirmationMail' )
->willReturn( \Status::newGood() );
- $userNotExpectsConfirmation = $this->getMockBuilder( 'User' )->getMock();
+ $userNotExpectsConfirmation = $this->getMockBuilder( \User::class )->getMock();
$userNotExpectsConfirmation->expects( $this->any() )->method( 'getEmail' )
->willReturn( 'foo@bar.baz' );
$userNotExpectsConfirmation->expects( $this->any() )->method( 'getInstanceForUpdate' )
diff --git a/www/wiki/tests/phpunit/includes/auth/LegacyHookPreAuthenticationProviderTest.php b/www/wiki/tests/phpunit/includes/auth/LegacyHookPreAuthenticationProviderTest.php
index a4b980f0..38ccb8a3 100644
--- a/www/wiki/tests/phpunit/includes/auth/LegacyHookPreAuthenticationProviderTest.php
+++ b/www/wiki/tests/phpunit/includes/auth/LegacyHookPreAuthenticationProviderTest.php
@@ -15,7 +15,7 @@ class LegacyHookPreAuthenticationProviderTest extends \MediaWikiTestCase {
* @return LegacyHookPreAuthenticationProvider
*/
protected function getProvider() {
- $request = $this->getMockBuilder( 'FauxRequest' )
+ $request = $this->getMockBuilder( \FauxRequest::class )
->setMethods( [ 'getIP' ] )->getMock();
$request->expects( $this->any() )->method( 'getIP' )->will( $this->returnValue( '127.0.0.42' ) );
@@ -101,7 +101,7 @@ class LegacyHookPreAuthenticationProviderTest extends \MediaWikiTestCase {
if ( $msgForLoginUserMigrated !== null ) {
$h->will( $this->returnCallback(
function ( $user, &$msg ) use ( $username, $msgForLoginUserMigrated ) {
- $this->assertInstanceOf( 'User', $user );
+ $this->assertInstanceOf( \User::class, $user );
$this->assertSame( $username, $user->getName() );
$msg = $msgForLoginUserMigrated;
return false;
@@ -111,7 +111,7 @@ class LegacyHookPreAuthenticationProviderTest extends \MediaWikiTestCase {
} else {
$h->will( $this->returnCallback(
function ( $user, &$msg ) use ( $username ) {
- $this->assertInstanceOf( 'User', $user );
+ $this->assertInstanceOf( \User::class, $user );
$this->assertSame( $username, $user->getName() );
return true;
}
@@ -122,7 +122,7 @@ class LegacyHookPreAuthenticationProviderTest extends \MediaWikiTestCase {
function ( $user, $pass, &$abort, &$msg )
use ( $username, $password, $abortForAbortLogin, $msgForAbortLogin )
{
- $this->assertInstanceOf( 'User', $user );
+ $this->assertInstanceOf( \User::class, $user );
$this->assertSame( $username, $user->getName() );
if ( $password !== null ) {
$this->assertSame( $password, $pass );
@@ -137,7 +137,7 @@ class LegacyHookPreAuthenticationProviderTest extends \MediaWikiTestCase {
} else {
$h2->will( $this->returnCallback(
function ( $user, $pass, &$abort, &$msg ) use ( $username, $password ) {
- $this->assertInstanceOf( 'User', $user );
+ $this->assertInstanceOf( \User::class, $user );
$this->assertSame( $username, $user->getName() );
if ( $password !== null ) {
$this->assertSame( $password, $pass );
@@ -160,7 +160,7 @@ class LegacyHookPreAuthenticationProviderTest extends \MediaWikiTestCase {
if ( $failMsg === null ) {
$this->assertEquals( \StatusValue::newGood(), $status, 'should succeed' );
} else {
- $this->assertInstanceOf( 'StatusValue', $status, 'should fail (type)' );
+ $this->assertInstanceOf( \StatusValue::class, $status, 'should fail (type)' );
$this->assertFalse( $status->isOk(), 'should fail (ok)' );
$errors = $status->getErrors();
$this->assertEquals( $failMsg, $errors[0]['message'], 'should fail (message)' );
@@ -282,14 +282,14 @@ class LegacyHookPreAuthenticationProviderTest extends \MediaWikiTestCase {
* @dataProvider provideTestForAccountCreation
* @param string $msg
* @param Status|null $status
- * @param StatusValue $result Result
+ * @param StatusValue $result
*/
public function testTestForAccountCreation( $msg, $status, $result ) {
$this->hook( 'AbortNewAccount', $this->once() )
->will( $this->returnCallback( function ( $user, &$error, &$abortStatus )
use ( $msg, $status )
{
- $this->assertInstanceOf( 'User', $user );
+ $this->assertInstanceOf( \User::class, $user );
$this->assertSame( 'User', $user->getName() );
$error = $msg;
$abortStatus = $status;
@@ -336,7 +336,7 @@ class LegacyHookPreAuthenticationProviderTest extends \MediaWikiTestCase {
$this->hook( 'AbortNewAccount', $this->never() );
$this->hook( 'AbortAutoAccount', $this->once() )
->will( $this->returnCallback( function ( $user, &$abortError ) use ( $testUser, $error ) {
- $this->assertInstanceOf( 'User', $user );
+ $this->assertInstanceOf( \User::class, $user );
$this->assertSame( $testUser->getName(), $user->getName() );
$abortError = $error;
return $error === null;
@@ -349,7 +349,7 @@ class LegacyHookPreAuthenticationProviderTest extends \MediaWikiTestCase {
if ( $failMsg === null ) {
$this->assertEquals( \StatusValue::newGood(), $status, 'should succeed' );
} else {
- $this->assertInstanceOf( 'StatusValue', $status, 'should fail (type)' );
+ $this->assertInstanceOf( \StatusValue::class, $status, 'should fail (type)' );
$this->assertFalse( $status->isOk(), 'should fail (ok)' );
$errors = $status->getErrors();
$this->assertEquals( $failMsg, $errors[0]['message'], 'should fail (message)' );
diff --git a/www/wiki/tests/phpunit/includes/auth/PasswordAuthenticationRequestTest.php b/www/wiki/tests/phpunit/includes/auth/PasswordAuthenticationRequestTest.php
index 3387e7c9..1ef675b6 100644
--- a/www/wiki/tests/phpunit/includes/auth/PasswordAuthenticationRequestTest.php
+++ b/www/wiki/tests/phpunit/includes/auth/PasswordAuthenticationRequestTest.php
@@ -129,10 +129,10 @@ class PasswordAuthenticationRequestTest extends AuthenticationRequestTestCase {
$ret = $req->describeCredentials();
$this->assertInternalType( 'array', $ret );
$this->assertArrayHasKey( 'provider', $ret );
- $this->assertInstanceOf( 'Message', $ret['provider'] );
+ $this->assertInstanceOf( \Message::class, $ret['provider'] );
$this->assertSame( 'authmanager-provider-password', $ret['provider']->getKey() );
$this->assertArrayHasKey( 'account', $ret );
- $this->assertInstanceOf( 'Message', $ret['account'] );
+ $this->assertInstanceOf( \Message::class, $ret['account'] );
$this->assertSame( [ 'UTSysop' ], $ret['account']->getParams() );
}
}
diff --git a/www/wiki/tests/phpunit/includes/auth/PasswordDomainAuthenticationRequestTest.php b/www/wiki/tests/phpunit/includes/auth/PasswordDomainAuthenticationRequestTest.php
index f746515b..36be4243 100644
--- a/www/wiki/tests/phpunit/includes/auth/PasswordDomainAuthenticationRequestTest.php
+++ b/www/wiki/tests/phpunit/includes/auth/PasswordDomainAuthenticationRequestTest.php
@@ -149,10 +149,10 @@ class PasswordDomainAuthenticationRequestTest extends AuthenticationRequestTestC
$ret = $req->describeCredentials();
$this->assertInternalType( 'array', $ret );
$this->assertArrayHasKey( 'provider', $ret );
- $this->assertInstanceOf( 'Message', $ret['provider'] );
+ $this->assertInstanceOf( \Message::class, $ret['provider'] );
$this->assertSame( 'authmanager-provider-password-domain', $ret['provider']->getKey() );
$this->assertArrayHasKey( 'account', $ret );
- $this->assertInstanceOf( 'Message', $ret['account'] );
+ $this->assertInstanceOf( \Message::class, $ret['account'] );
$this->assertSame( 'authmanager-account-password-domain', $ret['account']->getKey() );
$this->assertSame( [ 'UTSysop', 'd2' ], $ret['account']->getParams() );
}
diff --git a/www/wiki/tests/phpunit/includes/auth/TemporaryPasswordAuthenticationRequestTest.php b/www/wiki/tests/phpunit/includes/auth/TemporaryPasswordAuthenticationRequestTest.php
index 05c5165b..ab4a174e 100644
--- a/www/wiki/tests/phpunit/includes/auth/TemporaryPasswordAuthenticationRequestTest.php
+++ b/www/wiki/tests/phpunit/includes/auth/TemporaryPasswordAuthenticationRequestTest.php
@@ -70,10 +70,10 @@ class TemporaryPasswordAuthenticationRequestTest extends AuthenticationRequestTe
$ret = $req->describeCredentials();
$this->assertInternalType( 'array', $ret );
$this->assertArrayHasKey( 'provider', $ret );
- $this->assertInstanceOf( 'Message', $ret['provider'] );
+ $this->assertInstanceOf( \Message::class, $ret['provider'] );
$this->assertSame( 'authmanager-provider-temporarypassword', $ret['provider']->getKey() );
$this->assertArrayHasKey( 'account', $ret );
- $this->assertInstanceOf( 'Message', $ret['account'] );
+ $this->assertInstanceOf( \Message::class, $ret['account'] );
$this->assertSame( [ 'UTSysop' ], $ret['account']->getParams() );
}
}
diff --git a/www/wiki/tests/phpunit/includes/auth/ThrottlePreAuthenticationProviderTest.php b/www/wiki/tests/phpunit/includes/auth/ThrottlePreAuthenticationProviderTest.php
index 58982de2..d03b1515 100644
--- a/www/wiki/tests/phpunit/includes/auth/ThrottlePreAuthenticationProviderTest.php
+++ b/www/wiki/tests/phpunit/includes/auth/ThrottlePreAuthenticationProviderTest.php
@@ -121,7 +121,7 @@ class ThrottlePreAuthenticationProviderTest extends \MediaWikiTestCase {
$user = \User::newFromName( 'RandomUser' );
$creator = \User::newFromName( $creatorname );
if ( $hook ) {
- $mock = $this->getMockBuilder( 'stdClass' )
+ $mock = $this->getMockBuilder( stdClass::class )
->setMethods( [ 'onExemptFromAccountCreationThrottle' ] )
->getMock();
$mock->expects( $this->any() )->method( 'onExemptFromAccountCreationThrottle' )
diff --git a/www/wiki/tests/phpunit/includes/auth/ThrottlerTest.php b/www/wiki/tests/phpunit/includes/auth/ThrottlerTest.php
index f52048ad..f963ad9c 100644
--- a/www/wiki/tests/phpunit/includes/auth/ThrottlerTest.php
+++ b/www/wiki/tests/phpunit/includes/auth/ThrottlerTest.php
@@ -4,7 +4,6 @@ namespace MediaWiki\Auth;
use BagOStuff;
use HashBagOStuff;
-use InvalidArgumentException;
use Psr\Log\AbstractLogger;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
diff --git a/www/wiki/tests/phpunit/includes/cache/LocalisationCacheTest.php b/www/wiki/tests/phpunit/includes/cache/LocalisationCacheTest.php
index 5eed01cb..42957b60 100644
--- a/www/wiki/tests/phpunit/includes/cache/LocalisationCacheTest.php
+++ b/www/wiki/tests/phpunit/includes/cache/LocalisationCacheTest.php
@@ -19,7 +19,7 @@ class LocalisationCacheTest extends MediaWikiTestCase {
*/
protected function getMockLocalisationCache() {
global $IP;
- $lc = $this->getMockBuilder( 'LocalisationCache' )
+ $lc = $this->getMockBuilder( \LocalisationCache::class )
->setConstructorArgs( [ [ 'store' => 'detect' ] ] )
->setMethods( [ 'getMessagesDirs' ] )
->getMock();
diff --git a/www/wiki/tests/phpunit/includes/changes/CategoryMembershipChangeTest.php b/www/wiki/tests/phpunit/includes/changes/CategoryMembershipChangeTest.php
index e44de099..ca3ac1b6 100644
--- a/www/wiki/tests/phpunit/includes/changes/CategoryMembershipChangeTest.php
+++ b/www/wiki/tests/phpunit/includes/changes/CategoryMembershipChangeTest.php
@@ -48,7 +48,7 @@ class CategoryMembershipChangeTest extends MediaWikiLangTestCase {
public function setUp() {
parent::setUp();
self::$notifyCallCounter = 0;
- self::$mockRecentChange = self::getMock( 'RecentChange' );
+ self::$mockRecentChange = self::getMock( RecentChange::class );
$this->setContentLang( 'qqx' );
}
diff --git a/www/wiki/tests/phpunit/includes/changes/ChangesListBooleanFilterGroupTest.php b/www/wiki/tests/phpunit/includes/changes/ChangesListBooleanFilterGroupTest.php
index 07dec055..d80b6c10 100644
--- a/www/wiki/tests/phpunit/includes/changes/ChangesListBooleanFilterGroupTest.php
+++ b/www/wiki/tests/phpunit/includes/changes/ChangesListBooleanFilterGroupTest.php
@@ -63,6 +63,7 @@ class ChangesListBooleanFilterGroupTest extends MediaWikiTestCase {
'cssClass' => null,
'conflicts' => [],
'subset' => [],
+ 'defaultHighlightColor' => null,
],
[
'name' => 'hidefoo',
@@ -73,6 +74,7 @@ class ChangesListBooleanFilterGroupTest extends MediaWikiTestCase {
'cssClass' => null,
'conflicts' => [],
'subset' => [],
+ 'defaultHighlightColor' => null,
],
],
'conflicts' => [],
diff --git a/www/wiki/tests/phpunit/includes/changes/ChangesListBooleanFilterTest.php b/www/wiki/tests/phpunit/includes/changes/ChangesListBooleanFilterTest.php
index 000f0177..35dc1a83 100644
--- a/www/wiki/tests/phpunit/includes/changes/ChangesListBooleanFilterTest.php
+++ b/www/wiki/tests/phpunit/includes/changes/ChangesListBooleanFilterTest.php
@@ -49,6 +49,7 @@ class ChangesListBooleanFilterTest extends MediaWikiTestCase {
'default' => 1,
'priority' => 1,
'cssClass' => null,
+ 'defaultHighlightColor' => null,
'conflicts' => [
[
'group' => 'group',
@@ -85,6 +86,7 @@ class ChangesListBooleanFilterTest extends MediaWikiTestCase {
'default' => 1,
'priority' => 1,
'cssClass' => null,
+ 'defaultHighlightColor' => null,
'conflicts' => [
[
'group' => 'group',
diff --git a/www/wiki/tests/phpunit/includes/changes/ChangesListFilterGroupTest.php b/www/wiki/tests/phpunit/includes/changes/ChangesListFilterGroupTest.php
index 465a9d11..6190516e 100644
--- a/www/wiki/tests/phpunit/includes/changes/ChangesListFilterGroupTest.php
+++ b/www/wiki/tests/phpunit/includes/changes/ChangesListFilterGroupTest.php
@@ -4,12 +4,12 @@
* @covers ChangesListFilterGroup
*/
class ChangesListFilterGroupTest extends MediaWikiTestCase {
- // @codingStandardsIgnoreStart
/**
+ * phpcs:disable Generic.Files.LineLength
* @expectedException MWException
* @expectedExceptionMessage Group names may not contain '_'. Use the naming convention: 'camelCase'
+ * phpcs:enable
*/
- // @codingStandardsIgnoreEnd
public function testReservedCharacter() {
new MockChangesListFilterGroup(
[
diff --git a/www/wiki/tests/phpunit/includes/changes/ChangesListFilterTest.php b/www/wiki/tests/phpunit/includes/changes/ChangesListFilterTest.php
index 811c8c2b..039658e2 100644
--- a/www/wiki/tests/phpunit/includes/changes/ChangesListFilterTest.php
+++ b/www/wiki/tests/phpunit/includes/changes/ChangesListFilterTest.php
@@ -25,12 +25,12 @@ class ChangesListFilterTest extends MediaWikiTestCase {
);
}
- // @codingStandardsIgnoreStart
/**
+ * phpcs:disable Generic.Files.LineLength
* @expectedException MWException
* @expectedExceptionMessage Filter names may not contain '_'. Use the naming convention: 'lowercase'
+ * phpcs:enable
*/
- // @codingStandardsIgnoreEnd
public function testReservedCharacter() {
$filter = new MockChangesListFilter(
[
@@ -41,12 +41,10 @@ class ChangesListFilterTest extends MediaWikiTestCase {
);
}
- // @codingStandardsIgnoreStart
/**
* @expectedException MWException
* @expectedExceptionMessage Two filters in a group cannot have the same name: 'somename'
*/
- // @codingStandardsIgnoreEnd
public function testDuplicateName() {
new MockChangesListFilter(
[
diff --git a/www/wiki/tests/phpunit/includes/changes/ChangesListStringOptionsFilterGroupTest.php b/www/wiki/tests/phpunit/includes/changes/ChangesListStringOptionsFilterGroupTest.php
index 4f917e9d..b627178a 100644
--- a/www/wiki/tests/phpunit/includes/changes/ChangesListStringOptionsFilterGroupTest.php
+++ b/www/wiki/tests/phpunit/includes/changes/ChangesListStringOptionsFilterGroupTest.php
@@ -168,7 +168,7 @@ class ChangesListStringOptionsFilterGroupTest extends MediaWikiTestCase {
}
protected function getSpecialPage() {
- return $this->getMockBuilder( 'ChangesListSpecialPage' )
+ return $this->getMockBuilder( ChangesListSpecialPage::class )
->setConstructorArgs( [
'ChangesListSpecialPage',
'',
@@ -179,17 +179,17 @@ class ChangesListStringOptionsFilterGroupTest extends MediaWikiTestCase {
/**
* @param array $groupDefinition Group definition
* @param string $input Value in URL
- *
- * @dataProvider provideModifyQuery
*/
protected function modifyQueryHelper( $groupDefinition, $input ) {
- $ctx = $this->createMock( 'IContextSource' );
- $dbr = $this->createMock( 'IDatabase' );
+ $ctx = $this->createMock( IContextSource::class );
+ $dbr = $this->createMock( Wikimedia\Rdbms\IDatabase::class );
$tables = $fields = $conds = $query_options = $join_conds = [];
$group = new ChangesListStringOptionsFilterGroup( $groupDefinition );
$specialPage = $this->getSpecialPage();
+ $opts = new FormOptions();
+ $opts->add( $groupDefinition[ 'name' ], $input );
$group->modifyQuery(
$dbr,
@@ -199,7 +199,8 @@ class ChangesListStringOptionsFilterGroupTest extends MediaWikiTestCase {
$conds,
$query_options,
$join_conds,
- $input
+ $opts,
+ true
);
}
@@ -247,6 +248,7 @@ class ChangesListStringOptionsFilterGroupTest extends MediaWikiTestCase {
'cssClass' => null,
'conflicts' => [],
'subset' => [],
+ 'defaultHighlightColor' => null,
],
[
'name' => 'foo',
@@ -256,6 +258,7 @@ class ChangesListStringOptionsFilterGroupTest extends MediaWikiTestCase {
'cssClass' => null,
'conflicts' => [],
'subset' => [],
+ 'defaultHighlightColor' => null,
],
],
'conflicts' => [],
diff --git a/www/wiki/tests/phpunit/includes/changes/RCCacheEntryFactoryTest.php b/www/wiki/tests/phpunit/includes/changes/RCCacheEntryFactoryTest.php
index 97b4c088..b1857ccc 100644
--- a/www/wiki/tests/phpunit/includes/changes/RCCacheEntryFactoryTest.php
+++ b/www/wiki/tests/phpunit/includes/changes/RCCacheEntryFactoryTest.php
@@ -18,7 +18,7 @@ class RCCacheEntryFactoryTest extends MediaWikiLangTestCase {
private $testRecentChangesHelper;
/**
- * @var LinkRenderer;
+ * @var LinkRenderer
*/
private $linkRenderer;
@@ -57,7 +57,7 @@ class RCCacheEntryFactoryTest extends MediaWikiLangTestCase {
);
$cacheEntry = $cacheEntryFactory->newFromRecentChange( $recentChange, false );
- $this->assertInstanceOf( 'RCCacheEntry', $cacheEntry );
+ $this->assertInstanceOf( RCCacheEntry::class, $cacheEntry );
$this->assertEquals( false, $cacheEntry->watched, 'watched' );
$this->assertEquals( '21:21', $cacheEntry->timestamp, 'timestamp' );
@@ -92,7 +92,7 @@ class RCCacheEntryFactoryTest extends MediaWikiLangTestCase {
);
$cacheEntry = $cacheEntryFactory->newFromRecentChange( $recentChange, false );
- $this->assertInstanceOf( 'RCCacheEntry', $cacheEntry );
+ $this->assertInstanceOf( RCCacheEntry::class, $cacheEntry );
$this->assertEquals( false, $cacheEntry->watched, 'watched' );
$this->assertEquals( '21:21', $cacheEntry->timestamp, 'timestamp' );
@@ -126,7 +126,7 @@ class RCCacheEntryFactoryTest extends MediaWikiLangTestCase {
);
$cacheEntry = $cacheEntryFactory->newFromRecentChange( $recentChange, false );
- $this->assertInstanceOf( 'RCCacheEntry', $cacheEntry );
+ $this->assertInstanceOf( RCCacheEntry::class, $cacheEntry );
$this->assertEquals( false, $cacheEntry->watched, 'watched' );
$this->assertEquals( '21:21', $cacheEntry->timestamp, 'timestamp' );
diff --git a/www/wiki/tests/phpunit/includes/changes/RecentChangeTest.php b/www/wiki/tests/phpunit/includes/changes/RecentChangeTest.php
index d638d0ff..333eb286 100644
--- a/www/wiki/tests/phpunit/includes/changes/RecentChangeTest.php
+++ b/www/wiki/tests/phpunit/includes/changes/RecentChangeTest.php
@@ -27,12 +27,16 @@ class RecentChangeTest extends MediaWikiTestCase {
* @covers RecentChange::loadFromRow
*/
public function testNewFromRow() {
+ $user = $this->getTestUser()->getUser();
+ $actorId = $user->getActorId();
+
$row = new stdClass();
$row->rc_foo = 'AAA';
$row->rc_timestamp = '20150921134808';
$row->rc_deleted = 'bar';
$row->rc_comment_text = 'comment';
$row->rc_comment_data = null;
+ $row->rc_user = $user->getId();
$rc = RecentChange::newFromRow( $row );
@@ -43,6 +47,9 @@ class RecentChangeTest extends MediaWikiTestCase {
'rc_comment' => 'comment',
'rc_comment_text' => 'comment',
'rc_comment_data' => null,
+ 'rc_user' => $user->getId(),
+ 'rc_user_text' => $user->getName(),
+ 'rc_actor' => $actorId,
];
$this->assertEquals( $expected, $rc->getAttributes() );
@@ -51,10 +58,11 @@ class RecentChangeTest extends MediaWikiTestCase {
$row->rc_timestamp = '20150921134808';
$row->rc_deleted = 'bar';
$row->rc_comment = 'comment';
+ $row->rc_user = $user->getId();
- MediaWiki\suppressWarnings();
+ Wikimedia\suppressWarnings();
$rc = RecentChange::newFromRow( $row );
- MediaWiki\restoreWarnings();
+ Wikimedia\restoreWarnings();
$expected = [
'rc_foo' => 'AAA',
@@ -63,6 +71,9 @@ class RecentChangeTest extends MediaWikiTestCase {
'rc_comment' => 'comment',
'rc_comment_text' => 'comment',
'rc_comment_data' => null,
+ 'rc_user' => $user->getId(),
+ 'rc_user_text' => $user->getName(),
+ 'rc_actor' => $actorId,
];
$this->assertEquals( $expected, $rc->getAttributes() );
}
diff --git a/www/wiki/tests/phpunit/includes/changetags/ChangeTagsTest.php b/www/wiki/tests/phpunit/includes/changetags/ChangeTagsTest.php
index 723d6856..63e0ec22 100644
--- a/www/wiki/tests/phpunit/includes/changetags/ChangeTagsTest.php
+++ b/www/wiki/tests/phpunit/includes/changetags/ChangeTagsTest.php
@@ -5,7 +5,7 @@
*/
class ChangeTagsTest extends MediaWikiTestCase {
- // TODO only modifyDisplayQuery is tested, nothing else is
+ // TODO only modifyDisplayQuery and getSoftwareTags are tested, nothing else is
/** @dataProvider provideModifyDisplayQuery */
public function testModifyDisplayQuery( $origQuery, $filter_tag, $useTags, $modifiedQuery ) {
@@ -244,4 +244,66 @@ class ChangeTagsTest extends MediaWikiTestCase {
];
}
+ public static function dataGetSoftwareTags() {
+ return [
+ [
+ [
+ 'mw-contentModelChange' => true,
+ 'mw-redirect' => true,
+ 'mw-rollback' => true,
+ 'mw-blank' => true,
+ 'mw-replace' => true
+ ],
+ [
+ 'mw-rollback',
+ 'mw-replace',
+ 'mw-blank'
+ ]
+ ],
+
+ [
+ [
+ 'mw-contentmodelchanged' => true,
+ 'mw-replace' => true,
+ 'mw-new-redirects' => true,
+ 'mw-changed-redirect-target' => true,
+ 'mw-rolback' => true,
+ 'mw-blanking' => false
+ ],
+ [
+ 'mw-replace',
+ 'mw-changed-redirect-target'
+ ]
+ ],
+
+ [
+ [
+ null,
+ false,
+ 'Lorem ipsum',
+ 'mw-translation'
+ ],
+ []
+ ],
+
+ [
+ [],
+ []
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider dataGetSoftwareTags
+ * @covers ChangeTags::getSoftwareTags
+ */
+ public function testGetSoftwareTags( $softwareTags, $expected ) {
+ $this->setMwGlobals( 'wgSoftwareTags', $softwareTags );
+
+ $actual = ChangeTags::getSoftwareTags();
+ // Order of tags in arrays is not important
+ sort( $expected );
+ sort( $actual );
+ $this->assertEquals( $expected, $actual );
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/collation/CollationFaTest.php b/www/wiki/tests/phpunit/includes/collation/CollationFaTest.php
index 53a4f7b7..f7455419 100644
--- a/www/wiki/tests/phpunit/includes/collation/CollationFaTest.php
+++ b/www/wiki/tests/phpunit/includes/collation/CollationFaTest.php
@@ -1,4 +1,8 @@
<?php
+
+/**
+ * @covers CollationFa
+ */
class CollationFaTest extends MediaWikiTestCase {
/*
@@ -9,9 +13,7 @@ class CollationFaTest extends MediaWikiTestCase {
public function setUp() {
parent::setUp();
- if ( !extension_loaded( 'intl' ) ) {
- $this->markTestSkipped( "PHP extension 'intl' is not loaded, skipping." );
- }
+ $this->checkPHPExtension( 'intl' );
}
/**
diff --git a/www/wiki/tests/phpunit/includes/collation/CollationTest.php b/www/wiki/tests/phpunit/includes/collation/CollationTest.php
index 25911a79..b92e651e 100644
--- a/www/wiki/tests/phpunit/includes/collation/CollationTest.php
+++ b/www/wiki/tests/phpunit/includes/collation/CollationTest.php
@@ -21,7 +21,7 @@ class CollationTest extends MediaWikiLangTestCase {
* code makes this assumption.
*
* @param string $lang Language code for collator
- * @param string $base Base string
+ * @param string $base
* @param string $extended String containing base as a prefix.
*
* @dataProvider prefixDataProvider
diff --git a/www/wiki/tests/phpunit/includes/collation/CustomUppercaseCollationTest.php b/www/wiki/tests/phpunit/includes/collation/CustomUppercaseCollationTest.php
index 5d5317be..f9e0bc9b 100644
--- a/www/wiki/tests/phpunit/includes/collation/CustomUppercaseCollationTest.php
+++ b/www/wiki/tests/phpunit/includes/collation/CustomUppercaseCollationTest.php
@@ -1,11 +1,15 @@
<?php
+/**
+ * @covers CustomUppercaseCollation
+ */
class CustomUppercaseCollationTest extends MediaWikiTestCase {
public function setUp() {
$this->collation = new CustomUppercaseCollation( [
'D',
'C',
+ 'Cs',
'B'
], Language::factory( 'en' ) );
@@ -31,6 +35,7 @@ class CustomUppercaseCollationTest extends MediaWikiTestCase {
[ '💩 ', 'C', 'Test relocated to end' ],
[ 'c', 'b', 'lowercase' ],
[ 'x', 'z', 'lowercase original' ],
+ [ 'Cz', 'Cs', 'digraphs' ],
[ 'C50D', 'C100', 'Numbers' ]
];
}
@@ -50,8 +55,14 @@ class CustomUppercaseCollationTest extends MediaWikiTestCase {
[ 'afdsa', 'A' ],
[ "\xF3\xB3\x80\x80Foo", 'D' ],
[ "\xF3\xB3\x80\x81Foo", 'C' ],
- [ "\xF3\xB3\x80\x82Foo", 'B' ],
- [ "\xF3\xB3\x80\x83Foo", "\xF3\xB3\x80\x83" ],
+ [ "\xF3\xB3\x80\x82Foo", 'Cs' ],
+ [ "\xF3\xB3\x80\x83Foo", 'B' ],
+ [ "\xF3\xB3\x80\x84Foo", "\xF3\xB3\x80\x84" ],
+ [ 'C', 'C' ],
+ [ 'Cz', 'C' ],
+ [ 'Cs', 'Cs' ],
+ [ 'CS', 'Cs' ],
+ [ 'cs', 'Cs' ],
];
}
}
diff --git a/www/wiki/tests/phpunit/includes/composer/ComposerVersionNormalizerTest.php b/www/wiki/tests/phpunit/includes/composer/ComposerVersionNormalizerTest.php
index 8a2ebaff..c5c0dc7d 100644
--- a/www/wiki/tests/phpunit/includes/composer/ComposerVersionNormalizerTest.php
+++ b/www/wiki/tests/phpunit/includes/composer/ComposerVersionNormalizerTest.php
@@ -7,7 +7,10 @@
*
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
-class ComposerVersionNormalizerTest extends PHPUnit_Framework_TestCase {
+class ComposerVersionNormalizerTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+ use PHPUnit4And6Compat;
/**
* @dataProvider nonStringProvider
@@ -15,7 +18,7 @@ class ComposerVersionNormalizerTest extends PHPUnit_Framework_TestCase {
public function testGivenNonString_normalizeThrowsInvalidArgumentException( $nonString ) {
$normalizer = new ComposerVersionNormalizer();
- $this->setExpectedException( 'InvalidArgumentException' );
+ $this->setExpectedException( InvalidArgumentException::class );
$normalizer->normalizeSuffix( $nonString );
}
diff --git a/www/wiki/tests/phpunit/includes/config/ConfigFactoryTest.php b/www/wiki/tests/phpunit/includes/config/ConfigFactoryTest.php
index 608d8d94..ea747afa 100644
--- a/www/wiki/tests/phpunit/includes/config/ConfigFactoryTest.php
+++ b/www/wiki/tests/phpunit/includes/config/ConfigFactoryTest.php
@@ -18,13 +18,22 @@ class ConfigFactoryTest extends MediaWikiTestCase {
*/
public function testRegisterInvalid() {
$factory = new ConfigFactory();
- $this->setExpectedException( 'InvalidArgumentException' );
+ $this->setExpectedException( InvalidArgumentException::class );
$factory->register( 'invalid', 'Invalid callback' );
}
/**
* @covers ConfigFactory::register
*/
+ public function testRegisterInvalidInstance() {
+ $factory = new ConfigFactory();
+ $this->setExpectedException( InvalidArgumentException::class );
+ $factory->register( 'invalidInstance', new stdClass );
+ }
+
+ /**
+ * @covers ConfigFactory::register
+ */
public function testRegisterInstance() {
$config = GlobalVarConfig::newInstance();
$factory = new ConfigFactory();
@@ -78,7 +87,7 @@ class ConfigFactoryTest extends MediaWikiTestCase {
$this->assertNotSame( $bar, $newBar, 'don\'t salvage if callbacks differ' );
// the new factory doesn't have quux defined, so the quux instance should not be salvaged
- $this->setExpectedException( 'ConfigException' );
+ $this->setExpectedException( ConfigException::class );
$newFactory->makeConfig( 'quux' );
}
@@ -101,7 +110,7 @@ class ConfigFactoryTest extends MediaWikiTestCase {
$factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
$conf = $factory->makeConfig( 'unittest' );
- $this->assertInstanceOf( 'Config', $conf );
+ $this->assertInstanceOf( Config::class, $conf );
$this->assertSame( $conf, $factory->makeConfig( 'unittest' ) );
}
@@ -122,7 +131,7 @@ class ConfigFactoryTest extends MediaWikiTestCase {
$factory = new ConfigFactory();
$factory->register( '*', 'GlobalVarConfig::newInstance' );
$conf = $factory->makeConfig( 'unittest' );
- $this->assertInstanceOf( 'Config', $conf );
+ $this->assertInstanceOf( Config::class, $conf );
}
/**
@@ -130,7 +139,7 @@ class ConfigFactoryTest extends MediaWikiTestCase {
*/
public function testMakeConfigWithNoBuilders() {
$factory = new ConfigFactory();
- $this->setExpectedException( 'ConfigException' );
+ $this->setExpectedException( ConfigException::class );
$factory->makeConfig( 'nobuilderregistered' );
}
@@ -142,7 +151,7 @@ class ConfigFactoryTest extends MediaWikiTestCase {
$factory->register( 'unittest', function () {
return true; // Not a Config object
} );
- $this->setExpectedException( 'UnexpectedValueException' );
+ $this->setExpectedException( UnexpectedValueException::class );
$factory->makeConfig( 'unittest' );
}
@@ -153,7 +162,7 @@ class ConfigFactoryTest extends MediaWikiTestCase {
// NOTE: the global config factory returned here has been overwritten
// for operation in test mode. It may not reflect LocalSettings.
$factory = MediaWikiServices::getInstance()->getConfigFactory();
- $this->assertInstanceOf( 'Config', $factory->makeConfig( 'main' ) );
+ $this->assertInstanceOf( Config::class, $factory->makeConfig( 'main' ) );
}
}
diff --git a/www/wiki/tests/phpunit/includes/config/EtcdConfigTest.php b/www/wiki/tests/phpunit/includes/config/EtcdConfigTest.php
index ebe19725..3eecf827 100644
--- a/www/wiki/tests/phpunit/includes/config/EtcdConfigTest.php
+++ b/www/wiki/tests/phpunit/includes/config/EtcdConfigTest.php
@@ -2,7 +2,10 @@
use Wikimedia\TestingAccessWrapper;
-class EtcConfigTest extends PHPUnit_Framework_TestCase {
+class EtcdConfigTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+ use PHPUnit4And6Compat;
private function createConfigMock( array $options = [] ) {
return $this->getMockBuilder( EtcdConfig::class )
@@ -15,14 +18,23 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
->getMock();
}
- private function createSimpleConfigMock( array $config ) {
+ private static function createEtcdResponse( array $response ) {
+ $baseResponse = [
+ 'config' => null,
+ 'error' => null,
+ 'retry' => false,
+ 'modifiedIndex' => 0,
+ ];
+ return array_merge( $baseResponse, $response );
+ }
+
+ private function createSimpleConfigMock( array $config, $index = 0 ) {
$mock = $this->createConfigMock();
$mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
- ->willReturn( [
- $config,
- null, // error
- false // retry?
- ] );
+ ->willReturn( self::createEtcdResponse( [
+ 'config' => $config,
+ 'modifiedIndex' => $index,
+ ] ) );
return $mock;
}
@@ -69,6 +81,17 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
}
/**
+ * @covers EtcdConfig::getModifiedIndex
+ */
+ public function testGetModifiedIndex() {
+ $config = $this->createSimpleConfigMock(
+ [ 'some' => 'value' ],
+ 123
+ );
+ $this->assertSame( 123, $config->getModifiedIndex() );
+ }
+
+ /**
* @covers EtcdConfig::__construct
*/
public function testConstructCacheObj() {
@@ -79,6 +102,7 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
->willReturn( [
'config' => [ 'known' => 'from-cache' ],
'expires' => INF,
+ 'modifiedIndex' => 123
] );
$config = $this->createConfigMock( [ 'cache' => $cache ] );
@@ -93,11 +117,8 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
'class' => HashBagOStuff::class
] ] );
$config->expects( $this->once() )->method( 'fetchAllFromEtcd' )
- ->willReturn( [
- [ 'known' => 'from-fetch' ],
- null, // error
- false // retry?
- ] );
+ ->willReturn( self::createEtcdResponse(
+ [ 'config' => [ 'known' => 'from-fetch' ], ] ) );
$this->assertSame( 'from-fetch', $config->get( 'known' ) );
}
@@ -164,7 +185,8 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
'cache' => $cache,
] );
$mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
- ->willReturn( [ [ 'known' => 'from-fetch' ], null, false ] );
+ ->willReturn(
+ self::createEtcdResponse( [ 'config' => [ 'known' => 'from-fetch' ] ] ) );
$this->assertSame( 'from-fetch', $mock->get( 'known' ) );
}
@@ -189,7 +211,7 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
'cache' => $cache,
] );
$mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
- ->willReturn( [ null, 'Fake error', false ] );
+ ->willReturn( self::createEtcdResponse( [ 'error' => 'Fake error', ] ) );
$this->setExpectedException( ConfigException::class );
$mock->get( 'key' );
@@ -211,6 +233,7 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
[
'config' => [ 'known' => 'from-cache' ],
'expires' => INF,
+ 'modifiedIndex' => 123
]
) );
// .. misses lock
@@ -239,6 +262,7 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
->willReturn( [
'config' => [ 'known' => 'from-cache' ],
'expires' => INF,
+ 'modifiedIndex' => 0,
] );
$cache->expects( $this->never() )->method( 'lock' );
@@ -264,6 +288,7 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
->willReturn( [
'config' => [ 'known' => 'from-cache' ],
'expires' => INF,
+ 'modifiedIndex' => 0,
] );
$cache->expects( $this->never() )->method( 'lock' );
@@ -290,6 +315,7 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
[
'config' => [ 'known' => 'from-cache-expired' ],
'expires' => -INF,
+ 'modifiedIndex' => 0,
]
);
// .. gets lock
@@ -301,7 +327,7 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
'cache' => $cache,
] );
$mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
- ->willReturn( [ [ 'known' => 'from-fetch' ], null, false ] );
+ ->willReturn( self::createEtcdResponse( [ 'config' => [ 'known' => 'from-fetch' ] ] ) );
$this->assertSame( 'from-fetch', $mock->get( 'known' ) );
}
@@ -319,6 +345,7 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
[
'config' => [ 'known' => 'from-cache-expired' ],
'expires' => -INF,
+ 'modifiedIndex' => 0,
]
);
// .. gets lock
@@ -330,7 +357,7 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
'cache' => $cache,
] );
$mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
- ->willReturn( [ null, 'Fake failure', true ] );
+ ->willReturn( self::createEtcdResponse( [ 'error' => 'Fake failure', 'retry' => true ] ) );
$this->assertSame( 'from-cache-expired', $mock->get( 'known' ) );
}
@@ -348,6 +375,7 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
->willReturn( [
'config' => [ 'known' => 'from-cache-expired' ],
'expires' => -INF,
+ 'modifiedIndex' => 0,
] );
// .. misses lock
$cache->expects( $this->once() )->method( 'lock' )
@@ -372,16 +400,16 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
'body' => json_encode( [ 'node' => [ 'nodes' => [
[
'key' => '/example/foo',
- 'value' => json_encode( [ 'val' => true ] )
+ 'value' => json_encode( [ 'val' => true ] ),
+ 'modifiedIndex' => 123
],
] ] ] ),
'error' => '',
],
- 'expect' => [
- [ 'foo' => true ], // data
- null,
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'config' => [ 'foo' => true ], // data
+ 'modifiedIndex' => 123
+ ] ),
],
'200 OK - Empty dir' => [
'http' => [
@@ -391,25 +419,27 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
'body' => json_encode( [ 'node' => [ 'nodes' => [
[
'key' => '/example/foo',
- 'value' => json_encode( [ 'val' => true ] )
+ 'value' => json_encode( [ 'val' => true ] ),
+ 'modifiedIndex' => 123
],
[
'key' => '/example/sub',
'dir' => true,
+ 'modifiedIndex' => 234,
'nodes' => [],
],
[
'key' => '/example/bar',
- 'value' => json_encode( [ 'val' => false ] )
+ 'value' => json_encode( [ 'val' => false ] ),
+ 'modifiedIndex' => 125
],
] ] ] ),
'error' => '',
],
- 'expect' => [
- [ 'foo' => true, 'bar' => false ], // data
- null,
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'config' => [ 'foo' => true, 'bar' => false ], // data
+ 'modifiedIndex' => 125 // largest modified index
+ ] ),
],
'200 OK - Recursive' => [
'http' => [
@@ -420,25 +450,28 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
[
'key' => '/example/a',
'dir' => true,
+ 'modifiedIndex' => 124,
'nodes' => [
[
'key' => 'b',
'value' => json_encode( [ 'val' => true ] ),
+ 'modifiedIndex' => 123,
+
],
[
'key' => 'c',
'value' => json_encode( [ 'val' => false ] ),
+ 'modifiedIndex' => 123,
],
],
],
] ] ] ),
'error' => '',
],
- 'expect' => [
- [ 'a/b' => true, 'a/c' => false ], // data
- null,
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'config' => [ 'a/b' => true, 'a/c' => false ], // data
+ 'modifiedIndex' => 123 // largest modified index
+ ] ),
],
'200 OK - Missing nodes at second level' => [
'http' => [
@@ -449,15 +482,32 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
[
'key' => '/example/a',
'dir' => true,
+ 'modifiedIndex' => 0,
],
] ] ] ),
'error' => '',
],
- 'expect' => [
- null,
- "Unexpected JSON response in dir 'a'; missing 'nodes' list.",
- false // retry
+ 'expect' => self::createEtcdResponse( [
+ 'error' => "Unexpected JSON response in dir 'a'; missing 'nodes' list.",
+ ] ),
+ ],
+ '200 OK - Directory with non-array "nodes" key' => [
+ 'http' => [
+ 'code' => 200,
+ 'reason' => 'OK',
+ 'headers' => [],
+ 'body' => json_encode( [ 'node' => [ 'nodes' => [
+ [
+ 'key' => '/example/a',
+ 'dir' => true,
+ 'nodes' => 'not an array'
+ ],
+ ] ] ] ),
+ 'error' => '',
],
+ 'expect' => self::createEtcdResponse( [
+ 'error' => "Unexpected JSON response in dir 'a'; 'nodes' is not an array.",
+ ] ),
],
'200 OK - Correctly encoded garbage response' => [
'http' => [
@@ -467,11 +517,9 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
'body' => json_encode( [ 'foo' => 'bar' ] ),
'error' => '',
],
- 'expect' => [
- null,
- "Unexpected JSON response: Missing or invalid node at top level.",
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'error' => "Unexpected JSON response: Missing or invalid node at top level.",
+ ] ),
],
'200 OK - Bad value' => [
'http' => [
@@ -481,30 +529,27 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
'body' => json_encode( [ 'node' => [ 'nodes' => [
[
'key' => '/example/foo',
- 'value' => ';"broken{value'
+ 'value' => ';"broken{value',
+ 'modifiedIndex' => 123,
]
] ] ] ),
'error' => '',
],
- 'expect' => [
- null, // data
- "Failed to parse value for 'foo'.",
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'error' => "Failed to parse value for 'foo'.",
+ ] ),
],
'200 OK - Empty node list' => [
'http' => [
'code' => 200,
'reason' => 'OK',
'headers' => [],
- 'body' => '{"node":{"nodes":[]}}',
+ 'body' => '{"node":{"nodes":[], "modifiedIndex": 12 }}',
'error' => '',
],
- 'expect' => [
- [], // data
- null,
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'config' => [], // data
+ ] ),
],
'200 OK - Invalid JSON' => [
'http' => [
@@ -514,11 +559,9 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
'body' => '',
'error' => '(curl error: no status set)',
],
- 'expect' => [
- null, // data
- "Error unserializing JSON response.",
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'error' => "Error unserializing JSON response.",
+ ] ),
],
'404 Not Found' => [
'http' => [
@@ -528,11 +571,9 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
'body' => '',
'error' => '',
],
- 'expect' => [
- null, // data
- 'HTTP 404 (Not Found)',
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'error' => 'HTTP 404 (Not Found)',
+ ] ),
],
'400 Bad Request - custom error' => [
'http' => [
@@ -542,11 +583,10 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
'body' => '',
'error' => 'No good reason',
],
- 'expect' => [
- null, // data
- 'No good reason',
- true // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'error' => 'No good reason',
+ 'retry' => true, // retry
+ ] ),
],
];
}
diff --git a/www/wiki/tests/phpunit/includes/config/GlobalVarConfigTest.php b/www/wiki/tests/phpunit/includes/config/GlobalVarConfigTest.php
index a6b220d6..db5f73d4 100644
--- a/www/wiki/tests/phpunit/includes/config/GlobalVarConfigTest.php
+++ b/www/wiki/tests/phpunit/includes/config/GlobalVarConfigTest.php
@@ -7,7 +7,7 @@ class GlobalVarConfigTest extends MediaWikiTestCase {
*/
public function testNewInstance() {
$config = GlobalVarConfig::newInstance();
- $this->assertInstanceOf( 'GlobalVarConfig', $config );
+ $this->assertInstanceOf( GlobalVarConfig::class, $config );
$this->maybeStashGlobal( 'wgBaz' );
$GLOBALS['wgBaz'] = 'somevalue';
// Check prefix is set to 'wg'
@@ -24,7 +24,7 @@ class GlobalVarConfigTest extends MediaWikiTestCase {
$this->maybeStashGlobal( $var );
$GLOBALS[$var] = $rand;
$config = new GlobalVarConfig( $prefix );
- $this->assertInstanceOf( 'GlobalVarConfig', $config );
+ $this->assertInstanceOf( GlobalVarConfig::class, $config );
$this->assertEquals( $rand, $config->get( 'GlobalVarConfigTest' ) );
}
@@ -83,7 +83,7 @@ class GlobalVarConfigTest extends MediaWikiTestCase {
public function testGet( $name, $prefix, $expected ) {
$config = new GlobalVarConfig( $prefix );
if ( $expected === false ) {
- $this->setExpectedException( 'ConfigException', 'GlobalVarConfig::get: undefined option:' );
+ $this->setExpectedException( ConfigException::class, 'GlobalVarConfig::get: undefined option:' );
}
$this->assertEquals( $config->get( $name ), $expected );
}
diff --git a/www/wiki/tests/phpunit/includes/config/HashConfigTest.php b/www/wiki/tests/phpunit/includes/config/HashConfigTest.php
index 19b412d2..bac8311c 100644
--- a/www/wiki/tests/phpunit/includes/config/HashConfigTest.php
+++ b/www/wiki/tests/phpunit/includes/config/HashConfigTest.php
@@ -7,7 +7,7 @@ class HashConfigTest extends MediaWikiTestCase {
*/
public function testNewInstance() {
$conf = HashConfig::newInstance();
- $this->assertInstanceOf( 'HashConfig', $conf );
+ $this->assertInstanceOf( HashConfig::class, $conf );
}
/**
@@ -15,7 +15,7 @@ class HashConfigTest extends MediaWikiTestCase {
*/
public function testConstructor() {
$conf = new HashConfig();
- $this->assertInstanceOf( 'HashConfig', $conf );
+ $this->assertInstanceOf( HashConfig::class, $conf );
// Test passing arguments to the constructor
$conf2 = new HashConfig( [
@@ -32,7 +32,7 @@ class HashConfigTest extends MediaWikiTestCase {
'one' => '1',
] );
$this->assertEquals( '1', $conf->get( 'one' ) );
- $this->setExpectedException( 'ConfigException', 'HashConfig::get: undefined option' );
+ $this->setExpectedException( ConfigException::class, 'HashConfig::get: undefined option' );
$conf->get( 'two' );
}
diff --git a/www/wiki/tests/phpunit/includes/config/MultiConfigTest.php b/www/wiki/tests/phpunit/includes/config/MultiConfigTest.php
index d1eb5102..fc283951 100644
--- a/www/wiki/tests/phpunit/includes/config/MultiConfigTest.php
+++ b/www/wiki/tests/phpunit/includes/config/MultiConfigTest.php
@@ -17,7 +17,7 @@ class MultiConfigTest extends MediaWikiTestCase {
$this->assertEquals( 'bar', $multi->get( 'foo' ) );
$this->assertEquals( 'foo', $multi->get( 'bar' ) );
- $this->setExpectedException( 'ConfigException', 'MultiConfig::get: undefined option:' );
+ $this->setExpectedException( ConfigException::class, 'MultiConfig::get: undefined option:' );
$multi->get( 'notset' );
}
diff --git a/www/wiki/tests/phpunit/includes/content/ContentHandlerTest.php b/www/wiki/tests/phpunit/includes/content/ContentHandlerTest.php
index ee79ffa4..309b7b11 100644
--- a/www/wiki/tests/phpunit/includes/content/ContentHandlerTest.php
+++ b/www/wiki/tests/phpunit/includes/content/ContentHandlerTest.php
@@ -22,12 +22,12 @@ class ContentHandlerTest extends MediaWikiTestCase {
12312 => 'testing',
],
'wgContentHandlers' => [
- CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler',
- CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler',
- CONTENT_MODEL_JSON => 'JsonContentHandler',
- CONTENT_MODEL_CSS => 'CssContentHandler',
- CONTENT_MODEL_TEXT => 'TextContentHandler',
- 'testing' => 'DummyContentHandlerForTesting',
+ CONTENT_MODEL_WIKITEXT => WikitextContentHandler::class,
+ CONTENT_MODEL_JAVASCRIPT => JavaScriptContentHandler::class,
+ CONTENT_MODEL_JSON => JsonContentHandler::class,
+ CONTENT_MODEL_CSS => CssContentHandler::class,
+ CONTENT_MODEL_TEXT => TextContentHandler::class,
+ 'testing' => DummyContentHandlerForTesting::class,
'testing-callbacks' => function ( $modelId ) {
return new DummyContentHandlerForTesting( $modelId );
}
@@ -35,7 +35,7 @@ class ContentHandlerTest extends MediaWikiTestCase {
] );
// Reset namespace cache
- MWNamespace::getCanonicalNamespaces( true );
+ MWNamespace::clearCaches();
$wgContLang->resetNamespaces();
// And LinkCache
MediaWikiServices::getInstance()->resetServiceForTesting( 'LinkCache' );
@@ -45,7 +45,7 @@ class ContentHandlerTest extends MediaWikiTestCase {
global $wgContLang;
// Reset namespace cache
- MWNamespace::getCanonicalNamespaces( true );
+ MWNamespace::clearCaches();
$wgContLang->resetNamespaces();
// And LinkCache
MediaWikiServices::getInstance()->resetServiceForTesting( 'LinkCache' );
@@ -248,10 +248,6 @@ class ContentHandlerTest extends MediaWikiTestCase {
$this->assertNull( $text );
}
- /*
- public static function makeContent( $text, Title $title, $modelId = null, $format = null ) {}
- */
-
public static function dataMakeContent() {
return [
[ 'hallo', 'Help:Test', null, null, CONTENT_MODEL_WIKITEXT, 'hallo', false ],
@@ -332,7 +328,9 @@ class ContentHandlerTest extends MediaWikiTestCase {
}
}
- /*
+ /**
+ * @covers ContentHandler::getAutosummary
+ *
* Test if we become a "Created blank page" summary from getAutoSummary if no Content added to
* page.
*/
@@ -342,26 +340,43 @@ class ContentHandlerTest extends MediaWikiTestCase {
$content = new DummyContentHandlerForTesting( CONTENT_MODEL_WIKITEXT );
$title = Title::newFromText( 'Help:Test' );
// Create a new content object with no content
- $newContent = ContentHandler::makeContent( '', $title, null, null, CONTENT_MODEL_WIKITEXT );
+ $newContent = ContentHandler::makeContent( '', $title, CONTENT_MODEL_WIKITEXT, null );
// first check, if we become a blank page created summary with the right bitmask
$autoSummary = $content->getAutosummary( null, $newContent, 97 );
- $this->assertEquals( $autoSummary, 'Created blank page' );
+ $this->assertEquals( $autoSummary,
+ wfMessage( 'autosumm-newblank' )->inContentLanguage()->text() );
// now check, what we become with another bitmask
$autoSummary = $content->getAutosummary( null, $newContent, 92 );
$this->assertEquals( $autoSummary, '' );
}
- /*
- public function testSupportsSections() {
- $this->markTestIncomplete( "not yet implemented" );
+ /**
+ * Test software tag that is added when content model of the page changes
+ * @covers ContentHandler::getChangeTag
+ */
+ public function testGetChangeTag() {
+ $this->setMwGlobals( 'wgSoftwareTags', [ 'mw-contentmodelchange' => true ] );
+ $wikitextContentHandler = new DummyContentHandlerForTesting( CONTENT_MODEL_WIKITEXT );
+ // Create old content object with javascript content model
+ $oldContent = ContentHandler::makeContent( '', null, CONTENT_MODEL_JAVASCRIPT, null );
+ // Create new content object with wikitext content model
+ $newContent = ContentHandler::makeContent( '', null, CONTENT_MODEL_WIKITEXT, null );
+ // Get the tag for this edit
+ $tag = $wikitextContentHandler->getChangeTag( $oldContent, $newContent, EDIT_UPDATE );
+ $this->assertSame( $tag, 'mw-contentmodelchange' );
}
- */
+ /**
+ * @covers ContentHandler::supportsCategories
+ */
public function testSupportsCategories() {
$handler = new DummyContentHandlerForTesting( CONTENT_MODEL_WIKITEXT );
$this->assertTrue( $handler->supportsCategories(), 'content model supports categories' );
}
+ /**
+ * @covers ContentHandler::supportsDirectEditing
+ */
public function testSupportsDirectEditing() {
$handler = new DummyContentHandlerForTesting( CONTENT_MODEL_JSON );
$this->assertFalse( $handler->supportsDirectEditing(), 'direct editing is not supported' );
@@ -379,17 +394,18 @@ class ContentHandlerTest extends MediaWikiTestCase {
public function provideGetModelForID() {
return [
- [ CONTENT_MODEL_WIKITEXT, 'WikitextContentHandler' ],
- [ CONTENT_MODEL_JAVASCRIPT, 'JavaScriptContentHandler' ],
- [ CONTENT_MODEL_JSON, 'JsonContentHandler' ],
- [ CONTENT_MODEL_CSS, 'CssContentHandler' ],
- [ CONTENT_MODEL_TEXT, 'TextContentHandler' ],
- [ 'testing', 'DummyContentHandlerForTesting' ],
- [ 'testing-callbacks', 'DummyContentHandlerForTesting' ],
+ [ CONTENT_MODEL_WIKITEXT, WikitextContentHandler::class ],
+ [ CONTENT_MODEL_JAVASCRIPT, JavaScriptContentHandler::class ],
+ [ CONTENT_MODEL_JSON, JsonContentHandler::class ],
+ [ CONTENT_MODEL_CSS, CssContentHandler::class ],
+ [ CONTENT_MODEL_TEXT, TextContentHandler::class ],
+ [ 'testing', DummyContentHandlerForTesting::class ],
+ [ 'testing-callbacks', DummyContentHandlerForTesting::class ],
];
}
/**
+ * @covers ContentHandler::getForModelID
* @dataProvider provideGetModelForID
*/
public function testGetModelForID( $modelId, $handlerClass ) {
@@ -398,6 +414,9 @@ class ContentHandlerTest extends MediaWikiTestCase {
$this->assertInstanceOf( $handlerClass, $handler );
}
+ /**
+ * @covers ContentHandler::getFieldsForSearchIndex
+ */
public function testGetFieldsForSearchIndex() {
$searchEngine = $this->newSearchEngine();
@@ -413,7 +432,7 @@ class ContentHandlerTest extends MediaWikiTestCase {
}
private function newSearchEngine() {
- $searchEngine = $this->getMockBuilder( 'SearchEngine' )
+ $searchEngine = $this->getMockBuilder( SearchEngine::class )
->getMock();
$searchEngine->expects( $this->any() )
@@ -429,7 +448,7 @@ class ContentHandlerTest extends MediaWikiTestCase {
* @covers ContentHandler::getDataForSearchIndex
*/
public function testDataIndexFields() {
- $mockEngine = $this->createMock( 'SearchEngine' );
+ $mockEngine = $this->createMock( SearchEngine::class );
$title = Title::newFromText( 'Not_Main_Page', NS_MAIN );
$page = new WikiPage( $title );
diff --git a/www/wiki/tests/phpunit/includes/content/CssContentHandlerTest.php b/www/wiki/tests/phpunit/includes/content/CssContentHandlerTest.php
index d92fb8fc..7ca1afce 100644
--- a/www/wiki/tests/phpunit/includes/content/CssContentHandlerTest.php
+++ b/www/wiki/tests/phpunit/includes/content/CssContentHandlerTest.php
@@ -13,7 +13,7 @@ class CssContentHandlerTest extends MediaWikiLangTestCase {
] );
$ch = new CssContentHandler();
$content = $ch->makeRedirectContent( Title::newFromText( $title ) );
- $this->assertInstanceOf( 'CssContent', $content );
+ $this->assertInstanceOf( CssContent::class, $content );
$this->assertEquals( $expected, $content->serialize( CONTENT_FORMAT_CSS ) );
}
@@ -21,7 +21,7 @@ class CssContentHandlerTest extends MediaWikiLangTestCase {
* Keep this in sync with CssContentTest::provideGetRedirectTarget()
*/
public static function provideMakeRedirectContent() {
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return [
[
'MediaWiki:MonoBook.css',
@@ -36,6 +36,6 @@ class CssContentHandlerTest extends MediaWikiLangTestCase {
"/* #REDIRECT */@import url(//example.org/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);"
],
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
}
diff --git a/www/wiki/tests/phpunit/includes/content/CssContentTest.php b/www/wiki/tests/phpunit/includes/content/CssContentTest.php
index d2078d7a..f5cc05e0 100644
--- a/www/wiki/tests/phpunit/includes/content/CssContentTest.php
+++ b/www/wiki/tests/phpunit/includes/content/CssContentTest.php
@@ -83,6 +83,7 @@ class CssContentTest extends JavaScriptContentTest {
}
/**
+ * @covers CssContent::getRedirectTarget
* @dataProvider provideGetRedirectTarget
*/
public function testGetRedirectTarget( $title, $text ) {
@@ -100,7 +101,7 @@ class CssContentTest extends JavaScriptContentTest {
* Keep this in sync with CssContentHandlerTest::provideMakeRedirectContent()
*/
public static function provideGetRedirectTarget() {
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return [
[ 'MediaWiki:MonoBook.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=MediaWiki:MonoBook.css&action=raw&ctype=text/css);" ],
[ 'User:FooBar/common.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=User:FooBar/common.css&action=raw&ctype=text/css);" ],
@@ -110,7 +111,7 @@ class CssContentTest extends JavaScriptContentTest {
# Wrong domain
[ null, "/* #REDIRECT */@import url(//example.com/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);" ],
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
public static function dataEquals() {
diff --git a/www/wiki/tests/phpunit/includes/content/FileContentHandlerTest.php b/www/wiki/tests/phpunit/includes/content/FileContentHandlerTest.php
index 65efcc9e..9149fc4f 100644
--- a/www/wiki/tests/phpunit/includes/content/FileContentHandlerTest.php
+++ b/www/wiki/tests/phpunit/includes/content/FileContentHandlerTest.php
@@ -2,6 +2,8 @@
/**
* @group ContentHandler
+ *
+ * @covers FileContentHandler
*/
class FileContentHandlerTest extends MediaWikiLangTestCase {
/**
@@ -16,13 +18,13 @@ class FileContentHandlerTest extends MediaWikiLangTestCase {
}
public function testIndexMapping() {
- $mockEngine = $this->createMock( 'SearchEngine' );
+ $mockEngine = $this->createMock( SearchEngine::class );
$mockEngine->expects( $this->atLeastOnce() )
->method( 'makeSearchFieldMapping' )
->willReturnCallback( function ( $name, $type ) {
$mockField =
- $this->getMockBuilder( 'SearchIndexFieldDefinition' )
+ $this->getMockBuilder( SearchIndexFieldDefinition::class )
->setMethods( [ 'getMapping' ] )
->setConstructorArgs( [ $name, $type ] )
->getMock();
@@ -41,7 +43,7 @@ class FileContentHandlerTest extends MediaWikiLangTestCase {
'file_text' => 1,
];
foreach ( $map as $name => $field ) {
- $this->assertInstanceOf( 'SearchIndexField', $field );
+ $this->assertInstanceOf( SearchIndexField::class, $field );
$this->assertEquals( $name, $field->getName() );
unset( $expect[$name] );
}
diff --git a/www/wiki/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php b/www/wiki/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php
index d2296239..b5e3ab4a 100644
--- a/www/wiki/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php
+++ b/www/wiki/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php
@@ -13,7 +13,7 @@ class JavaScriptContentHandlerTest extends MediaWikiLangTestCase {
] );
$ch = new JavaScriptContentHandler();
$content = $ch->makeRedirectContent( Title::newFromText( $title ) );
- $this->assertInstanceOf( 'JavaScriptContent', $content );
+ $this->assertInstanceOf( JavaScriptContent::class, $content );
$this->assertEquals( $expected, $content->serialize( CONTENT_FORMAT_JAVASCRIPT ) );
}
@@ -21,7 +21,7 @@ class JavaScriptContentHandlerTest extends MediaWikiLangTestCase {
* Keep this in sync with JavaScriptContentTest::provideGetRedirectTarget()
*/
public static function provideMakeRedirectContent() {
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return [
[
'MediaWiki:MonoBook.js',
@@ -36,6 +36,6 @@ class JavaScriptContentHandlerTest extends MediaWikiLangTestCase {
'/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=Gadget:FooBaz.js\u0026action=raw\u0026ctype=text/javascript");'
],
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
}
diff --git a/www/wiki/tests/phpunit/includes/content/JavaScriptContentTest.php b/www/wiki/tests/phpunit/includes/content/JavaScriptContentTest.php
index 1c746bcd..823be6f7 100644
--- a/www/wiki/tests/phpunit/includes/content/JavaScriptContentTest.php
+++ b/www/wiki/tests/phpunit/includes/content/JavaScriptContentTest.php
@@ -155,16 +155,6 @@ class JavaScriptContentTest extends TextContentTest {
],
[ 'Foo',
null,
- 'comma',
- false
- ],
- [ 'Foo, bar',
- null,
- 'comma',
- false
- ],
- [ 'Foo',
- null,
'link',
false
],
@@ -190,11 +180,6 @@ class JavaScriptContentTest extends TextContentTest {
],
[ '#REDIRECT [[bar]]',
true,
- 'comma',
- false
- ],
- [ '#REDIRECT [[bar]]',
- true,
'link',
false
],
@@ -251,19 +236,18 @@ class JavaScriptContentTest extends TextContentTest {
}
public static function provideUpdateRedirect() {
+ // phpcs:disable Generic.Files.LineLength
return [
[
'#REDIRECT [[Someplace]]',
'#REDIRECT [[Someplace]]',
],
-
- // @codingStandardsIgnoreStart Generic.Files.LineLength
[
'/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=MediaWiki:MonoBook.js\u0026action=raw\u0026ctype=text/javascript");',
'/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=TestUpdateRedirect_target\u0026action=raw\u0026ctype=text/javascript");'
]
- // @codingStandardsIgnoreEnd
];
+ // phpcs:enable
}
/**
@@ -294,6 +278,7 @@ class JavaScriptContentTest extends TextContentTest {
}
/**
+ * @covers JavaScriptContent::getRedirectTarget
* @dataProvider provideGetRedirectTarget
*/
public function testGetRedirectTarget( $title, $text ) {
@@ -312,7 +297,7 @@ class JavaScriptContentTest extends TextContentTest {
* Keep this in sync with JavaScriptContentHandlerTest::provideMakeRedirectContent()
*/
public static function provideGetRedirectTarget() {
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return [
[
'MediaWiki:MonoBook.js',
@@ -337,6 +322,6 @@ class JavaScriptContentTest extends TextContentTest {
'/* #REDIRECT */mw.loader.load("//example.com/w/index.php?title=MediaWiki:OtherWiki.js\u0026action=raw\u0026ctype=text/javascript");'
],
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
}
diff --git a/www/wiki/tests/phpunit/includes/content/JsonContentTest.php b/www/wiki/tests/phpunit/includes/content/JsonContentTest.php
index de8e371e..7cddbad2 100644
--- a/www/wiki/tests/phpunit/includes/content/JsonContentTest.php
+++ b/www/wiki/tests/phpunit/includes/content/JsonContentTest.php
@@ -82,18 +82,18 @@ class JsonContentTest extends MediaWikiLangTestCase {
}
private function getMockTitle() {
- return $this->getMockBuilder( 'Title' )
+ return $this->getMockBuilder( Title::class )
->disableOriginalConstructor()
->getMock();
}
private function getMockUser() {
- return $this->getMockBuilder( 'User' )
+ return $this->getMockBuilder( User::class )
->disableOriginalConstructor()
->getMock();
}
private function getMockParserOptions() {
- return $this->getMockBuilder( 'ParserOptions' )
+ return $this->getMockBuilder( ParserOptions::class )
->disableOriginalConstructor()
->getMock();
}
@@ -146,7 +146,7 @@ class JsonContentTest extends MediaWikiLangTestCase {
public function testFillParserOutput( $data, $expected ) {
$obj = new JsonContent( FormatJson::encode( $data ) );
$parserOutput = $obj->getParserOutput( $this->getMockTitle(), null, null, true );
- $this->assertInstanceOf( 'ParserOutput', $parserOutput );
+ $this->assertInstanceOf( ParserOutput::class, $parserOutput );
$this->assertEquals( $expected, $parserOutput->getText() );
}
}
diff --git a/www/wiki/tests/phpunit/includes/content/TextContentHandlerTest.php b/www/wiki/tests/phpunit/includes/content/TextContentHandlerTest.php
index 7d9f74ec..6d0a3d5c 100644
--- a/www/wiki/tests/phpunit/includes/content/TextContentHandlerTest.php
+++ b/www/wiki/tests/phpunit/includes/content/TextContentHandlerTest.php
@@ -4,6 +4,9 @@
* @group ContentHandler
*/
class TextContentHandlerTest extends MediaWikiLangTestCase {
+ /**
+ * @covers TextContentHandler::supportsDirectEditing
+ */
public function testSupportsDirectEditing() {
$handler = new TextContentHandler();
$this->assertTrue( $handler->supportsDirectEditing(), 'direct editing is supported' );
@@ -16,13 +19,13 @@ class TextContentHandlerTest extends MediaWikiLangTestCase {
public function testFieldsForIndex() {
$handler = new TextContentHandler();
- $mockEngine = $this->createMock( 'SearchEngine' );
+ $mockEngine = $this->createMock( SearchEngine::class );
$mockEngine->expects( $this->atLeastOnce() )
->method( 'makeSearchFieldMapping' )
->willReturnCallback( function ( $name, $type ) {
$mockField =
- $this->getMockBuilder( 'SearchIndexFieldDefinition' )
+ $this->getMockBuilder( SearchIndexFieldDefinition::class )
->setConstructorArgs( [ $name, $type ] )
->getMock();
$mockField->expects( $this->atLeastOnce() )->method( 'getMapping' )->willReturn( [
@@ -39,7 +42,7 @@ class TextContentHandlerTest extends MediaWikiLangTestCase {
$fields = $handler->getFieldsForSearchIndex( $mockEngine );
$mappedFields = [];
foreach ( $fields as $name => $field ) {
- $this->assertInstanceOf( 'SearchIndexField', $field );
+ $this->assertInstanceOf( SearchIndexField::class, $field );
/**
* @var $field SearchIndexField
*/
diff --git a/www/wiki/tests/phpunit/includes/content/TextContentTest.php b/www/wiki/tests/phpunit/includes/content/TextContentTest.php
index b290f8f2..406bc96b 100644
--- a/www/wiki/tests/phpunit/includes/content/TextContentTest.php
+++ b/www/wiki/tests/phpunit/includes/content/TextContentTest.php
@@ -197,22 +197,11 @@ class TextContentTest extends MediaWikiLangTestCase {
'any',
true
],
- [ 'Foo',
- null,
- 'comma',
- false
- ],
- [ 'Foo, bar',
- null,
- 'comma',
- false
- ],
];
}
/**
* @dataProvider dataIsCountable
- * @group Database
* @covers TextContent::isCountable
*/
public function testIsCountable( $text, $hasLinks, $mode, $expected ) {
@@ -455,7 +444,7 @@ class TextContentTest extends MediaWikiLangTestCase {
if ( $expectedNative === false ) {
$this->assertFalse( $converted, "conversion to $model was expected to fail!" );
} else {
- $this->assertInstanceOf( 'Content', $converted );
+ $this->assertInstanceOf( Content::class, $converted );
$this->assertEquals( $expectedNative, $converted->getNativeData() );
}
}
diff --git a/www/wiki/tests/phpunit/includes/content/WikitextContentHandlerTest.php b/www/wiki/tests/phpunit/includes/content/WikitextContentHandlerTest.php
index 290b11ab..59984d85 100644
--- a/www/wiki/tests/phpunit/includes/content/WikitextContentHandlerTest.php
+++ b/www/wiki/tests/phpunit/includes/content/WikitextContentHandlerTest.php
@@ -115,6 +115,9 @@ class WikitextContentHandlerTest extends MediaWikiLangTestCase {
$this->assertEquals( $supported, $this->handler->isSupportedFormat( $format ) );
}
+ /**
+ * @covers WikitextContentHandler::supportsDirectEditing
+ */
public function testSupportsDirectEditing() {
$handler = new WikiTextContentHandler();
$this->assertTrue( $handler->supportsDirectEditing(), 'direct editing is supported' );
@@ -186,6 +189,13 @@ class WikitextContentHandlerTest extends MediaWikiLangTestCase {
],
[
+ null,
+ '',
+ EDIT_NEW,
+ '/^Created blank page$/'
+ ],
+
+ [
'Hello there, world!',
'',
0,
@@ -227,25 +237,109 @@ class WikitextContentHandlerTest extends MediaWikiLangTestCase {
);
}
- /**
- * @todo Text case requires database, should be done by a test class in the Database group
- */
- /*
- public function testGetAutoDeleteReason( Title $title, &$hasHistory ) {}
- */
+ public static function dataGetChangeTag() {
+ return [
+ [
+ null,
+ '#REDIRECT [[Foo]]',
+ 0,
+ 'mw-new-redirect'
+ ],
+
+ [
+ 'Lorem ipsum dolor',
+ '#REDIRECT [[Foo]]',
+ 0,
+ 'mw-new-redirect'
+ ],
+
+ [
+ '#REDIRECT [[Foo]]',
+ 'Lorem ipsum dolor',
+ 0,
+ 'mw-removed-redirect'
+ ],
+
+ [
+ '#REDIRECT [[Foo]]',
+ '#REDIRECT [[Bar]]',
+ 0,
+ 'mw-changed-redirect-target'
+ ],
+
+ [
+ null,
+ 'Lorem ipsum dolor',
+ EDIT_NEW,
+ null // mw-newpage is not defined as a tag
+ ],
+
+ [
+ null,
+ '',
+ EDIT_NEW,
+ null // mw-newblank is not defined as a tag
+ ],
+
+ [
+ 'Lorem ipsum dolor',
+ '',
+ 0,
+ 'mw-blank'
+ ],
+
+ [
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
+ eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
+ voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
+ clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.',
+ 'Ipsum',
+ 0,
+ 'mw-replace'
+ ],
+
+ [
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
+ eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
+ voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
+ clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.',
+ 'Duis purus odio, rhoncus et finibus dapibus, facilisis ac urna. Pellentesque
+ arcu, tristique nec tempus nec, suscipit vel arcu. Sed non dolor nec ligula
+ congue tempor. Quisque pellentesque finibus orci a molestie. Nam maximus, purus
+ euismod finibus mollis, dui ante malesuada felis, dignissim rutrum diam sapien.',
+ 0,
+ null
+ ],
+ ];
+ }
/**
- * @todo Text case requires database, should be done by a test class in the Database group
+ * @dataProvider dataGetChangeTag
+ * @covers WikitextContentHandler::getChangeTag
*/
- /*
- public function testGetUndoContent( Revision $current, Revision $undo,
- Revision $undoafter = null
- ) {
+ public function testGetChangeTag( $old, $new, $flags, $expected ) {
+ $this->setMwGlobals( 'wgSoftwareTags', [
+ 'mw-new-redirect' => true,
+ 'mw-removed-redirect' => true,
+ 'mw-changed-redirect-target' => true,
+ 'mw-newpage' => true,
+ 'mw-newblank' => true,
+ 'mw-blank' => true,
+ 'mw-replace' => true,
+ ] );
+ $oldContent = is_null( $old ) ? null : new WikitextContent( $old );
+ $newContent = is_null( $new ) ? null : new WikitextContent( $new );
+
+ $tag = $this->handler->getChangeTag( $oldContent, $newContent, $flags );
+
+ $this->assertSame( $expected, $tag );
}
- */
+ /**
+ * @covers WikitextContentHandler::getDataForSearchIndex
+ */
public function testDataIndexFieldsFile() {
- $mockEngine = $this->createMock( 'SearchEngine' );
+ $mockEngine = $this->createMock( SearchEngine::class );
$title = Title::newFromText( 'Somefile.jpg', NS_FILE );
$page = new WikiPage( $title );
diff --git a/www/wiki/tests/phpunit/includes/content/WikitextContentTest.php b/www/wiki/tests/phpunit/includes/content/WikitextContentTest.php
index d0996e3c..1db6aab6 100644
--- a/www/wiki/tests/phpunit/includes/content/WikitextContentTest.php
+++ b/www/wiki/tests/phpunit/includes/content/WikitextContentTest.php
@@ -40,7 +40,7 @@ more stuff
[ "WikitextContentTest_testGetSecondaryDataUpdates_1",
CONTENT_MODEL_WIKITEXT, "hello ''world''\n",
[
- 'LinksUpdate' => [
+ LinksUpdate::class => [
'mRecursive' => true,
'mLinks' => []
]
@@ -49,7 +49,7 @@ more stuff
[ "WikitextContentTest_testGetSecondaryDataUpdates_2",
CONTENT_MODEL_WIKITEXT, "hello [[world test 21344]]\n",
[
- 'LinksUpdate' => [
+ LinksUpdate::class => [
'mRecursive' => true,
'mLinks' => [
[ 'World_test_21344' => 0 ]
@@ -268,16 +268,6 @@ just a test"
],
[ 'Foo',
null,
- 'comma',
- false
- ],
- [ 'Foo, bar',
- null,
- 'comma',
- true
- ],
- [ 'Foo',
- null,
'link',
false
],
@@ -303,11 +293,6 @@ just a test"
],
[ '#REDIRECT [[bar]]',
true,
- 'comma',
- false
- ],
- [ '#REDIRECT [[bar]]',
- true,
'link',
false
],
@@ -446,11 +431,11 @@ just a test"
return [
[ "WikitextContentTest_testGetSecondaryDataUpdates_1",
CONTENT_MODEL_WIKITEXT, "hello ''world''\n",
- [ 'LinksDeletionUpdate' => [] ]
+ [ LinksDeletionUpdate::class => [] ]
],
[ "WikitextContentTest_testGetSecondaryDataUpdates_2",
CONTENT_MODEL_WIKITEXT, "hello [[world test 21344]]\n",
- [ 'LinksDeletionUpdate' => [] ]
+ [ LinksDeletionUpdate::class => [] ]
],
// @todo more...?
];
diff --git a/www/wiki/tests/phpunit/includes/content/WikitextStructureTest.php b/www/wiki/tests/phpunit/includes/content/WikitextStructureTest.php
index f1b54f6a..88f4d8f7 100644
--- a/www/wiki/tests/phpunit/includes/content/WikitextStructureTest.php
+++ b/www/wiki/tests/phpunit/includes/content/WikitextStructureTest.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @covers WikiTextStructure
+ */
class WikitextStructureTest extends MediaWikiLangTestCase {
private function getMockTitle() {
@@ -101,7 +104,7 @@ END;
$this->assertEquals( "Opening text is opening.", $struct->getOpeningText() );
$this->assertEquals( "Opening text is opening. Then we got more text",
$struct->getMainText() );
- $this->assertEquals( [ "Header table row in table another row in table" ],
+ $this->assertEquals( [ "Header table row in table another row in table" ],
$struct->getAuxiliaryText() );
}
}
diff --git a/www/wiki/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php b/www/wiki/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php
deleted file mode 100644
index bb747c7c..00000000
--- a/www/wiki/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php
+++ /dev/null
@@ -1,364 +0,0 @@
-<?php
-/**
- * Holds tests for DatabaseMysqlBase MediaWiki class.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @author Antoine Musso
- * @author Bryan Davis
- * @copyright © 2013 Antoine Musso
- * @copyright © 2013 Bryan Davis
- * @copyright © 2013 Wikimedia Foundation Inc.
- */
-
-/**
- * Fake class around abstract class so we can call concrete methods.
- */
-class FakeDatabaseMysqlBase extends DatabaseMysqlBase {
- // From DatabaseBase
- function __construct() {
- }
-
- protected function closeConnection() {
- }
-
- protected function doQuery( $sql ) {
- }
-
- // From DatabaseMysql
- protected function mysqlConnect( $realServer ) {
- }
-
- protected function mysqlSetCharset( $charset ) {
- }
-
- protected function mysqlFreeResult( $res ) {
- }
-
- protected function mysqlFetchObject( $res ) {
- }
-
- protected function mysqlFetchArray( $res ) {
- }
-
- protected function mysqlNumRows( $res ) {
- }
-
- protected function mysqlNumFields( $res ) {
- }
-
- protected function mysqlFieldName( $res, $n ) {
- }
-
- protected function mysqlFieldType( $res, $n ) {
- }
-
- protected function mysqlDataSeek( $res, $row ) {
- }
-
- protected function mysqlError( $conn = null ) {
- }
-
- protected function mysqlFetchField( $res, $n ) {
- }
-
- protected function mysqlPing() {
- }
-
- protected function mysqlRealEscapeString( $s ) {
-
- }
-
- // From interface DatabaseType
- function insertId() {
- }
-
- function lastErrno() {
- }
-
- function affectedRows() {
- }
-
- function getServerVersion() {
- }
-}
-
-class DatabaseMysqlBaseTest extends MediaWikiTestCase {
- /**
- * @dataProvider provideDiapers
- * @covers DatabaseMysqlBase::addIdentifierQuotes
- */
- public function testAddIdentifierQuotes( $expected, $in ) {
- $db = new FakeDatabaseMysqlBase();
- $quoted = $db->addIdentifierQuotes( $in );
- $this->assertEquals( $expected, $quoted );
- }
-
- /**
- * Feeds testAddIdentifierQuotes
- *
- * Named per bug 20281 convention.
- */
- function provideDiapers() {
- return [
- // Format: expected, input
- [ '``', '' ],
-
- // Yeah I really hate loosely typed PHP idiocies nowadays
- [ '``', null ],
-
- // Dear codereviewer, guess what addIdentifierQuotes()
- // will return with thoses:
- [ '``', false ],
- [ '`1`', true ],
-
- // We never know what could happen
- [ '`0`', 0 ],
- [ '`1`', 1 ],
-
- // Whatchout! Should probably use something more meaningful
- [ "`'`", "'" ], # single quote
- [ '`"`', '"' ], # double quote
- [ '````', '`' ], # backtick
- [ '`’`', '’' ], # apostrophe (look at your encyclopedia)
-
- // sneaky NUL bytes are lurking everywhere
- [ '``', "\0" ],
- [ '`xyzzy`', "\0x\0y\0z\0z\0y\0" ],
-
- // unicode chars
- [
- self::createUnicodeString( '`\u0001a\uFFFFb`' ),
- self::createUnicodeString( '\u0001a\uFFFFb' )
- ],
- [
- self::createUnicodeString( '`\u0001\uFFFF`' ),
- self::createUnicodeString( '\u0001\u0000\uFFFF\u0000' )
- ],
- [ '`☃`', '☃' ],
- [ '`メインページ`', 'メインページ' ],
- [ '`Басты_бет`', 'Басты_бет' ],
-
- // Real world:
- [ '`Alix`', 'Alix' ], # while( ! $recovered ) { sleep(); }
- [ '`Backtick: ```', 'Backtick: `' ],
- [ '`This is a test`', 'This is a test' ],
- ];
- }
-
- private static function createUnicodeString( $str ) {
- return json_decode( '"' . $str . '"' );
- }
-
- function getMockForViews() {
- $db = $this->getMockBuilder( 'DatabaseMysql' )
- ->disableOriginalConstructor()
- ->setMethods( [ 'fetchRow', 'query' ] )
- ->getMock();
-
- $db->expects( $this->any() )
- ->method( 'query' )
- ->with( $this->anything() )
- ->will(
- $this->returnValue( null )
- );
-
- $db->expects( $this->any() )
- ->method( 'fetchRow' )
- ->with( $this->anything() )
- ->will( $this->onConsecutiveCalls(
- [ 'Tables_in_' => 'view1' ],
- [ 'Tables_in_' => 'view2' ],
- [ 'Tables_in_' => 'myview' ],
- false # no more rows
- ) );
- return $db;
- }
- /**
- * @covers DatabaseMysqlBase::listViews
- */
- function testListviews() {
- $db = $this->getMockForViews();
-
- // The first call populate an internal cache of views
- $this->assertEquals( [ 'view1', 'view2', 'myview' ],
- $db->listViews() );
- $this->assertEquals( [ 'view1', 'view2', 'myview' ],
- $db->listViews() );
-
- // Prefix filtering
- $this->assertEquals( [ 'view1', 'view2' ],
- $db->listViews( 'view' ) );
- $this->assertEquals( [ 'myview' ],
- $db->listViews( 'my' ) );
- $this->assertEquals( [],
- $db->listViews( 'UNUSED_PREFIX' ) );
- $this->assertEquals( [ 'view1', 'view2', 'myview' ],
- $db->listViews( '' ) );
- }
-
- /**
- * @covers DatabaseMysqlBase::isView
- * @dataProvider provideViewExistanceChecks
- */
- function testIsView( $isView, $viewName ) {
- $db = $this->getMockForViews();
-
- switch ( $isView ) {
- case true:
- $this->assertTrue( $db->isView( $viewName ),
- "$viewName should be considered a view" );
- break;
-
- case false:
- $this->assertFalse( $db->isView( $viewName ),
- "$viewName has not been defined as a view" );
- break;
- }
-
- }
-
- function provideViewExistanceChecks() {
- return [
- // format: whether it is a view, view name
- [ true, 'view1' ],
- [ true, 'view2' ],
- [ true, 'myview' ],
-
- [ false, 'user' ],
-
- [ false, 'view10' ],
- [ false, 'my' ],
- [ false, 'OH_MY_GOD' ], # they killed kenny!
- ];
- }
-
- /**
- * @dataProvider provideComparePositions
- */
- function testHasReached( MySQLMasterPos $lowerPos, MySQLMasterPos $higherPos ) {
- $this->assertTrue( $higherPos->hasReached( $lowerPos ) );
- $this->assertTrue( $higherPos->hasReached( $higherPos ) );
- $this->assertTrue( $lowerPos->hasReached( $lowerPos ) );
- $this->assertFalse( $lowerPos->hasReached( $higherPos ) );
- }
-
- function provideComparePositions() {
- return [
- [
- new MySQLMasterPos( 'db1034-bin.000976', '843431247' ),
- new MySQLMasterPos( 'db1034-bin.000976', '843431248' )
- ],
- [
- new MySQLMasterPos( 'db1034-bin.000976', '999' ),
- new MySQLMasterPos( 'db1034-bin.000976', '1000' )
- ],
- [
- new MySQLMasterPos( 'db1034-bin.000976', '999' ),
- new MySQLMasterPos( 'db1035-bin.000976', '1000' )
- ],
- ];
- }
-
- /**
- * @dataProvider provideChannelPositions
- */
- function testChannelsMatch( MySQLMasterPos $pos1, MySQLMasterPos $pos2, $matches ) {
- $this->assertEquals( $matches, $pos1->channelsMatch( $pos2 ) );
- $this->assertEquals( $matches, $pos2->channelsMatch( $pos1 ) );
- }
-
- function provideChannelPositions() {
- return [
- [
- new MySQLMasterPos( 'db1034-bin.000876', '44' ),
- new MySQLMasterPos( 'db1034-bin.000976', '74' ),
- true
- ],
- [
- new MySQLMasterPos( 'db1052-bin.000976', '999' ),
- new MySQLMasterPos( 'db1052-bin.000976', '1000' ),
- true
- ],
- [
- new MySQLMasterPos( 'db1066-bin.000976', '9999' ),
- new MySQLMasterPos( 'db1035-bin.000976', '10000' ),
- false
- ],
- [
- new MySQLMasterPos( 'db1066-bin.000976', '9999' ),
- new MySQLMasterPos( 'trump2016.000976', '10000' ),
- false
- ],
- ];
- }
-
- /**
- * @dataProvider provideLagAmounts
- */
- function testPtHeartbeat( $lag ) {
- $db = $this->getMockBuilder( 'DatabaseMysql' )
- ->disableOriginalConstructor()
- ->setMethods( [
- 'getLagDetectionMethod', 'getHeartbeatData', 'getMasterServerInfo' ] )
- ->getMock();
-
- $db->expects( $this->any() )
- ->method( 'getLagDetectionMethod' )
- ->will( $this->returnValue( 'pt-heartbeat' ) );
-
- $db->expects( $this->any() )
- ->method( 'getMasterServerInfo' )
- ->will( $this->returnValue( [ '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( [ 'server_id' => 172 ] )
- ->will( $this->returnValue( [ $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 [
- [ 0 ],
- [ 0.3 ],
- [ 6.5 ],
- [ 10.1 ],
- [ 200.2 ],
- [ 400.7 ],
- [ 600.22 ],
- [ 1000.77 ],
- ];
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/db/DatabaseOracleTest.php b/www/wiki/tests/phpunit/includes/db/DatabaseOracleTest.php
new file mode 100644
index 00000000..061e121a
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/db/DatabaseOracleTest.php
@@ -0,0 +1,52 @@
+<?php
+
+class DatabaseOracleTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+ use PHPUnit4And6Compat;
+
+ /**
+ * @return PHPUnit_Framework_MockObject_MockObject|DatabaseOracle
+ */
+ private function getMockDb() {
+ return $this->getMockBuilder( DatabaseOracle::class )
+ ->disableOriginalConstructor()
+ ->setMethods( null )
+ ->getMock();
+ }
+
+ public function provideBuildSubstring() {
+ yield [ 'someField', 1, 2, 'SUBSTR(someField,1,2)' ];
+ yield [ 'someField', 1, null, 'SUBSTR(someField,1)' ];
+ }
+
+ /**
+ * @covers DatabaseOracle::buildSubstring
+ * @dataProvider provideBuildSubstring
+ */
+ public function testBuildSubstring( $input, $start, $length, $expected ) {
+ $mockDb = $this->getMockDb();
+ $output = $mockDb->buildSubstring( $input, $start, $length );
+ $this->assertSame( $expected, $output );
+ }
+
+ public function provideBuildSubstring_invalidParams() {
+ yield [ -1, 1 ];
+ yield [ 1, -1 ];
+ yield [ 1, 'foo' ];
+ yield [ 'foo', 1 ];
+ yield [ null, 1 ];
+ yield [ 0, 1 ];
+ }
+
+ /**
+ * @covers DatabaseOracle::buildSubstring
+ * @dataProvider provideBuildSubstring_invalidParams
+ */
+ public function testBuildSubstring_invalidParams( $start, $length ) {
+ $mockDb = $this->getMockDb();
+ $this->setExpectedException( InvalidArgumentException::class );
+ $mockDb->buildSubstring( 'foo', $start, $length );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/db/DatabasePostgresTest.php b/www/wiki/tests/phpunit/includes/db/DatabasePostgresTest.php
new file mode 100644
index 00000000..5c2aa2bb
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/db/DatabasePostgresTest.php
@@ -0,0 +1,177 @@
+<?php
+
+use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\DatabasePostgres;
+use Wikimedia\ScopedCallback;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @group Database
+ */
+class DatabasePostgresTest extends MediaWikiTestCase {
+
+ private function doTestInsertIgnore() {
+ $reset = new ScopedCallback( function () {
+ if ( $this->db->explicitTrxActive() ) {
+ $this->db->rollback( __METHOD__ );
+ }
+ $this->db->query( 'DROP TABLE IF EXISTS ' . $this->db->tableName( 'foo' ) );
+ } );
+
+ $this->db->query(
+ "CREATE TEMPORARY TABLE {$this->db->tableName( 'foo' )} (i INTEGER NOT NULL PRIMARY KEY)"
+ );
+ $this->db->insert( 'foo', [ [ 'i' => 1 ], [ 'i' => 2 ] ], __METHOD__ );
+
+ // Normal INSERT IGNORE
+ $this->db->begin( __METHOD__ );
+ $this->db->insert(
+ 'foo', [ [ 'i' => 3 ], [ 'i' => 2 ], [ 'i' => 5 ] ], __METHOD__, [ 'IGNORE' ]
+ );
+ $this->assertSame( 2, $this->db->affectedRows() );
+ $this->assertSame(
+ [ '1', '2', '3', '5' ],
+ $this->db->selectFieldValues( 'foo', 'i', [], __METHOD__, [ 'ORDER BY' => 'i' ] )
+ );
+ $this->db->rollback( __METHOD__ );
+
+ // INSERT IGNORE doesn't ignore stuff like NOT NULL violations
+ $this->db->begin( __METHOD__ );
+ $this->db->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ try {
+ $this->db->insert(
+ 'foo', [ [ 'i' => 7 ], [ 'i' => null ] ], __METHOD__, [ 'IGNORE' ]
+ );
+ $this->db->endAtomic( __METHOD__ );
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( DBQueryError $e ) {
+ $this->assertSame( 0, $this->db->affectedRows() );
+ $this->db->cancelAtomic( __METHOD__ );
+ }
+ $this->assertSame(
+ [ '1', '2' ],
+ $this->db->selectFieldValues( 'foo', 'i', [], __METHOD__, [ 'ORDER BY' => 'i' ] )
+ );
+ $this->db->rollback( __METHOD__ );
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\DatabasePostgres::insert
+ */
+ public function testInsertIgnoreOld() {
+ if ( !$this->db instanceof DatabasePostgres ) {
+ $this->markTestSkipped( 'Not PostgreSQL' );
+ }
+ if ( $this->db->getServerVersion() < 9.5 ) {
+ $this->doTestInsertIgnore();
+ } else {
+ // Hack version to make it take the old code path
+ $w = TestingAccessWrapper::newFromObject( $this->db );
+ $oldVer = $w->numericVersion;
+ $w->numericVersion = 9.4;
+ try {
+ $this->doTestInsertIgnore();
+ } finally {
+ $w->numericVersion = $oldVer;
+ }
+ }
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\DatabasePostgres::insert
+ */
+ public function testInsertIgnoreNew() {
+ if ( !$this->db instanceof DatabasePostgres ) {
+ $this->markTestSkipped( 'Not PostgreSQL' );
+ }
+ if ( $this->db->getServerVersion() < 9.5 ) {
+ $this->markTestSkipped( 'PostgreSQL version is ' . $this->db->getServerVersion() );
+ }
+
+ $this->doTestInsertIgnore();
+ }
+
+ private function doTestInsertSelectIgnore() {
+ $reset = new ScopedCallback( function () {
+ if ( $this->db->explicitTrxActive() ) {
+ $this->db->rollback( __METHOD__ );
+ }
+ $this->db->query( 'DROP TABLE IF EXISTS ' . $this->db->tableName( 'foo' ) );
+ $this->db->query( 'DROP TABLE IF EXISTS ' . $this->db->tableName( 'bar' ) );
+ } );
+
+ $this->db->query(
+ "CREATE TEMPORARY TABLE {$this->db->tableName( 'foo' )} (i INTEGER)"
+ );
+ $this->db->query(
+ "CREATE TEMPORARY TABLE {$this->db->tableName( 'bar' )} (i INTEGER NOT NULL PRIMARY KEY)"
+ );
+ $this->db->insert( 'bar', [ [ 'i' => 1 ], [ 'i' => 2 ] ], __METHOD__ );
+
+ // Normal INSERT IGNORE
+ $this->db->begin( __METHOD__ );
+ $this->db->insert( 'foo', [ [ 'i' => 3 ], [ 'i' => 2 ], [ 'i' => 5 ] ], __METHOD__ );
+ $this->db->insertSelect( 'bar', 'foo', [ 'i' => 'i' ], [], __METHOD__, [ 'IGNORE' ] );
+ $this->assertSame( 2, $this->db->affectedRows() );
+ $this->assertSame(
+ [ '1', '2', '3', '5' ],
+ $this->db->selectFieldValues( 'bar', 'i', [], __METHOD__, [ 'ORDER BY' => 'i' ] )
+ );
+ $this->db->rollback( __METHOD__ );
+
+ // INSERT IGNORE doesn't ignore stuff like NOT NULL violations
+ $this->db->begin( __METHOD__ );
+ $this->db->insert( 'foo', [ [ 'i' => 7 ], [ 'i' => null ] ], __METHOD__ );
+ $this->db->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ try {
+ $this->db->insertSelect( 'bar', 'foo', [ 'i' => 'i' ], [], __METHOD__, [ 'IGNORE' ] );
+ $this->db->endAtomic( __METHOD__ );
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( DBQueryError $e ) {
+ $this->assertSame( 0, $this->db->affectedRows() );
+ $this->db->cancelAtomic( __METHOD__ );
+ }
+ $this->assertSame(
+ [ '1', '2' ],
+ $this->db->selectFieldValues( 'bar', 'i', [], __METHOD__, [ 'ORDER BY' => 'i' ] )
+ );
+ $this->db->rollback( __METHOD__ );
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\DatabasePostgres::nativeInsertSelect
+ */
+ public function testInsertSelectIgnoreOld() {
+ if ( !$this->db instanceof DatabasePostgres ) {
+ $this->markTestSkipped( 'Not PostgreSQL' );
+ }
+ if ( $this->db->getServerVersion() < 9.5 ) {
+ $this->doTestInsertSelectIgnore();
+ } else {
+ // Hack version to make it take the old code path
+ $w = TestingAccessWrapper::newFromObject( $this->db );
+ $oldVer = $w->numericVersion;
+ $w->numericVersion = 9.4;
+ try {
+ $this->doTestInsertSelectIgnore();
+ } finally {
+ $w->numericVersion = $oldVer;
+ }
+ }
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\DatabasePostgres::nativeInsertSelect
+ */
+ public function testInsertSelectIgnoreNew() {
+ if ( !$this->db instanceof DatabasePostgres ) {
+ $this->markTestSkipped( 'Not PostgreSQL' );
+ }
+ if ( $this->db->getServerVersion() < 9.5 ) {
+ $this->markTestSkipped( 'PostgreSQL version is ' . $this->db->getServerVersion() );
+ }
+
+ $this->doTestInsertSelectIgnore();
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/db/DatabaseSQLTest.php b/www/wiki/tests/phpunit/includes/db/DatabaseSQLTest.php
deleted file mode 100644
index 5d5521cc..00000000
--- a/www/wiki/tests/phpunit/includes/db/DatabaseSQLTest.php
+++ /dev/null
@@ -1,805 +0,0 @@
-<?php
-
-/**
- * Test the abstract database layer
- * This is a non DBMS depending test.
- */
-class DatabaseSQLTest extends MediaWikiTestCase {
-
- /**
- * @var DatabaseTestHelper
- */
- private $database;
-
- protected function setUp() {
- parent::setUp();
- $this->database = new DatabaseTestHelper( __CLASS__ );
- }
-
- protected function assertLastSql( $sqlText ) {
- $this->assertEquals(
- $this->database->getLastSqls(),
- $sqlText
- );
- }
-
- /**
- * @dataProvider provideSelect
- * @covers DatabaseBase::select
- */
- public function testSelect( $sql, $sqlText ) {
- $this->database->select(
- $sql['tables'],
- $sql['fields'],
- isset( $sql['conds'] ) ? $sql['conds'] : [],
- __METHOD__,
- isset( $sql['options'] ) ? $sql['options'] : [],
- isset( $sql['join_conds'] ) ? $sql['join_conds'] : []
- );
- $this->assertLastSql( $sqlText );
- }
-
- public static function provideSelect() {
- return [
- [
- [
- 'tables' => 'table',
- 'fields' => [ 'field', 'alias' => 'field2' ],
- 'conds' => [ 'alias' => 'text' ],
- ],
- "SELECT field,field2 AS alias " .
- "FROM table " .
- "WHERE alias = 'text'"
- ],
- [
- [
- 'tables' => 'table',
- 'fields' => [ 'field', 'alias' => 'field2' ],
- 'conds' => [ 'alias' => 'text' ],
- 'options' => [ 'LIMIT' => 1, 'ORDER BY' => 'field' ],
- ],
- "SELECT field,field2 AS alias " .
- "FROM table " .
- "WHERE alias = 'text' " .
- "ORDER BY field " .
- "LIMIT 1"
- ],
- [
- [
- 'tables' => [ 'table', 't2' => 'table2' ],
- 'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ],
- 'conds' => [ 'alias' => 'text' ],
- 'options' => [ 'LIMIT' => 1, 'ORDER BY' => 'field' ],
- 'join_conds' => [ 't2' => [
- 'LEFT JOIN', 'tid = t2.id'
- ] ],
- ],
- "SELECT tid,field,field2 AS alias,t2.id " .
- "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " .
- "WHERE alias = 'text' " .
- "ORDER BY field " .
- "LIMIT 1"
- ],
- [
- [
- 'tables' => [ 'table', 't2' => 'table2' ],
- 'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ],
- 'conds' => [ 'alias' => 'text' ],
- 'options' => [ 'LIMIT' => 1, 'GROUP BY' => 'field', 'HAVING' => 'COUNT(*) > 1' ],
- 'join_conds' => [ 't2' => [
- 'LEFT JOIN', 'tid = t2.id'
- ] ],
- ],
- "SELECT tid,field,field2 AS alias,t2.id " .
- "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " .
- "WHERE alias = 'text' " .
- "GROUP BY field HAVING COUNT(*) > 1 " .
- "LIMIT 1"
- ],
- [
- [
- 'tables' => [ 'table', 't2' => 'table2' ],
- 'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ],
- 'conds' => [ 'alias' => 'text' ],
- 'options' => [
- 'LIMIT' => 1,
- 'GROUP BY' => [ 'field', 'field2' ],
- 'HAVING' => [ 'COUNT(*) > 1', 'field' => 1 ]
- ],
- 'join_conds' => [ 't2' => [
- 'LEFT JOIN', 'tid = t2.id'
- ] ],
- ],
- "SELECT tid,field,field2 AS alias,t2.id " .
- "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " .
- "WHERE alias = 'text' " .
- "GROUP BY field,field2 HAVING (COUNT(*) > 1) AND field = '1' " .
- "LIMIT 1"
- ],
- [
- [
- 'tables' => [ 'table' ],
- 'fields' => [ 'alias' => 'field' ],
- 'conds' => [ 'alias' => [ 1, 2, 3, 4 ] ],
- ],
- "SELECT field AS alias " .
- "FROM table " .
- "WHERE alias IN ('1','2','3','4')"
- ],
- ];
- }
-
- /**
- * @dataProvider provideUpdate
- * @covers DatabaseBase::update
- */
- public function testUpdate( $sql, $sqlText ) {
- $this->database->update(
- $sql['table'],
- $sql['values'],
- $sql['conds'],
- __METHOD__,
- isset( $sql['options'] ) ? $sql['options'] : []
- );
- $this->assertLastSql( $sqlText );
- }
-
- public static function provideUpdate() {
- return [
- [
- [
- 'table' => 'table',
- 'values' => [ 'field' => 'text', 'field2' => 'text2' ],
- 'conds' => [ 'alias' => 'text' ],
- ],
- "UPDATE table " .
- "SET field = 'text'" .
- ",field2 = 'text2' " .
- "WHERE alias = 'text'"
- ],
- [
- [
- 'table' => 'table',
- 'values' => [ 'field = other', 'field2' => 'text2' ],
- 'conds' => [ 'id' => '1' ],
- ],
- "UPDATE table " .
- "SET field = other" .
- ",field2 = 'text2' " .
- "WHERE id = '1'"
- ],
- [
- [
- 'table' => 'table',
- 'values' => [ 'field = other', 'field2' => 'text2' ],
- 'conds' => '*',
- ],
- "UPDATE table " .
- "SET field = other" .
- ",field2 = 'text2'"
- ],
- ];
- }
-
- /**
- * @dataProvider provideDelete
- * @covers DatabaseBase::delete
- */
- public function testDelete( $sql, $sqlText ) {
- $this->database->delete(
- $sql['table'],
- $sql['conds'],
- __METHOD__
- );
- $this->assertLastSql( $sqlText );
- }
-
- public static function provideDelete() {
- return [
- [
- [
- 'table' => 'table',
- 'conds' => [ 'alias' => 'text' ],
- ],
- "DELETE FROM table " .
- "WHERE alias = 'text'"
- ],
- [
- [
- 'table' => 'table',
- 'conds' => '*',
- ],
- "DELETE FROM table"
- ],
- ];
- }
-
- /**
- * @dataProvider provideUpsert
- * @covers DatabaseBase::upsert
- */
- public function testUpsert( $sql, $sqlText ) {
- $this->database->upsert(
- $sql['table'],
- $sql['rows'],
- $sql['uniqueIndexes'],
- $sql['set'],
- __METHOD__
- );
- $this->assertLastSql( $sqlText );
- }
-
- public static function provideUpsert() {
- return [
- [
- [
- 'table' => 'upsert_table',
- 'rows' => [ 'field' => 'text', 'field2' => 'text2' ],
- 'uniqueIndexes' => [ 'field' ],
- 'set' => [ 'field' => 'set' ],
- ],
- "BEGIN; " .
- "UPDATE upsert_table " .
- "SET field = 'set' " .
- "WHERE ((field = 'text')); " .
- "INSERT IGNORE INTO upsert_table " .
- "(field,field2) " .
- "VALUES ('text','text2'); " .
- "COMMIT"
- ],
- ];
- }
-
- /**
- * @dataProvider provideDeleteJoin
- * @covers DatabaseBase::deleteJoin
- */
- public function testDeleteJoin( $sql, $sqlText ) {
- $this->database->deleteJoin(
- $sql['delTable'],
- $sql['joinTable'],
- $sql['delVar'],
- $sql['joinVar'],
- $sql['conds'],
- __METHOD__
- );
- $this->assertLastSql( $sqlText );
- }
-
- public static function provideDeleteJoin() {
- return [
- [
- [
- 'delTable' => 'table',
- 'joinTable' => 'table_join',
- 'delVar' => 'field',
- 'joinVar' => 'field_join',
- 'conds' => [ 'alias' => 'text' ],
- ],
- "DELETE FROM table " .
- "WHERE field IN (" .
- "SELECT field_join FROM table_join WHERE alias = 'text'" .
- ")"
- ],
- [
- [
- 'delTable' => 'table',
- 'joinTable' => 'table_join',
- 'delVar' => 'field',
- 'joinVar' => 'field_join',
- 'conds' => '*',
- ],
- "DELETE FROM table " .
- "WHERE field IN (" .
- "SELECT field_join FROM table_join " .
- ")"
- ],
- ];
- }
-
- /**
- * @dataProvider provideInsert
- * @covers DatabaseBase::insert
- */
- public function testInsert( $sql, $sqlText ) {
- $this->database->insert(
- $sql['table'],
- $sql['rows'],
- __METHOD__,
- isset( $sql['options'] ) ? $sql['options'] : []
- );
- $this->assertLastSql( $sqlText );
- }
-
- public static function provideInsert() {
- return [
- [
- [
- 'table' => 'table',
- 'rows' => [ 'field' => 'text', 'field2' => 2 ],
- ],
- "INSERT INTO table " .
- "(field,field2) " .
- "VALUES ('text','2')"
- ],
- [
- [
- 'table' => 'table',
- 'rows' => [ 'field' => 'text', 'field2' => 2 ],
- 'options' => 'IGNORE',
- ],
- "INSERT IGNORE INTO table " .
- "(field,field2) " .
- "VALUES ('text','2')"
- ],
- [
- [
- 'table' => 'table',
- 'rows' => [
- [ 'field' => 'text', 'field2' => 2 ],
- [ 'field' => 'multi', 'field2' => 3 ],
- ],
- 'options' => 'IGNORE',
- ],
- "INSERT IGNORE INTO table " .
- "(field,field2) " .
- "VALUES " .
- "('text','2')," .
- "('multi','3')"
- ],
- ];
- }
-
- /**
- * @dataProvider provideInsertSelect
- * @covers DatabaseBase::insertSelect
- */
- public function testInsertSelect( $sql, $sqlText ) {
- $this->database->insertSelect(
- $sql['destTable'],
- $sql['srcTable'],
- $sql['varMap'],
- $sql['conds'],
- __METHOD__,
- isset( $sql['insertOptions'] ) ? $sql['insertOptions'] : [],
- isset( $sql['selectOptions'] ) ? $sql['selectOptions'] : []
- );
- $this->assertLastSql( $sqlText );
- }
-
- public static function provideInsertSelect() {
- return [
- [
- [
- 'destTable' => 'insert_table',
- 'srcTable' => 'select_table',
- 'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ],
- 'conds' => '*',
- ],
- "INSERT INTO insert_table " .
- "(field_insert,field) " .
- "SELECT field_select,field2 " .
- "FROM select_table"
- ],
- [
- [
- 'destTable' => 'insert_table',
- 'srcTable' => 'select_table',
- 'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ],
- 'conds' => [ 'field' => 2 ],
- ],
- "INSERT INTO insert_table " .
- "(field_insert,field) " .
- "SELECT field_select,field2 " .
- "FROM select_table " .
- "WHERE field = '2'"
- ],
- [
- [
- 'destTable' => 'insert_table',
- 'srcTable' => 'select_table',
- 'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ],
- 'conds' => [ 'field' => 2 ],
- 'insertOptions' => 'IGNORE',
- 'selectOptions' => [ 'ORDER BY' => 'field' ],
- ],
- "INSERT IGNORE INTO insert_table " .
- "(field_insert,field) " .
- "SELECT field_select,field2 " .
- "FROM select_table " .
- "WHERE field = '2' " .
- "ORDER BY field"
- ],
- ];
- }
-
- /**
- * @dataProvider provideReplace
- * @covers DatabaseBase::replace
- */
- public function testReplace( $sql, $sqlText ) {
- $this->database->replace(
- $sql['table'],
- $sql['uniqueIndexes'],
- $sql['rows'],
- __METHOD__
- );
- $this->assertLastSql( $sqlText );
- }
-
- public static function provideReplace() {
- return [
- [
- [
- 'table' => 'replace_table',
- 'uniqueIndexes' => [ 'field' ],
- 'rows' => [ 'field' => 'text', 'field2' => 'text2' ],
- ],
- "DELETE FROM replace_table " .
- "WHERE ( field='text' ); " .
- "INSERT INTO replace_table " .
- "(field,field2) " .
- "VALUES ('text','text2')"
- ],
- [
- [
- 'table' => 'module_deps',
- 'uniqueIndexes' => [ [ 'md_module', 'md_skin' ] ],
- 'rows' => [
- 'md_module' => 'module',
- 'md_skin' => 'skin',
- 'md_deps' => 'deps',
- ],
- ],
- "DELETE FROM module_deps " .
- "WHERE ( md_module='module' AND md_skin='skin' ); " .
- "INSERT INTO module_deps " .
- "(md_module,md_skin,md_deps) " .
- "VALUES ('module','skin','deps')"
- ],
- [
- [
- 'table' => 'module_deps',
- 'uniqueIndexes' => [ [ 'md_module', 'md_skin' ] ],
- 'rows' => [
- [
- 'md_module' => 'module',
- 'md_skin' => 'skin',
- 'md_deps' => 'deps',
- ], [
- 'md_module' => 'module2',
- 'md_skin' => 'skin2',
- 'md_deps' => 'deps2',
- ],
- ],
- ],
- "DELETE FROM module_deps " .
- "WHERE ( md_module='module' AND md_skin='skin' ); " .
- "INSERT INTO module_deps " .
- "(md_module,md_skin,md_deps) " .
- "VALUES ('module','skin','deps'); " .
- "DELETE FROM module_deps " .
- "WHERE ( md_module='module2' AND md_skin='skin2' ); " .
- "INSERT INTO module_deps " .
- "(md_module,md_skin,md_deps) " .
- "VALUES ('module2','skin2','deps2')"
- ],
- [
- [
- 'table' => 'module_deps',
- 'uniqueIndexes' => [ 'md_module', 'md_skin' ],
- 'rows' => [
- [
- 'md_module' => 'module',
- 'md_skin' => 'skin',
- 'md_deps' => 'deps',
- ], [
- 'md_module' => 'module2',
- 'md_skin' => 'skin2',
- 'md_deps' => 'deps2',
- ],
- ],
- ],
- "DELETE FROM module_deps " .
- "WHERE ( md_module='module' ) OR ( md_skin='skin' ); " .
- "INSERT INTO module_deps " .
- "(md_module,md_skin,md_deps) " .
- "VALUES ('module','skin','deps'); " .
- "DELETE FROM module_deps " .
- "WHERE ( md_module='module2' ) OR ( md_skin='skin2' ); " .
- "INSERT INTO module_deps " .
- "(md_module,md_skin,md_deps) " .
- "VALUES ('module2','skin2','deps2')"
- ],
- [
- [
- 'table' => 'module_deps',
- 'uniqueIndexes' => [],
- 'rows' => [
- 'md_module' => 'module',
- 'md_skin' => 'skin',
- 'md_deps' => 'deps',
- ],
- ],
- "INSERT INTO module_deps " .
- "(md_module,md_skin,md_deps) " .
- "VALUES ('module','skin','deps')"
- ],
- ];
- }
-
- /**
- * @dataProvider provideNativeReplace
- * @covers DatabaseBase::nativeReplace
- */
- public function testNativeReplace( $sql, $sqlText ) {
- $this->database->nativeReplace(
- $sql['table'],
- $sql['rows'],
- __METHOD__
- );
- $this->assertLastSql( $sqlText );
- }
-
- public static function provideNativeReplace() {
- return [
- [
- [
- 'table' => 'replace_table',
- 'rows' => [ 'field' => 'text', 'field2' => 'text2' ],
- ],
- "REPLACE INTO replace_table " .
- "(field,field2) " .
- "VALUES ('text','text2')"
- ],
- ];
- }
-
- /**
- * @dataProvider provideConditional
- * @covers DatabaseBase::conditional
- */
- public function testConditional( $sql, $sqlText ) {
- $this->assertEquals( trim( $this->database->conditional(
- $sql['conds'],
- $sql['true'],
- $sql['false']
- ) ), $sqlText );
- }
-
- public static function provideConditional() {
- return [
- [
- [
- 'conds' => [ 'field' => 'text' ],
- 'true' => 1,
- 'false' => 'NULL',
- ],
- "(CASE WHEN field = 'text' THEN 1 ELSE NULL END)"
- ],
- [
- [
- 'conds' => [ 'field' => 'text', 'field2' => 'anothertext' ],
- 'true' => 1,
- 'false' => 'NULL',
- ],
- "(CASE WHEN field = 'text' AND field2 = 'anothertext' THEN 1 ELSE NULL END)"
- ],
- [
- [
- 'conds' => 'field=1',
- 'true' => 1,
- 'false' => 'NULL',
- ],
- "(CASE WHEN field=1 THEN 1 ELSE NULL END)"
- ],
- ];
- }
-
- /**
- * @dataProvider provideBuildConcat
- * @covers DatabaseBase::buildConcat
- */
- public function testBuildConcat( $stringList, $sqlText ) {
- $this->assertEquals( trim( $this->database->buildConcat(
- $stringList
- ) ), $sqlText );
- }
-
- public static function provideBuildConcat() {
- return [
- [
- [ 'field', 'field2' ],
- "CONCAT(field,field2)"
- ],
- [
- [ "'test'", 'field2' ],
- "CONCAT('test',field2)"
- ],
- ];
- }
-
- /**
- * @dataProvider provideBuildLike
- * @covers DatabaseBase::buildLike
- */
- public function testBuildLike( $array, $sqlText ) {
- $this->assertEquals( trim( $this->database->buildLike(
- $array
- ) ), $sqlText );
- }
-
- public static function provideBuildLike() {
- return [
- [
- 'text',
- "LIKE 'text'"
- ],
- [
- [ 'text', new LikeMatch( '%' ) ],
- "LIKE 'text%'"
- ],
- [
- [ 'text', new LikeMatch( '%' ), 'text2' ],
- "LIKE 'text%text2'"
- ],
- [
- [ 'text', new LikeMatch( '_' ) ],
- "LIKE 'text_'"
- ],
- ];
- }
-
- /**
- * @dataProvider provideUnionQueries
- * @covers DatabaseBase::unionQueries
- */
- public function testUnionQueries( $sql, $sqlText ) {
- $this->assertEquals( trim( $this->database->unionQueries(
- $sql['sqls'],
- $sql['all']
- ) ), $sqlText );
- }
-
- public static function provideUnionQueries() {
- return [
- [
- [
- 'sqls' => [ 'RAW SQL', 'RAW2SQL' ],
- 'all' => true,
- ],
- "(RAW SQL) UNION ALL (RAW2SQL)"
- ],
- [
- [
- 'sqls' => [ 'RAW SQL', 'RAW2SQL' ],
- 'all' => false,
- ],
- "(RAW SQL) UNION (RAW2SQL)"
- ],
- [
- [
- 'sqls' => [ 'RAW SQL', 'RAW2SQL', 'RAW3SQL' ],
- 'all' => false,
- ],
- "(RAW SQL) UNION (RAW2SQL) UNION (RAW3SQL)"
- ],
- ];
- }
-
- /**
- * @covers DatabaseBase::commit
- */
- public function testTransactionCommit() {
- $this->database->begin( __METHOD__ );
- $this->database->commit( __METHOD__ );
- $this->assertLastSql( 'BEGIN; COMMIT' );
- }
-
- /**
- * @covers DatabaseBase::rollback
- */
- public function testTransactionRollback() {
- $this->database->begin( __METHOD__ );
- $this->database->rollback( __METHOD__ );
- $this->assertLastSql( 'BEGIN; ROLLBACK' );
- }
-
- /**
- * @covers DatabaseBase::dropTable
- */
- public function testDropTable() {
- $this->database->setExistingTables( [ 'table' ] );
- $this->database->dropTable( 'table', __METHOD__ );
- $this->assertLastSql( 'DROP TABLE table' );
- }
-
- /**
- * @covers DatabaseBase::dropTable
- */
- public function testDropNonExistingTable() {
- $this->assertFalse(
- $this->database->dropTable( 'non_existing', __METHOD__ )
- );
- }
-
- /**
- * @dataProvider provideMakeList
- * @covers DatabaseBase::makeList
- */
- public function testMakeList( $list, $mode, $sqlText ) {
- $this->assertEquals( trim( $this->database->makeList(
- $list, $mode
- ) ), $sqlText );
- }
-
- public static function provideMakeList() {
- return [
- [
- [ 'value', 'value2' ],
- LIST_COMMA,
- "'value','value2'"
- ],
- [
- [ 'field', 'field2' ],
- LIST_NAMES,
- "field,field2"
- ],
- [
- [ 'field' => 'value', 'field2' => 'value2' ],
- LIST_AND,
- "field = 'value' AND field2 = 'value2'"
- ],
- [
- [ 'field' => null, "field2 != 'value2'" ],
- LIST_AND,
- "field IS NULL AND (field2 != 'value2')"
- ],
- [
- [ 'field' => [ 'value', null, 'value2' ], 'field2' => 'value2' ],
- LIST_AND,
- "(field IN ('value','value2') OR field IS NULL) AND field2 = 'value2'"
- ],
- [
- [ 'field' => [ null ], 'field2' => null ],
- LIST_AND,
- "field IS NULL AND field2 IS NULL"
- ],
- [
- [ 'field' => 'value', 'field2' => 'value2' ],
- LIST_OR,
- "field = 'value' OR field2 = 'value2'"
- ],
- [
- [ 'field' => 'value', 'field2' => null ],
- LIST_OR,
- "field = 'value' OR field2 IS NULL"
- ],
- [
- [ 'field' => [ 'value', 'value2' ], 'field2' => [ 'value' ] ],
- LIST_OR,
- "field IN ('value','value2') OR field2 = 'value'"
- ],
- [
- [ 'field' => [ null, 'value', null, 'value2' ], "field2 != 'value2'" ],
- LIST_OR,
- "(field IN ('value','value2') OR field IS NULL) OR (field2 != 'value2')"
- ],
- [
- [ 'field' => 'value', 'field2' => 'value2' ],
- LIST_SET,
- "field = 'value',field2 = 'value2'"
- ],
- [
- [ 'field' => 'value', 'field2' => null ],
- LIST_SET,
- "field = 'value',field2 = NULL"
- ],
- [
- [ 'field' => 'value', "field2 != 'value2'" ],
- LIST_SET,
- "field = 'value',field2 != 'value2'"
- ],
- ];
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/db/DatabaseSqliteTest.php b/www/wiki/tests/phpunit/includes/db/DatabaseSqliteTest.php
index ae61070f..729b58c7 100644
--- a/www/wiki/tests/phpunit/includes/db/DatabaseSqliteTest.php
+++ b/www/wiki/tests/phpunit/includes/db/DatabaseSqliteTest.php
@@ -3,10 +3,9 @@
use Wikimedia\Rdbms\Blob;
use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\DatabaseSqlite;
+use Wikimedia\Rdbms\ResultWrapper;
class DatabaseSqliteMock extends DatabaseSqlite {
- private $lastQuery;
-
public static function newInstance( array $p = [] ) {
$p['dbFilePath'] = ':memory:';
$p['schema'] = false;
@@ -15,8 +14,6 @@ class DatabaseSqliteMock extends DatabaseSqlite {
}
function query( $sql, $fname = '', $tempIgnore = false ) {
- $this->lastQuery = $sql;
-
return true;
}
@@ -285,6 +282,9 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
);
}
+ /**
+ * @coversNothing
+ */
public function testEntireSchema() {
global $IP;
@@ -298,6 +298,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
/**
* Runs upgrades of older databases and compares results with current schema
* @todo Currently only checks list of tables
+ * @coversNothing
*/
public function testUpgrades() {
global $IP, $wgVersion, $wgProfiler;
@@ -310,6 +311,11 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
'1.16',
'1.17',
'1.18',
+ '1.19',
+ '1.20',
+ '1.21',
+ '1.22',
+ '1.23',
];
// Mismatches for these columns we can safely ignore
@@ -388,7 +394,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
$db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
$databaseCreation = $db->query( 'CREATE TABLE a ( a_1 )', __METHOD__ );
- $this->assertInstanceOf( 'ResultWrapper', $databaseCreation, "Database creation" );
+ $this->assertInstanceOf( ResultWrapper::class, $databaseCreation, "Database creation" );
$insertion = $db->insert( 'a', [ 'a_1' => 10 ], __METHOD__ );
$this->assertTrue( $insertion, "Insertion worked" );
@@ -481,7 +487,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
$db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
$databaseCreation = $db->query( 'CREATE TABLE a ( a_1 )', __METHOD__ );
- $this->assertInstanceOf( 'ResultWrapper', $databaseCreation, "Failed to create table a" );
+ $this->assertInstanceOf( ResultWrapper::class, $databaseCreation, "Failed to create table a" );
$res = $db->select( 'a', '*' );
$this->assertEquals( 0, $db->numFields( $res ), "expects to get 0 fields for an empty table" );
$insertion = $db->insert( 'a', [ 'a_1' => 10 ], __METHOD__ );
@@ -492,6 +498,9 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
$this->assertTrue( $db->close(), "closing database" );
}
+ /**
+ * @covers \Wikimedia\Rdbms\DatabaseSqlite::__toString
+ */
public function testToString() {
$db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
@@ -499,4 +508,12 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
$this->assertContains( 'SQLite ', $toString );
}
+
+ /**
+ * @covers \Wikimedia\Rdbms\DatabaseSqlite::getAttributes()
+ */
+ public function testsAttributes() {
+ $attributes = Database::attributesFromType( 'sqlite' );
+ $this->assertTrue( $attributes[Database::ATTR_DB_LEVEL_LOCKING] );
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/db/DatabaseTest.php b/www/wiki/tests/phpunit/includes/db/DatabaseTest.php
deleted file mode 100644
index 0730529b..00000000
--- a/www/wiki/tests/phpunit/includes/db/DatabaseTest.php
+++ /dev/null
@@ -1,263 +0,0 @@
-<?php
-
-/**
- * @group Database
- * @group DatabaseBase
- */
-class DatabaseTest extends MediaWikiTestCase {
- /**
- * @var DatabaseBase
- */
- protected $db;
-
- private $functionTest = false;
-
- protected function setUp() {
- parent::setUp();
- $this->db = wfGetDB( DB_MASTER );
- }
-
- protected function tearDown() {
- parent::tearDown();
- if ( $this->functionTest ) {
- $this->dropFunctions();
- $this->functionTest = false;
- }
- }
- /**
- * @covers DatabaseBase::dropTable
- */
- public function testAddQuotesNull() {
- $check = "NULL";
- if ( $this->db->getType() === 'sqlite' || $this->db->getType() === 'oracle' ) {
- $check = "''";
- }
- $this->assertEquals( $check, $this->db->addQuotes( null ) );
- }
-
- public function testAddQuotesInt() {
- # returning just "1234" should be ok too, though...
- # maybe
- $this->assertEquals(
- "'1234'",
- $this->db->addQuotes( 1234 ) );
- }
-
- public function testAddQuotesFloat() {
- # returning just "1234.5678" would be ok too, though
- $this->assertEquals(
- "'1234.5678'",
- $this->db->addQuotes( 1234.5678 ) );
- }
-
- public function testAddQuotesString() {
- $this->assertEquals(
- "'string'",
- $this->db->addQuotes( 'string' ) );
- }
-
- public function testAddQuotesStringQuote() {
- $check = "'string''s cause trouble'";
- if ( $this->db->getType() === 'mysql' ) {
- $check = "'string\'s cause trouble'";
- }
- $this->assertEquals(
- $check,
- $this->db->addQuotes( "string's cause trouble" ) );
- }
-
- private function getSharedTableName( $table, $database, $prefix, $format = 'quoted' ) {
- global $wgSharedDB, $wgSharedTables, $wgSharedPrefix;
-
- $oldName = $wgSharedDB;
- $oldTables = $wgSharedTables;
- $oldPrefix = $wgSharedPrefix;
-
- $wgSharedDB = $database;
- $wgSharedTables = [ $table ];
- $wgSharedPrefix = $prefix;
-
- $ret = $this->db->tableName( $table, $format );
-
- $wgSharedDB = $oldName;
- $wgSharedTables = $oldTables;
- $wgSharedPrefix = $oldPrefix;
-
- return $ret;
- }
-
- private function prefixAndQuote( $table, $database = null, $prefix = null, $format = 'quoted' ) {
- if ( $this->db->getType() === 'sqlite' || $format !== 'quoted' ) {
- $quote = '';
- } elseif ( $this->db->getType() === 'mysql' ) {
- $quote = '`';
- } elseif ( $this->db->getType() === 'oracle' ) {
- $quote = '/*Q*/';
- } else {
- $quote = '"';
- }
-
- if ( $database !== null ) {
- if ( $this->db->getType() === 'oracle' ) {
- $database = $quote . $database . '.';
- } else {
- $database = $quote . $database . $quote . '.';
- }
- }
-
- if ( $prefix === null ) {
- $prefix = $this->dbPrefix();
- }
-
- if ( $this->db->getType() === 'oracle' ) {
- return strtoupper( $database . $quote . $prefix . $table );
- } else {
- return $database . $quote . $prefix . $table . $quote;
- }
- }
-
- public function testTableNameLocal() {
- $this->assertEquals(
- $this->prefixAndQuote( 'tablename' ),
- $this->db->tableName( 'tablename' )
- );
- }
-
- public function testTableNameRawLocal() {
- $this->assertEquals(
- $this->prefixAndQuote( 'tablename', null, null, 'raw' ),
- $this->db->tableName( 'tablename', 'raw' )
- );
- }
-
- public function testTableNameShared() {
- $this->assertEquals(
- $this->prefixAndQuote( 'tablename', 'sharedatabase', 'sh_' ),
- $this->getSharedTableName( 'tablename', 'sharedatabase', 'sh_' )
- );
-
- $this->assertEquals(
- $this->prefixAndQuote( 'tablename', 'sharedatabase', null ),
- $this->getSharedTableName( 'tablename', 'sharedatabase', null )
- );
- }
-
- public function testTableNameRawShared() {
- $this->assertEquals(
- $this->prefixAndQuote( 'tablename', 'sharedatabase', 'sh_', 'raw' ),
- $this->getSharedTableName( 'tablename', 'sharedatabase', 'sh_', 'raw' )
- );
-
- $this->assertEquals(
- $this->prefixAndQuote( 'tablename', 'sharedatabase', null, 'raw' ),
- $this->getSharedTableName( 'tablename', 'sharedatabase', null, 'raw' )
- );
- }
-
- public function testTableNameForeign() {
- $this->assertEquals(
- $this->prefixAndQuote( 'tablename', 'databasename', '' ),
- $this->db->tableName( 'databasename.tablename' )
- );
- }
-
- public function testTableNameRawForeign() {
- $this->assertEquals(
- $this->prefixAndQuote( 'tablename', 'databasename', '', 'raw' ),
- $this->db->tableName( 'databasename.tablename', 'raw' )
- );
- }
-
- public function testFillPreparedEmpty() {
- $sql = $this->db->fillPrepared(
- 'SELECT * FROM interwiki', [] );
- $this->assertEquals(
- "SELECT * FROM interwiki",
- $sql );
- }
-
- public function testFillPreparedQuestion() {
- $sql = $this->db->fillPrepared(
- 'SELECT * FROM cur WHERE cur_namespace=? AND cur_title=?',
- [ 4, "Snicker's_paradox" ] );
-
- $check = "SELECT * FROM cur WHERE cur_namespace='4' AND cur_title='Snicker''s_paradox'";
- if ( $this->db->getType() === 'mysql' ) {
- $check = "SELECT * FROM cur WHERE cur_namespace='4' AND cur_title='Snicker\'s_paradox'";
- }
- $this->assertEquals( $check, $sql );
- }
-
- public function testFillPreparedBang() {
- $sql = $this->db->fillPrepared(
- 'SELECT user_id FROM ! WHERE user_name=?',
- [ '"user"', "Slash's Dot" ] );
-
- $check = "SELECT user_id FROM \"user\" WHERE user_name='Slash''s Dot'";
- if ( $this->db->getType() === 'mysql' ) {
- $check = "SELECT user_id FROM \"user\" WHERE user_name='Slash\'s Dot'";
- }
- $this->assertEquals( $check, $sql );
- }
-
- public function testFillPreparedRaw() {
- $sql = $this->db->fillPrepared(
- "SELECT * FROM cur WHERE cur_title='This_\\&_that,_WTF\\?\\!'",
- [ '"user"', "Slash's Dot" ] );
- $this->assertEquals(
- "SELECT * FROM cur WHERE cur_title='This_&_that,_WTF?!'",
- $sql );
- }
-
- public function testStoredFunctions() {
- if ( !in_array( wfGetDB( DB_MASTER )->getType(), [ 'mysql', 'postgres' ] ) ) {
- $this->markTestSkipped( 'MySQL or Postgres required' );
- }
- global $IP;
- $this->dropFunctions();
- $this->functionTest = true;
- $this->assertTrue(
- $this->db->sourceFile( "$IP/tests/phpunit/data/db/{$this->db->getType()}/functions.sql" )
- );
- $res = $this->db->query( 'SELECT mw_test_function() AS test', __METHOD__ );
- $this->assertEquals( 42, $res->fetchObject()->test );
- }
-
- private function dropFunctions() {
- $this->db->query( 'DROP FUNCTION IF EXISTS mw_test_function'
- . ( $this->db->getType() == 'postgres' ? '()' : '' )
- );
- }
-
- public function testUnknownTableCorruptsResults() {
- $res = $this->db->select( 'page', '*', [ 'page_id' => 1 ] );
- $this->assertFalse( $this->db->tableExists( 'foobarbaz' ) );
- $this->assertInternalType( 'int', $res->numRows() );
- }
-
- public function testTransactionIdle() {
- $db = $this->db;
-
- $db->setFlag( DBO_TRX );
- $flagSet = null;
- $db->onTransactionIdle( function() use ( $db, &$flagSet ) {
- $flagSet = $db->getFlag( DBO_TRX );
- } );
- $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
- $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
-
- $db->clearFlag( DBO_TRX );
- $flagSet = null;
- $db->onTransactionIdle( function() use ( $db, &$flagSet ) {
- $flagSet = $db->getFlag( DBO_TRX );
- } );
- $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
- $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
-
- $db->clearFlag( DBO_TRX );
- $db->onTransactionIdle( function() use ( $db ) {
- $db->setFlag( DBO_TRX );
- } );
- $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/db/DatabaseTestHelper.php b/www/wiki/tests/phpunit/includes/db/DatabaseTestHelper.php
index d19d9981..e9fc34fa 100644
--- a/www/wiki/tests/phpunit/includes/db/DatabaseTestHelper.php
+++ b/www/wiki/tests/phpunit/includes/db/DatabaseTestHelper.php
@@ -2,6 +2,7 @@
use Wikimedia\Rdbms\TransactionProfiler;
use Wikimedia\Rdbms\DatabaseDomain;
+use Wikimedia\Rdbms\Database;
/**
* Helper for testing the methods from the Database class
@@ -25,6 +26,11 @@ class DatabaseTestHelper extends Database {
/** @var array List of row arrays */
protected $nextResult = [];
+ /** @var array|null */
+ protected $nextError = null;
+ /** @var array|null */
+ protected $lastError = null;
+
/**
* Array of tables to be considered as existing by tableExist()
* Use setExistingTables() to alter.
@@ -47,7 +53,11 @@ class DatabaseTestHelper extends Database {
$this->errorLogger = function ( Exception $e ) {
wfWarn( get_class( $e ) . ": {$e->getMessage()}" );
};
+ $this->deprecationLogger = function ( $msg ) {
+ wfWarn( $msg );
+ };
$this->currentDomain = DatabaseDomain::newUnspecified();
+ $this->open( 'localhost', 'testuser', 'password', 'testdb' );
}
/**
@@ -73,6 +83,16 @@ class DatabaseTestHelper extends Database {
$this->nextResult = $res;
}
+ /**
+ * @param int $errno Error number
+ * @param string $error Error text
+ * @param array $options
+ * - wasKnownStatementRollbackError: Return value for wasKnownStatementRollbackError()
+ */
+ public function forceNextQueryError( $errno, $error, $options = [] ) {
+ $this->nextError = [ 'errno' => $errno, 'error' => $error ] + $options;
+ }
+
protected function addSql( $sql ) {
// clean up spaces before and after some words and the whole string
$this->lastSqls[] = trim( preg_replace(
@@ -82,7 +102,17 @@ class DatabaseTestHelper extends Database {
}
protected function checkFunctionName( $fname ) {
- if ( substr( $fname, 0, strlen( $this->testName ) ) !== $this->testName ) {
+ if ( $fname === 'Wikimedia\\Rdbms\\Database::close' ) {
+ return; // no $fname parameter
+ }
+
+ // Handle some internal calls from the Database class
+ $check = $fname;
+ if ( preg_match( '/^Wikimedia\\\\Rdbms\\\\Database::query \((.+)\)$/', $fname, $m ) ) {
+ $check = $m[1];
+ }
+
+ if ( substr( $check, 0, strlen( $this->testName ) ) !== $this->testName ) {
throw new MWException( 'function name does not start with test class. ' .
$fname . ' vs. ' . $this->testName . '. ' .
'Please provide __METHOD__ to database methods.' );
@@ -101,14 +131,13 @@ class DatabaseTestHelper extends Database {
public function query( $sql, $fname = '', $tempIgnore = false ) {
$this->checkFunctionName( $fname );
- $this->addSql( $sql );
return parent::query( $sql, $fname, $tempIgnore );
}
public function tableExists( $table, $fname = __METHOD__ ) {
$tableRaw = $this->tableName( $table, 'raw' );
- if ( isset( $this->mSessionTempTables[$tableRaw] ) ) {
+ if ( isset( $this->sessionTempTables[$tableRaw] ) ) {
return true; // already known to exist
}
@@ -127,7 +156,9 @@ class DatabaseTestHelper extends Database {
}
function open( $server, $user, $password, $dbName ) {
- return false;
+ $this->conn = (object)[ 'test' ];
+
+ return true;
}
function fetchObject( $res ) {
@@ -159,11 +190,17 @@ class DatabaseTestHelper extends Database {
}
function lastErrno() {
- return -1;
+ return $this->lastError ? $this->lastError['errno'] : -1;
}
function lastError() {
- return 'test';
+ return $this->lastError ? $this->lastError['error'] : 'test';
+ }
+
+ protected function wasKnownStatementRollbackError() {
+ return isset( $this->lastError['wasKnownStatementRollbackError'] )
+ ? $this->lastError['wasKnownStatementRollbackError']
+ : false;
}
function fieldInfo( $table, $field ) {
@@ -174,7 +211,7 @@ class DatabaseTestHelper extends Database {
return false;
}
- function affectedRows() {
+ function fetchAffectedRowCount() {
return -1;
}
@@ -191,7 +228,7 @@ class DatabaseTestHelper extends Database {
}
function isOpen() {
- return true;
+ return $this->conn ? true : false;
}
function ping( &$rtt = null ) {
@@ -200,12 +237,22 @@ class DatabaseTestHelper extends Database {
}
protected function closeConnection() {
- return false;
+ return true;
}
protected function doQuery( $sql ) {
+ $sql = preg_replace( '< /\* .+? \*/>', '', $sql );
+ $this->addSql( $sql );
+
+ if ( $this->nextError ) {
+ $this->lastError = $this->nextError;
+ $this->nextError = null;
+ return false;
+ }
+
$res = $this->nextResult;
$this->nextResult = [];
+ $this->lastError = null;
return new FakeResultWrapper( $res );
}
diff --git a/www/wiki/tests/phpunit/includes/db/LBFactoryTest.php b/www/wiki/tests/phpunit/includes/db/LBFactoryTest.php
index 1efeeebd..ed4c977f 100644
--- a/www/wiki/tests/phpunit/includes/db/LBFactoryTest.php
+++ b/www/wiki/tests/phpunit/includes/db/LBFactoryTest.php
@@ -1,10 +1,4 @@
<?php
-
-use Wikimedia\Rdbms\LBFactorySimple;
-use Wikimedia\Rdbms\LBFactoryMulti;
-use Wikimedia\Rdbms\ChronologyProtector;
-use Wikimedia\Rdbms\MySQLMasterPos;
-
/**
* Holds tests for LBFactory abstract MediaWiki class.
*
@@ -23,19 +17,33 @@ use Wikimedia\Rdbms\MySQLMasterPos;
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
- * @group Database
* @file
* @author Antoine Musso
* @copyright © 2013 Antoine Musso
* @copyright © 2013 Wikimedia Foundation Inc.
*/
+
+use Wikimedia\Rdbms\LBFactorySimple;
+use Wikimedia\Rdbms\LBFactoryMulti;
+use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\Rdbms\ChronologyProtector;
+use Wikimedia\Rdbms\DatabaseMysqli;
+use Wikimedia\Rdbms\MySQLMasterPos;
+use Wikimedia\Rdbms\DatabaseDomain;
+
+/**
+ * @group Database
+ * @covers \Wikimedia\Rdbms\LBFactorySimple
+ * @covers \Wikimedia\Rdbms\LBFactoryMulti
+ */
class LBFactoryTest extends MediaWikiTestCase {
/**
+ * @covers MWLBFactory::getLBFactoryClass
* @dataProvider getLBFactoryClassProvider
*/
public function testGetLBFactoryClass( $expected, $deprecated ) {
- $mockDB = $this->getMockBuilder( 'DatabaseMysqli' )
+ $mockDB = $this->getMockBuilder( DatabaseMysqli::class )
->disableOriginalConstructor()
->getMock();
@@ -123,7 +131,7 @@ class LBFactoryTest extends MediaWikiTestCase {
$factory = new LBFactorySimple( [
'servers' => $servers,
- 'loadMonitorClass' => 'LoadMonitorNull'
+ 'loadMonitorClass' => LoadMonitorNull::class
] );
$lb = $factory->getMainLB();
@@ -168,7 +176,7 @@ class LBFactoryTest extends MediaWikiTestCase {
'test-db1' => $wgDBserver,
'test-db2' => $wgDBserver
],
- 'loadMonitorClass' => 'LoadMonitorNull'
+ 'loadMonitorClass' => LoadMonitorNull::class
] );
$lb = $factory->getMainLB();
@@ -182,35 +190,66 @@ class LBFactoryTest extends MediaWikiTestCase {
$lb->closeAll();
}
+ /**
+ * @covers \Wikimedia\Rdbms\ChronologyProtector
+ */
public function testChronologyProtector() {
+ $now = microtime( true );
+
// (a) First HTTP request
- $mPos = new MySQLMasterPos( 'db1034-bin.000976', '843431247' );
+ $m1Pos = new MySQLMasterPos( 'db1034-bin.000976/843431247', $now );
+ $m2Pos = new MySQLMasterPos( 'db1064-bin.002400/794074907', $now );
- $now = microtime( true );
- $mockDB = $this->getMockBuilder( 'DatabaseMysqli' )
+ // Master DB 1
+ $mockDB1 = $this->getMockBuilder( DatabaseMysqli::class )
->disableOriginalConstructor()
->getMock();
- $mockDB->method( 'writesOrCallbacksPending' )->willReturn( true );
- $mockDB->method( 'lastDoneWrites' )->willReturn( $now );
- $mockDB->method( 'getMasterPos' )->willReturn( $mPos );
-
- $lb = $this->getMockBuilder( 'LoadBalancer' )
+ $mockDB1->method( 'writesOrCallbacksPending' )->willReturn( true );
+ $mockDB1->method( 'lastDoneWrites' )->willReturn( $now );
+ $mockDB1->method( 'getMasterPos' )->willReturn( $m1Pos );
+ // Load balancer for master DB 1
+ $lb1 = $this->getMockBuilder( LoadBalancer::class )
->disableOriginalConstructor()
->getMock();
- $lb->method( 'getConnection' )->willReturn( $mockDB );
- $lb->method( 'getServerCount' )->willReturn( 2 );
- $lb->method( 'parentInfo' )->willReturn( [ 'id' => "main-DEFAULT" ] );
- $lb->method( 'getAnyOpenConnection' )->willReturn( $mockDB );
- $lb->method( 'hasOrMadeRecentMasterChanges' )->will( $this->returnCallback(
- function () use ( $mockDB ) {
+ $lb1->method( 'getConnection' )->willReturn( $mockDB1 );
+ $lb1->method( 'getServerCount' )->willReturn( 2 );
+ $lb1->method( 'getAnyOpenConnection' )->willReturn( $mockDB1 );
+ $lb1->method( 'hasOrMadeRecentMasterChanges' )->will( $this->returnCallback(
+ function () use ( $mockDB1 ) {
$p = 0;
- $p |= call_user_func( [ $mockDB, 'writesOrCallbacksPending' ] );
- $p |= call_user_func( [ $mockDB, 'lastDoneWrites' ] );
+ $p |= call_user_func( [ $mockDB1, 'writesOrCallbacksPending' ] );
+ $p |= call_user_func( [ $mockDB1, 'lastDoneWrites' ] );
return (bool)$p;
}
) );
- $lb->method( 'getMasterPos' )->willReturn( $mPos );
+ $lb1->method( 'getMasterPos' )->willReturn( $m1Pos );
+ $lb1->method( 'getServerName' )->with( 0 )->willReturn( 'master1' );
+ // Master DB 2
+ $mockDB2 = $this->getMockBuilder( DatabaseMysqli::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mockDB2->method( 'writesOrCallbacksPending' )->willReturn( true );
+ $mockDB2->method( 'lastDoneWrites' )->willReturn( $now );
+ $mockDB2->method( 'getMasterPos' )->willReturn( $m2Pos );
+ // Load balancer for master DB 2
+ $lb2 = $this->getMockBuilder( LoadBalancer::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $lb2->method( 'getConnection' )->willReturn( $mockDB2 );
+ $lb2->method( 'getServerCount' )->willReturn( 2 );
+ $lb2->method( 'getAnyOpenConnection' )->willReturn( $mockDB2 );
+ $lb2->method( 'hasOrMadeRecentMasterChanges' )->will( $this->returnCallback(
+ function () use ( $mockDB2 ) {
+ $p = 0;
+ $p |= call_user_func( [ $mockDB2, 'writesOrCallbacksPending' ] );
+ $p |= call_user_func( [ $mockDB2, 'lastDoneWrites' ] );
+
+ return (bool)$p;
+ }
+ ) );
+ $lb2->method( 'getMasterPos' )->willReturn( $m2Pos );
+ $lb2->method( 'getServerName' )->with( 0 )->willReturn( 'master2' );
$bag = new HashBagOStuff();
$cp = new ChronologyProtector(
@@ -221,36 +260,65 @@ class LBFactoryTest extends MediaWikiTestCase {
]
);
- $mockDB->expects( $this->exactly( 2 ) )->method( 'writesOrCallbacksPending' );
- $mockDB->expects( $this->exactly( 2 ) )->method( 'lastDoneWrites' );
+ $mockDB1->expects( $this->exactly( 1 ) )->method( 'writesOrCallbacksPending' );
+ $mockDB1->expects( $this->exactly( 1 ) )->method( 'lastDoneWrites' );
+ $mockDB2->expects( $this->exactly( 1 ) )->method( 'writesOrCallbacksPending' );
+ $mockDB2->expects( $this->exactly( 1 ) )->method( 'lastDoneWrites' );
+
+ // Nothing to wait for on first HTTP request start
+ $cp->initLB( $lb1 );
+ $cp->initLB( $lb2 );
+ // Record positions in stash on first HTTP request end
+ $cp->shutdownLB( $lb1 );
+ $cp->shutdownLB( $lb2 );
+ $cpIndex = null;
+ $cp->shutdown( null, 'sync', $cpIndex );
- // Nothing to wait for
- $cp->initLB( $lb );
- // Record in stash
- $cp->shutdownLB( $lb );
- $cp->shutdown();
+ $this->assertEquals( 1, $cpIndex, "CP write index set" );
// (b) Second HTTP request
+
+ // Load balancer for master DB 1
+ $lb1 = $this->getMockBuilder( LoadBalancer::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $lb1->method( 'getServerCount' )->willReturn( 2 );
+ $lb1->method( 'getServerName' )->with( 0 )->willReturn( 'master1' );
+ $lb1->expects( $this->once() )
+ ->method( 'waitFor' )->with( $this->equalTo( $m1Pos ) );
+ // Load balancer for master DB 2
+ $lb2 = $this->getMockBuilder( LoadBalancer::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $lb2->method( 'getServerCount' )->willReturn( 2 );
+ $lb2->method( 'getServerName' )->with( 0 )->willReturn( 'master2' );
+ $lb2->expects( $this->once() )
+ ->method( 'waitFor' )->with( $this->equalTo( $m2Pos ) );
+
$cp = new ChronologyProtector(
$bag,
[
'ip' => '127.0.0.1',
'agent' => "Totally-Not-FireFox"
- ]
+ ],
+ $cpIndex
);
- $lb->expects( $this->once() )
- ->method( 'waitFor' )->with( $this->equalTo( $mPos ) );
+ // Wait for last positions to be reached on second HTTP request start
+ $cp->initLB( $lb1 );
+ $cp->initLB( $lb2 );
+ // Shutdown (nothing to record)
+ $cp->shutdownLB( $lb1 );
+ $cp->shutdownLB( $lb2 );
+ $cpIndex = null;
+ $cp->shutdown( null, 'sync', $cpIndex );
- // Wait
- $cp->initLB( $lb );
- // Record in stash
- $cp->shutdownLB( $lb );
- $cp->shutdown();
+ $this->assertEquals( null, $cpIndex, "CP write index retained" );
}
private function newLBFactoryMulti( array $baseOverride = [], array $serverOverride = [] ) {
- global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgSQLiteDataDir;
+ global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBprefix, $wgDBtype;
+ global $wgSQLiteDataDir;
return new LBFactoryMulti( $baseOverride + [
'sectionsByDB' => [],
@@ -261,6 +329,7 @@ class LBFactoryTest extends MediaWikiTestCase {
],
'serverTemplate' => $serverOverride + [
'dbname' => $wgDBname,
+ 'tablePrefix' => $wgDBprefix,
'user' => $wgDBuser,
'password' => $wgDBpassword,
'type' => $wgDBtype,
@@ -270,69 +339,74 @@ class LBFactoryTest extends MediaWikiTestCase {
'hostsByName' => [
'test-db1' => $wgDBserver,
],
- 'loadMonitorClass' => 'LoadMonitorNull',
- 'localDomain' => wfWikiID()
+ 'loadMonitorClass' => LoadMonitorNull::class,
+ 'localDomain' => new DatabaseDomain( $wgDBname, null, $wgDBprefix )
] );
}
public function testNiceDomains() {
- global $wgDBname, $wgDBtype;
-
- if ( $wgDBtype === 'sqlite' ) {
- $tmpDir = $this->getNewTempDirectory();
- $dbPath = "$tmpDir/unit_test_db.sqlite";
- file_put_contents( $dbPath, '' );
- $tempFsFile = new TempFSFile( $dbPath );
- $tempFsFile->autocollect();
- } else {
- $dbPath = null;
+ global $wgDBname;
+
+ if ( wfGetDB( DB_MASTER )->databasesAreIndependent() ) {
+ self::markTestSkipped( "Skipping tests about selecting DBs: not applicable" );
+ return;
}
$factory = $this->newLBFactoryMulti(
[],
- [ 'dbFilePath' => $dbPath ]
+ []
);
$lb = $factory->getMainLB();
- if ( $wgDBtype !== 'sqlite' ) {
- $db = $lb->getConnectionRef( DB_MASTER );
- $this->assertEquals(
- $wgDBname,
- $db->getDomainID()
- );
- unset( $db );
- }
+ $db = $lb->getConnectionRef( DB_MASTER );
+ $this->assertEquals(
+ wfWikiID(),
+ $db->getDomainID()
+ );
+ unset( $db );
/** @var Database $db */
$db = $lb->getConnection( DB_MASTER, [], '' );
- $lb->reuseConnection( $db ); // don't care
$this->assertEquals(
'',
- $db->getDomainID()
+ $db->getDomainId(),
+ 'Null domain ID handle used'
+ );
+ $this->assertEquals(
+ '',
+ $db->getDBname(),
+ 'Null domain ID handle used'
+ );
+ $this->assertEquals(
+ '',
+ $db->tablePrefix(),
+ 'Main domain ID handle used; prefix is empty though'
);
-
$this->assertEquals(
$this->quoteTable( $db, 'page' ),
$db->tableName( 'page' ),
"Correct full table name"
);
-
$this->assertEquals(
$this->quoteTable( $db, $wgDBname ) . '.' . $this->quoteTable( $db, 'page' ),
$db->tableName( "$wgDBname.page" ),
"Correct full table name"
);
-
$this->assertEquals(
$this->quoteTable( $db, 'nice_db' ) . '.' . $this->quoteTable( $db, 'page' ),
$db->tableName( 'nice_db.page' ),
"Correct full table name"
);
+ $lb->reuseConnection( $db ); // don't care
+
+ $db = $lb->getConnection( DB_MASTER ); // local domain connection
$factory->setDomainPrefix( 'my_' );
+
+ $this->assertEquals( $wgDBname, $db->getDBname() );
$this->assertEquals(
- '',
+ "$wgDBname-my_",
$db->getDomainID()
);
$this->assertEquals(
@@ -351,32 +425,25 @@ class LBFactoryTest extends MediaWikiTestCase {
}
public function testTrickyDomain() {
- global $wgDBtype;
-
- if ( $wgDBtype === 'sqlite' ) {
- $tmpDir = $this->getNewTempDirectory();
- $dbPath = "$tmpDir/unit_test_db.sqlite";
- file_put_contents( $dbPath, '' );
- $tempFsFile = new TempFSFile( $dbPath );
- $tempFsFile->autocollect();
- } else {
- $dbPath = null;
+ global $wgDBname;
+
+ if ( wfGetDB( DB_MASTER )->databasesAreIndependent() ) {
+ self::markTestSkipped( "Skipping tests about selecting DBs: not applicable" );
+ return;
}
- $dbname = 'unittest-domain';
+ $dbname = 'unittest-domain'; // explodes if DB is selected
$factory = $this->newLBFactoryMulti(
- [ 'localDomain' => $dbname ],
- [ 'dbname' => $dbname, 'dbFilePath' => $dbPath ]
+ [ 'localDomain' => ( new DatabaseDomain( $dbname, null, '' ) )->getId() ],
+ [
+ 'dbName' => 'do_not_select_me' // explodes if DB is selected
+ ]
);
$lb = $factory->getMainLB();
/** @var Database $db */
$db = $lb->getConnection( DB_MASTER, [], '' );
- $lb->reuseConnection( $db ); // don't care
- $this->assertEquals(
- '',
- $db->getDomainID()
- );
+ $this->assertEquals( '', $db->getDomainID(), "Null domain used" );
$this->assertEquals(
$this->quoteTable( $db, 'page' ),
@@ -396,7 +463,10 @@ class LBFactoryTest extends MediaWikiTestCase {
"Correct full table name"
);
+ $lb->reuseConnection( $db ); // don't care
+
$factory->setDomainPrefix( 'my_' );
+ $db = $lb->getConnection( DB_MASTER, [], "$wgDBname-my_" );
$this->assertEquals(
$this->quoteTable( $db, 'my_page' ),
@@ -408,30 +478,46 @@ class LBFactoryTest extends MediaWikiTestCase {
$db->tableName( 'other_nice_db.page' ),
"Correct full table name"
);
-
$this->assertEquals(
$this->quoteTable( $db, 'garbage-db' ) . '.' . $this->quoteTable( $db, 'page' ),
$db->tableName( 'garbage-db.page' ),
"Correct full table name"
);
- if ( $db->databasesAreIndependent() ) {
+ $lb->reuseConnection( $db ); // don't care
+
+ $factory->closeAll();
+ $factory->destroy();
+ }
+
+ public function testInvalidSelectDB() {
+ $dbname = 'unittest-domain'; // explodes if DB is selected
+ $factory = $this->newLBFactoryMulti(
+ [ 'localDomain' => ( new DatabaseDomain( $dbname, null, '' ) )->getId() ],
+ [
+ 'dbName' => 'do_not_select_me' // explodes if DB is selected
+ ]
+ );
+ $lb = $factory->getMainLB();
+ /** @var Database $db */
+ $db = $lb->getConnection( DB_MASTER, [], '' );
+
+ if ( $db->getType() === 'sqlite' ) {
+ $this->assertFalse( $db->selectDB( 'garbage-db' ) );
+ } elseif ( $db->databasesAreIndependent() ) {
try {
$e = null;
$db->selectDB( 'garbage-db' );
} catch ( \Wikimedia\Rdbms\DBConnectionError $e ) {
// expected
}
- $this->assertInstanceOf( '\Wikimedia\Rdbms\DBConnectionError', $e );
+ $this->assertInstanceOf( \Wikimedia\Rdbms\DBConnectionError::class, $e );
$this->assertFalse( $db->isOpen() );
} else {
- \MediaWiki\suppressWarnings();
+ \Wikimedia\suppressWarnings();
$this->assertFalse( $db->selectDB( 'garbage-db' ) );
- \MediaWiki\restoreWarnings();
+ \Wikimedia\restoreWarnings();
}
-
- $factory->closeAll();
- $factory->destroy();
}
private function quoteTable( Database $db, $table ) {
diff --git a/www/wiki/tests/phpunit/includes/db/LoadBalancerTest.php b/www/wiki/tests/phpunit/includes/db/LoadBalancerTest.php
index f8ab7f48..e054569d 100644
--- a/www/wiki/tests/phpunit/includes/db/LoadBalancerTest.php
+++ b/www/wiki/tests/phpunit/includes/db/LoadBalancerTest.php
@@ -1,7 +1,5 @@
<?php
-use Wikimedia\Rdbms\LoadBalancer;
-
/**
* Holds tests for LoadBalancer MediaWiki class.
*
@@ -20,62 +18,94 @@ use Wikimedia\Rdbms\LoadBalancer;
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
- * @group Database
* @file
*/
+
+use Wikimedia\Rdbms\DBError;
+use Wikimedia\Rdbms\DatabaseDomain;
+use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\Rdbms\LoadMonitorNull;
+
+/**
+ * @group Database
+ * @covers \Wikimedia\Rdbms\LoadBalancer
+ */
class LoadBalancerTest extends MediaWikiTestCase {
- public function testLBSimpleServer() {
+ private function makeServerConfig() {
global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
- $servers = [
- [
- 'host' => $wgDBserver,
- 'dbname' => $wgDBname,
- 'user' => $wgDBuser,
- 'password' => $wgDBpassword,
- 'type' => $wgDBtype,
- 'dbDirectory' => $wgSQLiteDataDir,
- 'load' => 0,
- 'flags' => DBO_TRX // REPEATABLE-READ for consistency
- ],
+ return [
+ 'host' => $wgDBserver,
+ 'dbname' => $wgDBname,
+ 'tablePrefix' => $this->dbPrefix(),
+ 'user' => $wgDBuser,
+ 'password' => $wgDBpassword,
+ 'type' => $wgDBtype,
+ 'dbDirectory' => $wgSQLiteDataDir,
+ 'load' => 0,
+ 'flags' => DBO_TRX // REPEATABLE-READ for consistency
];
+ }
+
+ public function testWithoutReplica() {
+ global $wgDBname;
+ $called = false;
$lb = new LoadBalancer( [
- 'servers' => $servers,
- 'localDomain' => wfWikiID()
+ 'servers' => [ $this->makeServerConfig() ],
+ 'queryLogger' => MediaWiki\Logger\LoggerFactory::getInstance( 'DBQuery' ),
+ 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
+ 'chronologyCallback' => function () use ( &$called ) {
+ $called = true;
+ }
] );
+ $ld = DatabaseDomain::newFromId( $lb->getLocalDomainID() );
+ $this->assertEquals( $wgDBname, $ld->getDatabase(), 'local domain DB set' );
+ $this->assertEquals( $this->dbPrefix(), $ld->getTablePrefix(), 'local domain prefix set' );
+
+ $this->assertFalse( $called );
$dbw = $lb->getConnection( DB_MASTER );
+ $this->assertTrue( $called );
$this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
$this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on master" );
+ $this->assertWriteAllowed( $dbw );
$dbr = $lb->getConnection( DB_REPLICA );
$this->assertTrue( $dbr->getLBInfo( 'master' ), 'DB_REPLICA also gets the master' );
- $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on replica" );
+ $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on replica" );
- $dbwAuto = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTO );
- $this->assertFalse( $dbwAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTO" );
- $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on master" );
- $this->assertNotEquals( $dbw, $dbwAuto, "CONN_TRX_AUTO uses separate connection" );
+ if ( !$lb->getServerAttributes( $lb->getWriterIndex() )[$dbw::ATTR_DB_LEVEL_LOCKING] ) {
+ $dbwAuto = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
+ $this->assertFalse(
+ $dbwAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
+ $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on master" );
+ $this->assertNotEquals(
+ $dbw, $dbwAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
- $dbrAuto = $lb->getConnection( DB_REPLICA, [], false, $lb::CONN_TRX_AUTO );
- $this->assertFalse( $dbrAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTO" );
- $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on replica" );
- $this->assertNotEquals( $dbr, $dbrAuto, "CONN_TRX_AUTO uses separate connection" );
+ $dbrAuto = $lb->getConnection( DB_REPLICA, [], false, $lb::CONN_TRX_AUTOCOMMIT );
+ $this->assertFalse(
+ $dbrAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
+ $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on replica" );
+ $this->assertNotEquals(
+ $dbr, $dbrAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
- $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTO );
- $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTO reuses connections" );
+ $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
+ $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTOCOMMIT reuses connections" );
+ }
$lb->closeAll();
}
- public function testLBSimpleServers() {
+ public function testWithReplica() {
global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
$servers = [
[ // master
'host' => $wgDBserver,
'dbname' => $wgDBname,
+ 'tablePrefix' => $this->dbPrefix(),
'user' => $wgDBuser,
'password' => $wgDBpassword,
'type' => $wgDBtype,
@@ -83,9 +113,10 @@ class LoadBalancerTest extends MediaWikiTestCase {
'load' => 0,
'flags' => DBO_TRX // REPEATABLE-READ for consistency
],
- [ // emulated slave
+ [ // emulated replica
'host' => $wgDBserver,
'dbname' => $wgDBname,
+ 'tablePrefix' => $this->dbPrefix(),
'user' => $wgDBuser,
'password' => $wgDBpassword,
'type' => $wgDBtype,
@@ -97,8 +128,9 @@ class LoadBalancerTest extends MediaWikiTestCase {
$lb = new LoadBalancer( [
'servers' => $servers,
- 'localDomain' => wfWikiID(),
- 'loadMonitorClass' => 'LoadMonitorNull'
+ 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
+ 'queryLogger' => MediaWiki\Logger\LoggerFactory::getInstance( 'DBQuery' ),
+ 'loadMonitorClass' => LoadMonitorNull::class
] );
$dbw = $lb->getConnection( DB_MASTER );
@@ -108,27 +140,165 @@ class LoadBalancerTest extends MediaWikiTestCase {
$dbw->getLBInfo( 'clusterMasterHost' ),
'cluster master set' );
$this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on master" );
+ $this->assertWriteAllowed( $dbw );
$dbr = $lb->getConnection( DB_REPLICA );
- $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'slave shows as slave' );
+ $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'replica shows as replica' );
$this->assertEquals(
( $wgDBserver != '' ) ? $wgDBserver : 'localhost',
$dbr->getLBInfo( 'clusterMasterHost' ),
'cluster master set' );
- $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on replica" );
+ $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on replica" );
+ $this->assertWriteForbidden( $dbr );
+
+ if ( !$lb->getServerAttributes( $lb->getWriterIndex() )[$dbw::ATTR_DB_LEVEL_LOCKING] ) {
+ $dbwAuto = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
+ $this->assertFalse(
+ $dbwAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
+ $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on master" );
+ $this->assertNotEquals(
+ $dbw, $dbwAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
+
+ $dbrAuto = $lb->getConnection( DB_REPLICA, [], false, $lb::CONN_TRX_AUTOCOMMIT );
+ $this->assertFalse(
+ $dbrAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
+ $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on replica" );
+ $this->assertNotEquals(
+ $dbr, $dbrAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
+
+ $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
+ $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTOCOMMIT reuses connections" );
+ }
+
+ $lb->closeAll();
+ }
+
+ private function assertWriteForbidden( Database $db ) {
+ try {
+ $db->delete( 'some_table', [ 'id' => 57634126 ], __METHOD__ );
+ $this->fail( 'Write operation should have failed!' );
+ } catch ( DBError $ex ) {
+ // check that the exception message contains "Write operation"
+ $constraint = new PHPUnit_Framework_Constraint_StringContains( 'Write operation' );
- $dbwAuto = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTO );
- $this->assertFalse( $dbwAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTO" );
- $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on master" );
- $this->assertNotEquals( $dbw, $dbwAuto, "CONN_TRX_AUTO uses separate connection" );
+ if ( !$constraint->evaluate( $ex->getMessage(), '', true ) ) {
+ // re-throw original error, to preserve stack trace
+ throw $ex;
+ }
+ }
+ }
+
+ private function assertWriteAllowed( Database $db ) {
+ $table = $db->tableName( 'some_table' );
+ try {
+ $db->dropTable( 'some_table' ); // clear for sanity
+
+ // Trigger DBO_TRX to create a transaction so the flush below will
+ // roll everything here back in sqlite. But don't actually do the
+ // code below inside an atomic section becaue MySQL and Oracle
+ // auto-commit transactions for DDL statements like CREATE TABLE.
+ $db->startAtomic( __METHOD__ );
+ $db->endAtomic( __METHOD__ );
+
+ // Use only basic SQL and trivial types for these queries for compatibility
+ $this->assertNotSame(
+ false,
+ $db->query( "CREATE TABLE $table (id INT, time INT)", __METHOD__ ),
+ "table created"
+ );
+ $this->assertNotSame(
+ false,
+ $db->query( "DELETE FROM $table WHERE id=57634126", __METHOD__ ),
+ "delete query"
+ );
+ } finally {
+ // Drop the table to clean up, ignoring any error.
+ $db->query( "DROP TABLE $table", __METHOD__, true );
+ // Rollback the DBO_TRX transaction for sqlite's benefit.
+ $db->rollback( __METHOD__, 'flush' );
+ }
+ }
+
+ public function testServerAttributes() {
+ $servers = [
+ [ // master
+ 'dbname' => 'my_unittest_wiki',
+ 'tablePrefix' => 'unittest_',
+ 'type' => 'sqlite',
+ 'dbDirectory' => "some_directory",
+ 'load' => 0
+ ]
+ ];
+
+ $lb = new LoadBalancer( [
+ 'servers' => $servers,
+ 'localDomain' => new DatabaseDomain( 'my_unittest_wiki', null, 'unittest_' ),
+ 'loadMonitorClass' => LoadMonitorNull::class
+ ] );
+
+ $this->assertTrue( $lb->getServerAttributes( 0 )[Database::ATTR_DB_LEVEL_LOCKING] );
- $dbrAuto = $lb->getConnection( DB_REPLICA, [], false, $lb::CONN_TRX_AUTO );
- $this->assertFalse( $dbrAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTO" );
- $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on replica" );
- $this->assertNotEquals( $dbr, $dbrAuto, "CONN_TRX_AUTO uses separate connection" );
+ $servers = [
+ [ // master
+ 'host' => 'db1001',
+ 'user' => 'wikiuser',
+ 'password' => 'none',
+ 'dbname' => 'my_unittest_wiki',
+ 'tablePrefix' => 'unittest_',
+ 'type' => 'mysql',
+ 'load' => 100
+ ],
+ [ // emulated replica
+ 'host' => 'db1002',
+ 'user' => 'wikiuser',
+ 'password' => 'none',
+ 'dbname' => 'my_unittest_wiki',
+ 'tablePrefix' => 'unittest_',
+ 'type' => 'mysql',
+ 'load' => 100
+ ]
+ ];
+
+ $lb = new LoadBalancer( [
+ 'servers' => $servers,
+ 'localDomain' => new DatabaseDomain( 'my_unittest_wiki', null, 'unittest_' ),
+ 'loadMonitorClass' => LoadMonitorNull::class
+ ] );
+
+ $this->assertFalse( $lb->getServerAttributes( 1 )[Database::ATTR_DB_LEVEL_LOCKING] );
+ }
+
+ /**
+ * @covers LoadBalancer::openConnection()
+ * @covers LoadBalancer::getAnyOpenConnection()
+ */
+ function testOpenConnection() {
+ global $wgDBname;
+
+ $lb = new LoadBalancer( [
+ 'servers' => [ $this->makeServerConfig() ],
+ 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() )
+ ] );
- $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTO );
- $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTO reuses connections" );
+ $i = $lb->getWriterIndex();
+ $this->assertEquals( null, $lb->getAnyOpenConnection( $i ) );
+ $conn1 = $lb->getConnection( $i );
+ $this->assertNotEquals( null, $conn1 );
+ $this->assertEquals( $conn1, $lb->getAnyOpenConnection( $i ) );
+ $conn2 = $lb->getConnection( $i, [], false, $lb::CONN_TRX_AUTOCOMMIT );
+ $this->assertNotEquals( null, $conn2 );
+ if ( $lb->getServerAttributes( $i )[Database::ATTR_DB_LEVEL_LOCKING] ) {
+ $this->assertEquals( null,
+ $lb->getAnyOpenConnection( $i, $lb::CONN_TRX_AUTOCOMMIT ) );
+ $this->assertEquals( $conn1,
+ $lb->getConnection(
+ $i, [], false, $lb::CONN_TRX_AUTOCOMMIT ), $lb::CONN_TRX_AUTOCOMMIT );
+ } else {
+ $this->assertEquals( $conn2,
+ $lb->getAnyOpenConnection( $i, $lb::CONN_TRX_AUTOCOMMIT ) );
+ $this->assertEquals( $conn2,
+ $lb->getConnection( $i, [], false, $lb::CONN_TRX_AUTOCOMMIT ) );
+ }
$lb->closeAll();
}
diff --git a/www/wiki/tests/phpunit/includes/debug/MWDebugTest.php b/www/wiki/tests/phpunit/includes/debug/MWDebugTest.php
index 5c654831..6f0b1db9 100644
--- a/www/wiki/tests/phpunit/includes/debug/MWDebugTest.php
+++ b/www/wiki/tests/phpunit/includes/debug/MWDebugTest.php
@@ -11,13 +11,13 @@ class MWDebugTest extends MediaWikiTestCase {
public static function setUpBeforeClass() {
parent::setUpBeforeClass();
MWDebug::init();
- MediaWiki\suppressWarnings();
+ Wikimedia\suppressWarnings();
}
public static function tearDownAfterClass() {
parent::tearDownAfterClass();
MWDebug::deinit();
- MediaWiki\restoreWarnings();
+ Wikimedia\restoreWarnings();
}
/**
@@ -99,7 +99,7 @@ class MWDebugTest extends MediaWikiTestCase {
MWDebug::appendDebugInfoToApiResult( $context, $result );
- $this->assertInstanceOf( 'ApiResult', $result );
+ $this->assertInstanceOf( ApiResult::class, $result );
$data = $result->getResultData();
$expectedKeys = [ 'mwVersion', 'phpEngine', 'phpVersion', 'gitRevision', 'gitBranch',
@@ -110,7 +110,7 @@ class MWDebugTest extends MediaWikiTestCase {
$this->assertArrayHasKey( $expectedKey, $data['debuginfo'], "debuginfo has $expectedKey" );
}
- $xml = ApiFormatXml::recXmlPrint( 'help', $data );
+ $xml = ApiFormatXml::recXmlPrint( 'help', $data, null );
// exception not thrown
$this->assertInternalType( 'string', $xml );
@@ -123,7 +123,7 @@ class MWDebugTest extends MediaWikiTestCase {
* @return FauxRequest
*/
private function newApiRequest( array $params, $requestUrl ) {
- $request = $this->getMockBuilder( 'FauxRequest' )
+ $request = $this->getMockBuilder( FauxRequest::class )
->setMethods( [ 'getRequestURL' ] )
->setConstructorArgs( [
$params
diff --git a/www/wiki/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php b/www/wiki/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php
index 938397ab..baa4df73 100644
--- a/www/wiki/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php
+++ b/www/wiki/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php
@@ -23,6 +23,9 @@ namespace MediaWiki\Logger\Monolog;
use MediaWikiTestCase;
use PHPUnit_Framework_Error_Notice;
+/**
+ * @covers \MediaWiki\Logger\Monolog\AvroFormatter
+ */
class AvroFormatterTest extends MediaWikiTestCase {
protected function setUp() {
@@ -47,9 +50,9 @@ class AvroFormatterTest extends MediaWikiTestCase {
// disable conversion of notices
PHPUnit_Framework_Error_Notice::$enabled = false;
// have to keep the user notice from being output
- \MediaWiki\suppressWarnings();
+ \Wikimedia\suppressWarnings();
$res = $formatter->format( [ 'channel' => 'marty' ] );
- \MediaWiki\restoreWarnings();
+ \Wikimedia\restoreWarnings();
PHPUnit_Framework_Error_Notice::$enabled = $noticeEnabled;
$this->assertNull( $res );
}
diff --git a/www/wiki/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php b/www/wiki/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php
index 88cd2dd2..4c0ca04f 100644
--- a/www/wiki/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php
+++ b/www/wiki/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php
@@ -24,6 +24,9 @@ use MediaWikiTestCase;
use Monolog\Logger;
use Wikimedia\TestingAccessWrapper;
+/**
+ * @covers \MediaWiki\Logger\Monolog\KafkaHandler
+ */
class KafkaHandlerTest extends MediaWikiTestCase {
protected function setUp() {
@@ -155,13 +158,14 @@ class KafkaHandlerTest extends MediaWikiTestCase {
->method( 'send' )
->will( $this->returnValue( true ) );
// evil hax
- TestingAccessWrapper::newFromObject( $mockMethod )->matcher->parametersMatcher =
+ $matcher = TestingAccessWrapper::newFromObject( $mockMethod )->matcher;
+ TestingAccessWrapper::newFromObject( $matcher )->parametersMatcher =
new \PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters( [
[ $this->anything(), $this->anything(), [ 'words' ] ],
[ $this->anything(), $this->anything(), [ 'lines' ] ]
] );
- $formatter = $this->createMock( 'Monolog\Formatter\FormatterInterface' );
+ $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class );
$formatter->expects( $this->any() )
->method( 'format' )
->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) );
@@ -192,7 +196,7 @@ class KafkaHandlerTest extends MediaWikiTestCase {
->method( 'send' )
->will( $this->returnValue( true ) );
- $formatter = $this->createMock( 'Monolog\Formatter\FormatterInterface' );
+ $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class );
$formatter->expects( $this->any() )
->method( 'format' )
->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) );
diff --git a/www/wiki/tests/phpunit/includes/debug/logger/monolog/LogstashFormatterTest.php b/www/wiki/tests/phpunit/includes/debug/logger/monolog/LogstashFormatterTest.php
index 8086b4bf..1ee188e7 100644
--- a/www/wiki/tests/phpunit/includes/debug/logger/monolog/LogstashFormatterTest.php
+++ b/www/wiki/tests/phpunit/includes/debug/logger/monolog/LogstashFormatterTest.php
@@ -2,7 +2,7 @@
namespace MediaWiki\Logger\Monolog;
-class LogstashFormatterTest extends \PHPUnit_Framework_TestCase {
+class LogstashFormatterTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideV1
* @param array $record The input record.
diff --git a/www/wiki/tests/phpunit/includes/deferred/CdnCacheUpdateTest.php b/www/wiki/tests/phpunit/includes/deferred/CdnCacheUpdateTest.php
index 11b869a7..f3c949d3 100644
--- a/www/wiki/tests/phpunit/includes/deferred/CdnCacheUpdateTest.php
+++ b/www/wiki/tests/phpunit/includes/deferred/CdnCacheUpdateTest.php
@@ -3,6 +3,10 @@
use Wikimedia\TestingAccessWrapper;
class CdnCacheUpdateTest extends MediaWikiTestCase {
+
+ /**
+ * @covers CdnCacheUpdate::merge
+ */
public function testPurgeMergeWeb() {
$this->setMwGlobals( 'wgCommandLineMode', false );
diff --git a/www/wiki/tests/phpunit/includes/deferred/DeferredUpdatesTest.php b/www/wiki/tests/phpunit/includes/deferred/DeferredUpdatesTest.php
index 3b423563..6b417073 100644
--- a/www/wiki/tests/phpunit/includes/deferred/DeferredUpdatesTest.php
+++ b/www/wiki/tests/phpunit/includes/deferred/DeferredUpdatesTest.php
@@ -1,12 +1,68 @@
<?php
+use MediaWiki\MediaWikiServices;
+
class DeferredUpdatesTest extends MediaWikiTestCase {
/**
+ * @covers DeferredUpdates::addUpdate
+ * @covers DeferredUpdates::push
+ * @covers DeferredUpdates::doUpdates
+ * @covers DeferredUpdates::execute
+ * @covers DeferredUpdates::runUpdate
+ */
+ public function testAddAndRun() {
+ $update = $this->getMockBuilder( DeferrableUpdate::class )
+ ->setMethods( [ 'doUpdate' ] )->getMock();
+ $update->expects( $this->once() )->method( 'doUpdate' );
+
+ DeferredUpdates::addUpdate( $update );
+ DeferredUpdates::doUpdates();
+ }
+
+ /**
+ * @covers DeferredUpdates::addUpdate
+ * @covers DeferredUpdates::push
+ */
+ public function testAddMergeable() {
+ $this->setMwGlobals( 'wgCommandLineMode', false );
+
+ $update1 = $this->getMockBuilder( MergeableUpdate::class )
+ ->setMethods( [ 'merge', 'doUpdate' ] )->getMock();
+ $update1->expects( $this->once() )->method( 'merge' );
+ $update1->expects( $this->never() )->method( 'doUpdate' );
+
+ $update2 = $this->getMockBuilder( MergeableUpdate::class )
+ ->setMethods( [ 'merge', 'doUpdate' ] )->getMock();
+ $update2->expects( $this->never() )->method( 'merge' );
+ $update2->expects( $this->never() )->method( 'doUpdate' );
+
+ DeferredUpdates::addUpdate( $update1 );
+ DeferredUpdates::addUpdate( $update2 );
+ }
+
+ /**
+ * @covers DeferredUpdates::addCallableUpdate
+ * @covers MWCallableUpdate::getOrigin
+ */
+ public function testAddCallableUpdate() {
+ $this->setMwGlobals( 'wgCommandLineMode', true );
+
+ $ran = 0;
+ DeferredUpdates::addCallableUpdate( function () use ( &$ran ) {
+ $ran++;
+ } );
+ DeferredUpdates::doUpdates();
+
+ $this->assertSame( 1, $ran, 'Update ran' );
+ }
+
+ /**
* @covers DeferredUpdates::getPendingUpdates
+ * @covers DeferredUpdates::clearPendingUpdates
*/
public function testGetPendingUpdates() {
- # Prevent updates from running
+ // Prevent updates from running
$this->setMwGlobals( 'wgCommandLineMode', false );
$pre = DeferredUpdates::PRESEND;
@@ -34,6 +90,11 @@ class DeferredUpdatesTest extends MediaWikiTestCase {
$this->assertCount( 0, DeferredUpdates::getPendingUpdates() );
}
+ /**
+ * @covers DeferredUpdates::doUpdates
+ * @covers DeferredUpdates::execute
+ * @covers DeferredUpdates::addUpdate
+ */
public function testDoUpdatesWeb() {
$this->setMwGlobals( 'wgCommandLineMode', false );
@@ -126,6 +187,11 @@ class DeferredUpdatesTest extends MediaWikiTestCase {
$this->assertEquals( "Marychu", $y, "POSTSEND update ran" );
}
+ /**
+ * @covers DeferredUpdates::doUpdates
+ * @covers DeferredUpdates::execute
+ * @covers DeferredUpdates::addUpdate
+ */
public function testDoUpdatesCLI() {
$this->setMwGlobals( 'wgCommandLineMode', true );
$updates = [
@@ -140,7 +206,9 @@ class DeferredUpdatesTest extends MediaWikiTestCase {
'3-2-1' => "deferred update 1 within deferred update 2 with deferred update 3;\n",
];
- wfGetLBFactory()->commitMasterChanges( __METHOD__ ); // clear anything
+ // clear anything
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $lbFactory->commitMasterChanges( __METHOD__ );
DeferredUpdates::addCallableUpdate(
function () use ( $updates ) {
@@ -193,12 +261,19 @@ class DeferredUpdatesTest extends MediaWikiTestCase {
DeferredUpdates::doUpdates();
}
+ /**
+ * @covers DeferredUpdates::doUpdates
+ * @covers DeferredUpdates::execute
+ * @covers DeferredUpdates::addUpdate
+ */
public function testPresendAddOnPostsendRun() {
$this->setMwGlobals( 'wgCommandLineMode', true );
$x = false;
$y = false;
- wfGetLBFactory()->commitMasterChanges( __METHOD__ ); // clear anything
+ // clear anything
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $lbFactory->commitMasterChanges( __METHOD__ );
DeferredUpdates::addCallableUpdate(
function () use ( &$x, &$y ) {
@@ -218,4 +293,46 @@ class DeferredUpdatesTest extends MediaWikiTestCase {
$this->assertTrue( $x, "Outer POSTSEND update ran" );
$this->assertTrue( $y, "Nested PRESEND update ran" );
}
+
+ /**
+ * @covers DeferredUpdates::runUpdate
+ */
+ public function testRunUpdateTransactionScope() {
+ $this->setMwGlobals( 'wgCommandLineMode', false );
+
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $this->assertFalse( $lbFactory->hasTransactionRound(), 'Initial state' );
+
+ $ran = 0;
+ DeferredUpdates::addCallableUpdate( function () use ( &$ran, $lbFactory ) {
+ $ran++;
+ $this->assertTrue( $lbFactory->hasTransactionRound(), 'Has transaction' );
+ } );
+ DeferredUpdates::doUpdates();
+
+ $this->assertSame( 1, $ran, 'Update ran' );
+ $this->assertFalse( $lbFactory->hasTransactionRound(), 'Final state' );
+ }
+
+ /**
+ * @covers DeferredUpdates::runUpdate
+ * @covers TransactionRoundDefiningUpdate::getOrigin
+ */
+ public function testRunOuterScopeUpdate() {
+ $this->setMwGlobals( 'wgCommandLineMode', false );
+
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $this->assertFalse( $lbFactory->hasTransactionRound(), 'Initial state' );
+
+ $ran = 0;
+ DeferredUpdates::addUpdate( new TransactionRoundDefiningUpdate(
+ function () use ( &$ran, $lbFactory ) {
+ $ran++;
+ $this->assertFalse( $lbFactory->hasTransactionRound(), 'No transaction' );
+ } )
+ );
+ DeferredUpdates::doUpdates();
+
+ $this->assertSame( 1, $ran, 'Update ran' );
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/deferred/LinksUpdateTest.php b/www/wiki/tests/phpunit/includes/deferred/LinksUpdateTest.php
index 4c0a5fa9..ddc0798f 100644
--- a/www/wiki/tests/phpunit/includes/deferred/LinksUpdateTest.php
+++ b/www/wiki/tests/phpunit/includes/deferred/LinksUpdateTest.php
@@ -1,6 +1,7 @@
<?php
/**
+ * @covers LinksUpdate
* @group LinksUpdate
* @group Database
* ^--- make sure temporary tables are used.
diff --git a/www/wiki/tests/phpunit/includes/deferred/MWCallableUpdateTest.php b/www/wiki/tests/phpunit/includes/deferred/MWCallableUpdateTest.php
new file mode 100644
index 00000000..3ab9b565
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/deferred/MWCallableUpdateTest.php
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * @covers MWCallableUpdate
+ */
+class MWCallableUpdateTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
+ public function testDoUpdate() {
+ $ran = 0;
+ $update = new MWCallableUpdate( function () use ( &$ran ) {
+ $ran++;
+ } );
+ $this->assertSame( 0, $ran );
+ $update->doUpdate();
+ $this->assertSame( 1, $ran );
+ }
+
+ public function testCancel() {
+ // Prepare update and DB
+ $db = new DatabaseTestHelper( __METHOD__ );
+ $db->begin( __METHOD__ );
+ $ran = 0;
+ $update = new MWCallableUpdate( function () use ( &$ran ) {
+ $ran++;
+ }, __METHOD__, $db );
+
+ // Emulate rollback
+ $db->rollback( __METHOD__ );
+
+ $update->doUpdate();
+
+ // Ensure it was cancelled
+ $this->assertSame( 0, $ran );
+ }
+
+ public function testCancelSome() {
+ // Prepare update and DB
+ $db1 = new DatabaseTestHelper( __METHOD__ );
+ $db1->begin( __METHOD__ );
+ $db2 = new DatabaseTestHelper( __METHOD__ );
+ $db2->begin( __METHOD__ );
+ $ran = 0;
+ $update = new MWCallableUpdate( function () use ( &$ran ) {
+ $ran++;
+ }, __METHOD__, [ $db1, $db2 ] );
+
+ // Emulate rollback
+ $db1->rollback( __METHOD__ );
+
+ $update->doUpdate();
+
+ // Prevents: "Notice: DB transaction writes or callbacks still pending"
+ $db2->rollback( __METHOD__ );
+
+ // Ensure it was cancelled
+ $this->assertSame( 0, $ran );
+ }
+
+ public function testCancelAll() {
+ // Prepare update and DB
+ $db1 = new DatabaseTestHelper( __METHOD__ );
+ $db1->begin( __METHOD__ );
+ $db2 = new DatabaseTestHelper( __METHOD__ );
+ $db2->begin( __METHOD__ );
+ $ran = 0;
+ $update = new MWCallableUpdate( function () use ( &$ran ) {
+ $ran++;
+ }, __METHOD__, [ $db1, $db2 ] );
+
+ // Emulate rollbacks
+ $db1->rollback( __METHOD__ );
+ $db2->rollback( __METHOD__ );
+
+ $update->doUpdate();
+
+ // Ensure it was cancelled
+ $this->assertSame( 0, $ran );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/deferred/SiteStatsUpdateTest.php b/www/wiki/tests/phpunit/includes/deferred/SiteStatsUpdateTest.php
new file mode 100644
index 00000000..83e9a47c
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/deferred/SiteStatsUpdateTest.php
@@ -0,0 +1,77 @@
+<?php
+
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @group Database
+ */
+class SiteStatsUpdateTest extends MediaWikiTestCase {
+ /**
+ * @covers SiteStatsUpdate::factory
+ * @covers SiteStatsUpdate::merge
+ */
+ public function testFactoryAndMerge() {
+ $update1 = SiteStatsUpdate::factory( [ 'pages' => 1, 'users' => 2 ] );
+ $update2 = SiteStatsUpdate::factory( [ 'users' => 1, 'images' => 1 ] );
+
+ $update1->merge( $update2 );
+ $wrapped = TestingAccessWrapper::newFromObject( $update1 );
+
+ $this->assertEquals( 1, $wrapped->pages );
+ $this->assertEquals( 3, $wrapped->users );
+ $this->assertEquals( 1, $wrapped->images );
+ $this->assertEquals( 0, $wrapped->edits );
+ $this->assertEquals( 0, $wrapped->articles );
+ }
+
+ /**
+ * @covers SiteStatsUpdate::doUpdate()
+ * @covers SiteStatsInit::refresh()
+ */
+ public function testDoUpdate() {
+ $this->setMwGlobals( 'wgSiteStatsAsyncFactor', false );
+ $this->setMwGlobals( 'wgCommandLineMode', false ); // disable opportunistic updates
+
+ $dbw = wfGetDB( DB_MASTER );
+ $statsInit = new SiteStatsInit( $dbw );
+ $statsInit->refresh();
+
+ $ei = SiteStats::edits(); // trigger load
+ $pi = SiteStats::pages();
+ $ui = SiteStats::users();
+ $fi = SiteStats::images();
+ $ai = SiteStats::articles();
+
+ $dbw->begin( __METHOD__ ); // block opportunistic updates
+
+ $update = SiteStatsUpdate::factory( [ 'pages' => 2, 'images' => 1, 'edits' => 2 ] );
+ $this->assertEquals( 0, DeferredUpdates::pendingUpdatesCount() );
+ $update->doUpdate();
+ $this->assertEquals( 1, DeferredUpdates::pendingUpdatesCount() );
+
+ // Still the same
+ SiteStats::unload();
+ $this->assertEquals( $pi, SiteStats::pages(), 'page count' );
+ $this->assertEquals( $ei, SiteStats::edits(), 'edit count' );
+ $this->assertEquals( $ui, SiteStats::users(), 'user count' );
+ $this->assertEquals( $fi, SiteStats::images(), 'file count' );
+ $this->assertEquals( $ai, SiteStats::articles(), 'article count' );
+ $this->assertEquals( 1, DeferredUpdates::pendingUpdatesCount() );
+
+ $dbw->commit( __METHOD__ );
+
+ $this->assertEquals( 1, DeferredUpdates::pendingUpdatesCount() );
+ DeferredUpdates::doUpdates();
+ $this->assertEquals( 0, DeferredUpdates::pendingUpdatesCount() );
+
+ SiteStats::unload();
+ $this->assertEquals( $pi + 2, SiteStats::pages(), 'page count' );
+ $this->assertEquals( $ei + 2, SiteStats::edits(), 'edit count' );
+ $this->assertEquals( $ui, SiteStats::users(), 'user count' );
+ $this->assertEquals( $fi + 1, SiteStats::images(), 'file count' );
+ $this->assertEquals( $ai, SiteStats::articles(), 'article count' );
+
+ $statsInit = new SiteStatsInit();
+ $statsInit->refresh();
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/deferred/TransactionRoundDefiningUpdateTest.php b/www/wiki/tests/phpunit/includes/deferred/TransactionRoundDefiningUpdateTest.php
new file mode 100644
index 00000000..693897e6
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/deferred/TransactionRoundDefiningUpdateTest.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @covers TransactionRoundDefiningUpdate
+ */
+class TransactionRoundDefiningUpdateTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
+ public function testDoUpdate() {
+ $ran = 0;
+ $update = new TransactionRoundDefiningUpdate( function () use ( &$ran ) {
+ $ran++;
+ } );
+ $this->assertSame( 0, $ran );
+ $update->doUpdate();
+ $this->assertSame( 1, $ran );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php b/www/wiki/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php
index 106b86af..8d94404c 100644
--- a/www/wiki/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php
+++ b/www/wiki/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php
@@ -20,7 +20,7 @@ class ArrayDiffFormatterTest extends MediaWikiTestCase {
}
private function getMockDiff( $edits ) {
- $diff = $this->getMockBuilder( 'Diff' )
+ $diff = $this->getMockBuilder( Diff::class )
->disableOriginalConstructor()
->getMock();
$diff->expects( $this->any() )
@@ -30,7 +30,7 @@ class ArrayDiffFormatterTest extends MediaWikiTestCase {
}
private function getMockDiffOp( $type = null, $orig = [], $closing = [] ) {
- $diffOp = $this->getMockBuilder( 'DiffOp' )
+ $diffOp = $this->getMockBuilder( DiffOp::class )
->disableOriginalConstructor()
->getMock();
$diffOp->expects( $this->any() )
diff --git a/www/wiki/tests/phpunit/includes/diff/DifferenceEngineTest.php b/www/wiki/tests/phpunit/includes/diff/DifferenceEngineTest.php
index 3a8f4db3..57aeb200 100644
--- a/www/wiki/tests/phpunit/includes/diff/DifferenceEngineTest.php
+++ b/www/wiki/tests/phpunit/includes/diff/DifferenceEngineTest.php
@@ -1,5 +1,7 @@
<?php
+use Wikimedia\TestingAccessWrapper;
+
/**
* @covers DifferenceEngine
*
@@ -117,4 +119,30 @@ class DifferenceEngineTest extends MediaWikiTestCase {
$this->assertEquals( $revs[2], $diffEngine->getNewid(), 'diff get new id' );
}
+ public function provideLocaliseTitleTooltipsTestData() {
+ return [
+ 'moved paragraph left shoud get new location title' => [
+ '<a class="mw-diff-movedpara-left">⚫</a>',
+ '<a class="mw-diff-movedpara-left" title="(diff-paragraph-moved-tonew)">⚫</a>',
+ ],
+ 'moved paragraph right shoud get old location title' => [
+ '<a class="mw-diff-movedpara-right">⚫</a>',
+ '<a class="mw-diff-movedpara-right" title="(diff-paragraph-moved-toold)">⚫</a>',
+ ],
+ 'nothing changed when key not hit' => [
+ '<a class="mw-diff-movedpara-rightis">⚫</a>',
+ '<a class="mw-diff-movedpara-rightis">⚫</a>',
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideLocaliseTitleTooltipsTestData
+ */
+ public function testAddLocalisedTitleTooltips( $input, $expected ) {
+ $this->setContentLang( 'qqx' );
+ $diffEngine = TestingAccessWrapper::newFromObject( new DifferenceEngine() );
+ $this->assertEquals( $expected, $diffEngine->addLocalisedTitleTooltips( $input ) );
+ }
+
}
diff --git a/www/wiki/tests/phpunit/includes/editpage/TextboxBuilderTest.php b/www/wiki/tests/phpunit/includes/editpage/TextboxBuilderTest.php
new file mode 100644
index 00000000..4195f968
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/editpage/TextboxBuilderTest.php
@@ -0,0 +1,210 @@
+<?php
+/**
+ * Copyright (C) 2017 Kunal Mehta <legoktm@member.fsf.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+namespace MediaWiki\Tests\EditPage;
+
+use Language;
+use MediaWiki\EditPage\TextboxBuilder;
+use MediaWikiTestCase;
+use Title;
+use User;
+
+/**
+ * @covers \MediaWiki\EditPage\TextboxBuilder
+ */
+class TextboxBuilderTest extends MediaWikiTestCase {
+
+ public function provideAddNewLineAtEnd() {
+ return [
+ [ '', '' ],
+ [ 'foo', "foo\n" ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideAddNewLineAtEnd
+ */
+ public function testAddNewLineAtEnd( $input, $expected ) {
+ $builder = new TextboxBuilder();
+ $this->assertSame( $expected, $builder->addNewLineAtEnd( $input ) );
+ }
+
+ public function testBuildTextboxAttribs() {
+ $user = new User();
+ $user->setOption( 'editfont', 'monospace' );
+
+ $title = $this->getMockBuilder( Title::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $title->expects( $this->any() )
+ ->method( 'getPageLanguage' )
+ ->will( $this->returnValue( Language::factory( 'en' ) ) );
+
+ $builder = new TextboxBuilder();
+ $attribs = $builder->buildTextboxAttribs(
+ 'mw-textbox1',
+ [ 'class' => 'foo bar', 'data-foo' => '123', 'rows' => 30 ],
+ $user,
+ $title
+ );
+
+ $this->assertInternalType( 'array', $attribs );
+ // custom attrib showed up
+ $this->assertArrayHasKey( 'data-foo', $attribs );
+ // classes merged properly (string)
+ $this->assertSame( 'foo bar mw-editfont-monospace', $attribs['class'] );
+ // overrides in custom attrib worked
+ $this->assertSame( 30, $attribs['rows'] );
+ $this->assertSame( 'en', $attribs['lang'] );
+
+ $attribs2 = $builder->buildTextboxAttribs(
+ 'mw-textbox2', [ 'class' => [ 'foo', 'bar' ] ], $user, $title
+ );
+ // classes merged properly (array)
+ $this->assertSame( [ 'foo', 'bar', 'mw-editfont-monospace' ], $attribs2['class'] );
+
+ $attribs3 = $builder->buildTextboxAttribs(
+ 'mw-textbox3', [], $user, $title
+ );
+ // classes ok when nothing to be merged
+ $this->assertSame( 'mw-editfont-monospace', $attribs3['class'] );
+ }
+
+ public function provideMergeClassesIntoAttributes() {
+ return [
+ [
+ [],
+ [],
+ [],
+ ],
+ [
+ [ 'mw-new-classname' ],
+ [],
+ [ 'class' => 'mw-new-classname' ],
+ ],
+ [
+ [],
+ [ 'title' => 'My Title' ],
+ [ 'title' => 'My Title' ],
+ ],
+ [
+ [ 'mw-new-classname' ],
+ [ 'title' => 'My Title' ],
+ [ 'title' => 'My Title', 'class' => 'mw-new-classname' ],
+ ],
+ [
+ [ 'mw-new-classname' ],
+ [ 'class' => 'mw-existing-classname' ],
+ [ 'class' => 'mw-existing-classname mw-new-classname' ],
+ ],
+ [
+ [ 'mw-new-classname', 'mw-existing-classname' ],
+ [ 'class' => 'mw-existing-classname' ],
+ [ 'class' => 'mw-existing-classname mw-new-classname' ],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideMergeClassesIntoAttributes
+ */
+ public function testMergeClassesIntoAttributes( $inputClasses, $inputAttributes, $expected ) {
+ $builder = new TextboxBuilder();
+ $this->assertSame(
+ $expected,
+ $builder->mergeClassesIntoAttributes( $inputClasses, $inputAttributes )
+ );
+ }
+
+ public function provideGetTextboxProtectionCSSClasses() {
+ return [
+ [
+ [ '' ],
+ [ 'isProtected' ],
+ [],
+ ],
+ [
+ true,
+ [],
+ [],
+ ],
+ [
+ true,
+ [ 'isProtected' ],
+ [ 'mw-textarea-protected' ]
+ ],
+ [
+ true,
+ [ 'isProtected', 'isSemiProtected' ],
+ [ 'mw-textarea-sprotected' ],
+ ],
+ [
+ true,
+ [ 'isProtected', 'isCascadeProtected' ],
+ [ 'mw-textarea-protected', 'mw-textarea-cprotected' ],
+ ],
+ [
+ true,
+ [ 'isProtected', 'isCascadeProtected', 'isSemiProtected' ],
+ [ 'mw-textarea-sprotected', 'mw-textarea-cprotected' ],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetTextboxProtectionCSSClasses
+ */
+ public function testGetTextboxProtectionCSSClasses(
+ $restrictionLevels,
+ $protectionModes,
+ $expected
+ ) {
+ $this->setMwGlobals( [
+ // set to trick MWNamespace::getRestrictionLevels
+ 'wgRestrictionLevels' => $restrictionLevels
+ ] );
+
+ $builder = new TextboxBuilder();
+ $this->assertSame( $expected, $builder->getTextboxProtectionCSSClasses(
+ $this->mockProtectedTitle( $protectionModes )
+ ) );
+ }
+
+ /**
+ * @return Title
+ */
+ private function mockProtectedTitle( $methodsToReturnTrue ) {
+ $title = $this->getMockBuilder( Title::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $title->expects( $this->any() )
+ ->method( 'getNamespace' )
+ ->will( $this->returnValue( 1 ) );
+
+ foreach ( $methodsToReturnTrue as $method ) {
+ $title->expects( $this->any() )
+ ->method( $method )
+ ->will( $this->returnValue( true ) );
+ }
+
+ return $title;
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/exception/BadTitleErrorTest.php b/www/wiki/tests/phpunit/includes/exception/BadTitleErrorTest.php
index e6a18125..b706face 100644
--- a/www/wiki/tests/phpunit/includes/exception/BadTitleErrorTest.php
+++ b/www/wiki/tests/phpunit/includes/exception/BadTitleErrorTest.php
@@ -10,13 +10,15 @@ class BadTitleErrorTest extends MediaWikiTestCase {
try {
throw new BadTitleError();
} catch ( BadTitleError $e ) {
+ ob_start();
$e->report();
- $this->assertTrue( true );
+ $text = ob_get_clean();
+ $this->assertContains( $e->getText(), $text );
}
}
private function getMockWgOut() {
- $mock = $this->getMockBuilder( 'OutputPage' )
+ $mock = $this->getMockBuilder( OutputPage::class )
->disableOriginalConstructor()
->getMock();
$mock->expects( $this->once() )
diff --git a/www/wiki/tests/phpunit/includes/exception/ErrorPageErrorTest.php b/www/wiki/tests/phpunit/includes/exception/ErrorPageErrorTest.php
index e72865f6..49d454e8 100644
--- a/www/wiki/tests/phpunit/includes/exception/ErrorPageErrorTest.php
+++ b/www/wiki/tests/phpunit/includes/exception/ErrorPageErrorTest.php
@@ -7,7 +7,7 @@
class ErrorPageErrorTest extends MediaWikiTestCase {
private function getMockMessage() {
- $mockMessage = $this->getMockBuilder( 'Message' )
+ $mockMessage = $this->getMockBuilder( Message::class )
->disableOriginalConstructor()
->getMock();
$mockMessage->expects( $this->once() )
@@ -34,7 +34,7 @@ class ErrorPageErrorTest extends MediaWikiTestCase {
$title = 'Foo';
$params = [ 'Baz' ];
- $mock = $this->getMockBuilder( 'OutputPage' )
+ $mock = $this->getMockBuilder( OutputPage::class )
->disableOriginalConstructor()
->getMock();
$mock->expects( $this->once() )
diff --git a/www/wiki/tests/phpunit/includes/exception/MWExceptionTest.php b/www/wiki/tests/phpunit/includes/exception/MWExceptionTest.php
index 614a1c98..b1605549 100644
--- a/www/wiki/tests/phpunit/includes/exception/MWExceptionTest.php
+++ b/www/wiki/tests/phpunit/includes/exception/MWExceptionTest.php
@@ -10,6 +10,7 @@ class MWExceptionTest extends MediaWikiTestCase {
/**
* @expectedException MWException
+ * @covers MWException
*/
public function testMwexceptionThrowing() {
throw new MWException();
@@ -43,7 +44,7 @@ class MWExceptionTest extends MediaWikiTestCase {
}
private function getMockLanguage() {
- return $this->getMockBuilder( 'Language' )
+ return $this->getMockBuilder( Language::class )
->disableOriginalConstructor()
->getMock();
}
@@ -110,8 +111,8 @@ class MWExceptionTest extends MediaWikiTestCase {
public static function provideExceptionClasses() {
return [
- [ 'Exception' ],
- [ 'MWException' ],
+ [ Exception::class ],
+ [ MWException::class ],
];
}
@@ -146,7 +147,7 @@ class MWExceptionTest extends MediaWikiTestCase {
*/
public static function provideJsonSerializedKeys() {
$testCases = [];
- foreach ( [ 'Exception', 'MWException' ] as $exClass ) {
+ foreach ( [ Exception::class, MWException::class ] as $exClass ) {
$exTests = [
[ 'string', $exClass, 'id' ],
[ 'string', $exClass, 'file' ],
diff --git a/www/wiki/tests/phpunit/includes/exception/ThrottledErrorTest.php b/www/wiki/tests/phpunit/includes/exception/ThrottledErrorTest.php
index 23bb1e86..5214b6d4 100644
--- a/www/wiki/tests/phpunit/includes/exception/ThrottledErrorTest.php
+++ b/www/wiki/tests/phpunit/includes/exception/ThrottledErrorTest.php
@@ -11,13 +11,15 @@ class ThrottledErrorTest extends MediaWikiTestCase {
try {
throw new ThrottledError();
} catch ( ThrottledError $e ) {
+ ob_start();
$e->report();
- $this->assertTrue( true );
+ $text = ob_get_clean();
+ $this->assertContains( $e->getText(), $text );
}
}
private function getMockWgOut() {
- $mock = $this->getMockBuilder( 'OutputPage' )
+ $mock = $this->getMockBuilder( OutputPage::class )
->disableOriginalConstructor()
->getMock();
$mock->expects( $this->once() )
diff --git a/www/wiki/tests/phpunit/includes/externalstore/ExternalStoreFactoryTest.php b/www/wiki/tests/phpunit/includes/externalstore/ExternalStoreFactoryTest.php
new file mode 100644
index 00000000..f7626938
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/externalstore/ExternalStoreFactoryTest.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @covers ExternalStoreFactory
+ */
+class ExternalStoreFactoryTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
+ public function testExternalStoreFactory_noStores() {
+ $factory = new ExternalStoreFactory( [] );
+ $this->assertFalse( $factory->getStoreObject( 'ForTesting' ) );
+ $this->assertFalse( $factory->getStoreObject( 'foo' ) );
+ }
+
+ public function provideStoreNames() {
+ yield 'Same case as construction' => [ 'ForTesting' ];
+ yield 'All lower case' => [ 'fortesting' ];
+ yield 'All upper case' => [ 'FORTESTING' ];
+ yield 'Mix of cases' => [ 'FOrTEsTInG' ];
+ }
+
+ /**
+ * @dataProvider provideStoreNames
+ */
+ public function testExternalStoreFactory_someStore_protoMatch( $proto ) {
+ $factory = new ExternalStoreFactory( [ 'ForTesting' ] );
+ $store = $factory->getStoreObject( $proto );
+ $this->assertInstanceOf( ExternalStoreForTesting::class, $store );
+ }
+
+ /**
+ * @dataProvider provideStoreNames
+ */
+ public function testExternalStoreFactory_someStore_noProtoMatch( $proto ) {
+ $factory = new ExternalStoreFactory( [ 'SomeOtherClassName' ] );
+ $store = $factory->getStoreObject( $proto );
+ $this->assertFalse( $store );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/externalstore/ExternalStoreForTesting.php b/www/wiki/tests/phpunit/includes/externalstore/ExternalStoreForTesting.php
new file mode 100644
index 00000000..50f1e523
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/externalstore/ExternalStoreForTesting.php
@@ -0,0 +1,46 @@
+<?php
+
+class ExternalStoreForTesting {
+
+ protected $data = [
+ 'cluster1' => [
+ '200' => 'Hello',
+ '300' => [
+ 'Hello', 'World',
+ ],
+ // gzip string below generated with gzdeflate( 'AAAABBAAA' )
+ '12345' => "sttttr\002\022\000",
+ ],
+ ];
+
+ /**
+ * Fetch data from given URL
+ * @param string $url An url of the form FOO://cluster/id or FOO://cluster/id/itemid.
+ * @return mixed
+ */
+ public function fetchFromURL( $url ) {
+ // Based on ExternalStoreDB
+ $path = explode( '/', $url );
+ $cluster = $path[2];
+ $id = $path[3];
+ if ( isset( $path[4] ) ) {
+ $itemID = $path[4];
+ } else {
+ $itemID = false;
+ }
+
+ if ( !isset( $this->data[$cluster][$id] ) ) {
+ return null;
+ }
+
+ if ( $itemID !== false
+ && is_array( $this->data[$cluster][$id] )
+ && isset( $this->data[$cluster][$id][$itemID] )
+ ) {
+ return $this->data[$cluster][$id][$itemID];
+ }
+
+ return $this->data[$cluster][$id];
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/externalstore/ExternalStoreTest.php b/www/wiki/tests/phpunit/includes/externalstore/ExternalStoreTest.php
index a365c4de..7ca38749 100644
--- a/www/wiki/tests/phpunit/includes/externalstore/ExternalStoreTest.php
+++ b/www/wiki/tests/phpunit/includes/externalstore/ExternalStoreTest.php
@@ -1,31 +1,39 @@
<?php
-/**
- * External Store tests
- */
class ExternalStoreTest extends MediaWikiTestCase {
/**
* @covers ExternalStore::fetchFromURL
*/
- public function testExternalFetchFromURL() {
- $this->setMwGlobals( 'wgExternalStores', false );
+ public function testExternalFetchFromURL_noExternalStores() {
+ $this->setService(
+ 'ExternalStoreFactory',
+ new ExternalStoreFactory( [] )
+ );
$this->assertFalse(
- ExternalStore::fetchFromURL( 'FOO://cluster1/200' ),
+ ExternalStore::fetchFromURL( 'ForTesting://cluster1/200' ),
'Deny if wgExternalStores is not set to a non-empty array'
);
+ }
- $this->setMwGlobals( 'wgExternalStores', [ 'FOO' ] );
+ /**
+ * @covers ExternalStore::fetchFromURL
+ */
+ public function testExternalFetchFromURL_someExternalStore() {
+ $this->setService(
+ 'ExternalStoreFactory',
+ new ExternalStoreFactory( [ 'ForTesting' ] )
+ );
$this->assertEquals(
- ExternalStore::fetchFromURL( 'FOO://cluster1/200' ),
'Hello',
+ ExternalStore::fetchFromURL( 'ForTesting://cluster1/200' ),
'Allow FOO://cluster1/200'
);
$this->assertEquals(
- ExternalStore::fetchFromURL( 'FOO://cluster1/300/0' ),
'Hello',
+ ExternalStore::fetchFromURL( 'ForTesting://cluster1/300/0' ),
'Allow FOO://cluster1/300/0'
);
# Assertions for r68900
@@ -43,45 +51,3 @@ class ExternalStoreTest extends MediaWikiTestCase {
);
}
}
-
-class ExternalStoreFOO {
-
- protected $data = [
- 'cluster1' => [
- '200' => 'Hello',
- '300' => [
- 'Hello', 'World',
- ],
- ],
- ];
-
- /**
- * Fetch data from given URL
- * @param string $url An url of the form FOO://cluster/id or FOO://cluster/id/itemid.
- * @return mixed
- */
- function fetchFromURL( $url ) {
- // Based on ExternalStoreDB
- $path = explode( '/', $url );
- $cluster = $path[2];
- $id = $path[3];
- if ( isset( $path[4] ) ) {
- $itemID = $path[4];
- } else {
- $itemID = false;
- }
-
- if ( !isset( $this->data[$cluster][$id] ) ) {
- return null;
- }
-
- if ( $itemID !== false
- && is_array( $this->data[$cluster][$id] )
- && isset( $this->data[$cluster][$id][$itemID] )
- ) {
- return $this->data[$cluster][$id][$itemID];
- }
-
- return $this->data[$cluster][$id];
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/filebackend/FileBackendTest.php b/www/wiki/tests/phpunit/includes/filebackend/FileBackendTest.php
index ddcf19bd..2cd4ba6d 100644
--- a/www/wiki/tests/phpunit/includes/filebackend/FileBackendTest.php
+++ b/www/wiki/tests/phpunit/includes/filebackend/FileBackendTest.php
@@ -101,7 +101,7 @@ class FileBackendTest extends MediaWikiTestCase {
'backends' => [
[
'name' => 'localmultitesting1',
- 'class' => 'FSFileBackend',
+ 'class' => FSFileBackend::class,
'containerPaths' => [
'unittest-cont1' => "{$tmpDir}/localtestingmulti1-cont1",
'unittest-cont2' => "{$tmpDir}/localtestingmulti1-cont2" ],
@@ -109,7 +109,7 @@ class FileBackendTest extends MediaWikiTestCase {
],
[
'name' => 'localmultitesting2',
- 'class' => 'FSFileBackend',
+ 'class' => FSFileBackend::class,
'containerPaths' => [
'unittest-cont1' => "{$tmpDir}/localtestingmulti2-cont1",
'unittest-cont2' => "{$tmpDir}/localtestingmulti2-cont2" ],
@@ -2411,7 +2411,7 @@ class FileBackendTest extends MediaWikiTestCase {
$status = Status::newGood();
$sl = $this->backend->getScopedFileLocks( $paths, LockManager::LOCK_EX, $status );
- $this->assertInstanceOf( 'ScopedLock', $sl,
+ $this->assertInstanceOf( ScopedLock::class, $sl,
"Scoped locking of files succeeded ($backendName)." );
$this->assertEquals( [], $status->getErrors(),
"Scoped locking of files succeeded ($backendName)." );
@@ -2436,7 +2436,7 @@ class FileBackendTest extends MediaWikiTestCase {
$be = TestingAccessWrapper::newFromObject( new MemoryFileBackend(
[
'name' => 'testing',
- 'class' => 'MemoryFileBackend',
+ 'class' => MemoryFileBackend::class,
'wikiId' => 'meow',
'mimeCallback' => $mimeCallback
]
@@ -2471,13 +2471,13 @@ class FileBackendTest extends MediaWikiTestCase {
'backends' => [
[ // backend 0
'name' => 'multitesting0',
- 'class' => 'MemoryFileBackend',
+ 'class' => MemoryFileBackend::class,
'isMultiMaster' => false,
'readAffinity' => true
],
[ // backend 1
'name' => 'multitesting1',
- 'class' => 'MemoryFileBackend',
+ 'class' => MemoryFileBackend::class,
'isMultiMaster' => true
]
]
@@ -2521,12 +2521,12 @@ class FileBackendTest extends MediaWikiTestCase {
'backends' => [
[ // backend 0
'name' => 'multitesting0',
- 'class' => 'MemoryFileBackend',
+ 'class' => MemoryFileBackend::class,
'isMultiMaster' => false
],
[ // backend 1
'name' => 'multitesting1',
- 'class' => 'MemoryFileBackend',
+ 'class' => MemoryFileBackend::class,
'isMultiMaster' => true
]
],
@@ -2584,9 +2584,9 @@ class FileBackendTest extends MediaWikiTestCase {
]
];
- MediaWiki\suppressWarnings();
+ Wikimedia\suppressWarnings();
$actual = $be->sanitizeOpHeaders( $input );
- MediaWiki\restoreWarnings();
+ Wikimedia\restoreWarnings();
$this->assertEquals( $expected, $actual, "Header sanitized properly" );
}
diff --git a/www/wiki/tests/phpunit/includes/filebackend/SwiftFileBackendTest.php b/www/wiki/tests/phpunit/includes/filebackend/SwiftFileBackendTest.php
index 9cd2b100..35eca28f 100644
--- a/www/wiki/tests/phpunit/includes/filebackend/SwiftFileBackendTest.php
+++ b/www/wiki/tests/phpunit/includes/filebackend/SwiftFileBackendTest.php
@@ -22,7 +22,7 @@ class SwiftFileBackendTest extends MediaWikiTestCase {
$this->backend = TestingAccessWrapper::newFromObject(
new SwiftFileBackend( [
'name' => 'local-swift-testing',
- 'class' => 'SwiftFileBackend',
+ 'class' => SwiftFileBackend::class,
'wikiId' => 'unit-testing',
'lockManager' => LockManagerGroup::singleton()->get( 'fsLockManager' ),
'swiftAuthUrl' => 'http://127.0.0.1:8080/auth', // unused
@@ -34,6 +34,68 @@ class SwiftFileBackendTest extends MediaWikiTestCase {
}
/**
+ * @dataProvider provider_testSanitizeHdrsStrict
+ */
+ public function testSanitizeHdrsStrict( $raw, $sanitized ) {
+ $hdrs = $this->backend->sanitizeHdrsStrict( [ 'headers' => $raw ] );
+
+ $this->assertEquals( $hdrs, $sanitized, 'sanitizeHdrsStrict() has expected result' );
+ }
+
+ public static function provider_testSanitizeHdrsStrict() {
+ return [
+ [
+ [
+ 'content-length' => 345,
+ 'content-type' => 'image+bitmap/jpeg',
+ 'content-disposition' => 'inline',
+ 'content-duration' => 35.6363,
+ 'content-Custom' => 'hello',
+ 'x-content-custom' => 'hello'
+ ],
+ [
+ 'content-disposition' => 'inline',
+ 'content-duration' => 35.6363,
+ 'content-custom' => 'hello',
+ 'x-content-custom' => 'hello'
+ ]
+ ],
+ [
+ [
+ 'content-length' => 345,
+ 'content-type' => 'image+bitmap/jpeg',
+ 'content-Disposition' => 'inline; filename=xxx; ' . str_repeat( 'o', 1024 ),
+ 'content-duration' => 35.6363,
+ 'content-custom' => 'hello',
+ 'x-content-custom' => 'hello'
+ ],
+ [
+ 'content-disposition' => 'inline;filename=xxx',
+ 'content-duration' => 35.6363,
+ 'content-custom' => 'hello',
+ 'x-content-custom' => 'hello'
+ ]
+ ],
+ [
+ [
+ 'content-length' => 345,
+ 'content-type' => 'image+bitmap/jpeg',
+ 'content-disposition' => 'filename=' . str_repeat( 'o', 1024 ) . ';inline',
+ 'content-duration' => 35.6363,
+ 'content-custom' => 'hello',
+ 'x-content-custom' => 'hello'
+ ],
+ [
+ 'content-disposition' => '',
+ 'content-duration' => 35.6363,
+ 'content-custom' => 'hello',
+ 'x-content-custom' => 'hello'
+ ]
+ ]
+ ];
+ }
+
+ /**
* @dataProvider provider_testSanitizeHdrs
*/
public function testSanitizeHdrs( $raw, $sanitized ) {
@@ -54,6 +116,7 @@ class SwiftFileBackendTest extends MediaWikiTestCase {
'x-content-custom' => 'hello'
],
[
+ 'content-type' => 'image+bitmap/jpeg',
'content-disposition' => 'inline',
'content-duration' => 35.6363,
'content-custom' => 'hello',
@@ -70,6 +133,7 @@ class SwiftFileBackendTest extends MediaWikiTestCase {
'x-content-custom' => 'hello'
],
[
+ 'content-type' => 'image+bitmap/jpeg',
'content-disposition' => 'inline;filename=xxx',
'content-duration' => 35.6363,
'content-custom' => 'hello',
@@ -86,6 +150,7 @@ class SwiftFileBackendTest extends MediaWikiTestCase {
'x-content-custom' => 'hello'
],
[
+ 'content-type' => 'image+bitmap/jpeg',
'content-disposition' => '',
'content-duration' => 35.6363,
'content-custom' => 'hello',
diff --git a/www/wiki/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php b/www/wiki/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php
index 0d00fbc2..4c9855b0 100644
--- a/www/wiki/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php
+++ b/www/wiki/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php
@@ -112,19 +112,19 @@ class FileBackendDBRepoWrapperTest extends MediaWikiTestCase {
}
protected function getMocks() {
- $dbMock = $this->getMockBuilder( 'DatabaseMysqli' )
+ $dbMock = $this->getMockBuilder( Wikimedia\Rdbms\DatabaseMysqli::class )
->disableOriginalClone()
->disableOriginalConstructor()
->getMock();
- $backendMock = $this->getMockBuilder( 'FSFileBackend' )
+ $backendMock = $this->getMockBuilder( FSFileBackend::class )
->setConstructorArgs( [ [
'name' => $this->backendName,
'wikiId' => wfWikiID()
] ] )
->getMock();
- $wrapperMock = $this->getMockBuilder( 'FileBackendDBRepoWrapper' )
+ $wrapperMock = $this->getMockBuilder( FileBackendDBRepoWrapper::class )
->setMethods( [ 'getDB' ] )
->setConstructorArgs( [ [
'backend' => $backendMock,
diff --git a/www/wiki/tests/phpunit/includes/filerepo/FileRepoTest.php b/www/wiki/tests/phpunit/includes/filerepo/FileRepoTest.php
index d1e6dc39..0d3e679a 100644
--- a/www/wiki/tests/phpunit/includes/filerepo/FileRepoTest.php
+++ b/www/wiki/tests/phpunit/includes/filerepo/FileRepoTest.php
@@ -50,6 +50,6 @@ class FileRepoTest extends MediaWikiTestCase {
'containerPaths' => []
] )
] );
- $this->assertInstanceOf( 'FileRepo', $f );
+ $this->assertInstanceOf( FileRepo::class, $f );
}
}
diff --git a/www/wiki/tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php b/www/wiki/tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php
index 800c2fc7..9beea5b6 100644
--- a/www/wiki/tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php
+++ b/www/wiki/tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @covers MigrateFileRepoLayout
+ */
class MigrateFileRepoLayoutTest extends MediaWikiTestCase {
protected $tmpPrefix;
protected $migratorMock;
@@ -25,7 +28,7 @@ class MigrateFileRepoLayoutTest extends MediaWikiTestCase {
]
] );
- $dbMock = $this->getMockBuilder( 'DatabaseMysqli' )
+ $dbMock = $this->getMockBuilder( Wikimedia\Rdbms\DatabaseMysqli::class )
->disableOriginalConstructor()
->getMock();
@@ -41,7 +44,7 @@ class MigrateFileRepoLayoutTest extends MediaWikiTestCase {
new FakeResultWrapper( [] ) // filearchive
) );
- $repoMock = $this->getMockBuilder( 'LocalRepo' )
+ $repoMock = $this->getMockBuilder( LocalRepo::class )
->setMethods( [ 'getMasterDB' ] )
->setConstructorArgs( [ [
'name' => 'migratefilerepolayouttest',
@@ -54,7 +57,7 @@ class MigrateFileRepoLayoutTest extends MediaWikiTestCase {
->method( 'getMasterDB' )
->will( $this->returnValue( $dbMock ) );
- $this->migratorMock = $this->getMockBuilder( 'MigrateFileRepoLayout' )
+ $this->migratorMock = $this->getMockBuilder( MigrateFileRepoLayout::class )
->setMethods( [ 'getRepo' ] )->getMock();
$this->migratorMock
->expects( $this->any() )
diff --git a/www/wiki/tests/phpunit/includes/filerepo/RepoGroupTest.php b/www/wiki/tests/phpunit/includes/filerepo/RepoGroupTest.php
index 82ff12e3..5a343f65 100644
--- a/www/wiki/tests/phpunit/includes/filerepo/RepoGroupTest.php
+++ b/www/wiki/tests/phpunit/includes/filerepo/RepoGroupTest.php
@@ -1,4 +1,8 @@
<?php
+
+/**
+ * @covers RepoGroup
+ */
class RepoGroupTest extends MediaWikiTestCase {
function testHasForeignRepoNegative() {
@@ -15,7 +19,7 @@ class RepoGroupTest extends MediaWikiTestCase {
function testForEachForeignRepo() {
$this->setUpForeignRepo();
- $fakeCallback = $this->createMock( 'RepoGroupTestHelper' );
+ $fakeCallback = $this->createMock( RepoGroupTestHelper::class );
$fakeCallback->expects( $this->once() )->method( 'callback' );
RepoGroup::singleton()->forEachForeignRepo(
[ $fakeCallback, 'callback' ], [ [] ] );
@@ -25,7 +29,7 @@ class RepoGroupTest extends MediaWikiTestCase {
$this->setMwGlobals( 'wgForeignFileRepos', [] );
RepoGroup::destroySingleton();
FileBackendGroup::destroySingleton();
- $fakeCallback = $this->createMock( 'RepoGroupTestHelper' );
+ $fakeCallback = $this->createMock( RepoGroupTestHelper::class );
$fakeCallback->expects( $this->never() )->method( 'callback' );
RepoGroup::singleton()->forEachForeignRepo(
[ $fakeCallback, 'callback' ], [ [] ] );
@@ -34,7 +38,7 @@ class RepoGroupTest extends MediaWikiTestCase {
private function setUpForeignRepo() {
global $wgUploadDirectory;
$this->setMwGlobals( 'wgForeignFileRepos', [ [
- 'class' => 'ForeignAPIRepo',
+ 'class' => ForeignAPIRepo::class,
'name' => 'wikimediacommons',
'backend' => 'wikimediacommons-backend',
'apibase' => 'https://commons.wikimedia.org/w/api.php',
diff --git a/www/wiki/tests/phpunit/includes/filerepo/file/FileTest.php b/www/wiki/tests/phpunit/includes/filerepo/file/FileTest.php
index 5b5f1b09..3f4e46b5 100644
--- a/www/wiki/tests/phpunit/includes/filerepo/file/FileTest.php
+++ b/www/wiki/tests/phpunit/includes/filerepo/file/FileTest.php
@@ -6,6 +6,7 @@ class FileTest extends MediaWikiMediaTestCase {
* @param string $filename
* @param bool $expected
* @dataProvider providerCanAnimate
+ * @covers File::canAnimateThumbIfAppropriate
*/
function testCanAnimateThumbIfAppropriate( $filename, $expected ) {
$this->setMwGlobals( 'wgMaxAnimatedGifArea', 9000 );
@@ -37,7 +38,7 @@ class FileTest extends MediaWikiMediaTestCase {
$this->setMwGlobals( 'wgThumbnailBuckets', $data['buckets'] );
$this->setMwGlobals( 'wgThumbnailMinimumBucketDistance', $data['minimumBucketDistance'] );
- $fileMock = $this->getMockBuilder( 'File' )
+ $fileMock = $this->getMockBuilder( File::class )
->setConstructorArgs( [ 'fileMock', false ] )
->setMethods( [ 'getWidth' ] )
->getMockForAbstractClass();
@@ -136,11 +137,11 @@ class FileTest extends MediaWikiMediaTestCase {
* @covers File::getThumbnailSource
*/
public function testGetThumbnailSource( $data ) {
- $backendMock = $this->getMockBuilder( 'FSFileBackend' )
+ $backendMock = $this->getMockBuilder( FSFileBackend::class )
->setConstructorArgs( [ [ 'name' => 'backendMock', 'wikiId' => wfWikiID() ] ] )
->getMock();
- $repoMock = $this->getMockBuilder( 'FileRepo' )
+ $repoMock = $this->getMockBuilder( FileRepo::class )
->setConstructorArgs( [ [ 'name' => 'repoMock', 'backend' => $backendMock ] ] )
->setMethods( [ 'fileExists', 'getLocalReference' ] )
->getMock();
@@ -155,13 +156,13 @@ class FileTest extends MediaWikiMediaTestCase {
->method( 'getLocalReference' )
->will( $this->returnValue( $fsFile ) );
- $handlerMock = $this->getMockBuilder( 'BitmapHandler' )
+ $handlerMock = $this->getMockBuilder( BitmapHandler::class )
->setMethods( [ 'supportsBucketing' ] )->getMock();
$handlerMock->expects( $this->any() )
->method( 'supportsBucketing' )
->will( $this->returnValue( $data['supportsBucketing'] ) );
- $fileMock = $this->getMockBuilder( 'File' )
+ $fileMock = $this->getMockBuilder( File::class )
->setConstructorArgs( [ 'fileMock', $repoMock ] )
->setMethods( [ 'getThumbnailBucket', 'getLocalRefPath', 'getHandler' ] )
->getMockForAbstractClass();
@@ -247,22 +248,22 @@ class FileTest extends MediaWikiMediaTestCase {
public function testGenerateBucketsIfNeeded( $data ) {
$this->setMwGlobals( 'wgThumbnailBuckets', $data['buckets'] );
- $backendMock = $this->getMockBuilder( 'FSFileBackend' )
+ $backendMock = $this->getMockBuilder( FSFileBackend::class )
->setConstructorArgs( [ [ 'name' => 'backendMock', 'wikiId' => wfWikiID() ] ] )
->getMock();
- $repoMock = $this->getMockBuilder( 'FileRepo' )
+ $repoMock = $this->getMockBuilder( FileRepo::class )
->setConstructorArgs( [ [ 'name' => 'repoMock', 'backend' => $backendMock ] ] )
->setMethods( [ 'fileExists', 'getLocalReference' ] )
->getMock();
- $fileMock = $this->getMockBuilder( 'File' )
+ $fileMock = $this->getMockBuilder( File::class )
->setConstructorArgs( [ 'fileMock', $repoMock ] )
->setMethods( [ 'getWidth', 'getBucketThumbPath', 'makeTransformTmpFile',
'generateAndSaveThumb', 'getHandler' ] )
->getMockForAbstractClass();
- $handlerMock = $this->getMockBuilder( 'JpegHandler' )
+ $handlerMock = $this->getMockBuilder( JpegHandler::class )
->setMethods( [ 'supportsBucketing' ] )->getMock();
$handlerMock->expects( $this->any() )
->method( 'supportsBucketing' )
@@ -272,7 +273,7 @@ class FileTest extends MediaWikiMediaTestCase {
->method( 'getHandler' )
->will( $this->returnValue( $handlerMock ) );
- $reflectionMethod = new ReflectionMethod( 'File', 'generateBucketsIfNeeded' );
+ $reflectionMethod = new ReflectionMethod( File::class, 'generateBucketsIfNeeded' );
$reflectionMethod->setAccessible( true );
$fileMock->expects( $this->any() )
diff --git a/www/wiki/tests/phpunit/includes/filerepo/file/LocalFileTest.php b/www/wiki/tests/phpunit/includes/filerepo/file/LocalFileTest.php
index ffaa2c32..e25e6064 100644
--- a/www/wiki/tests/phpunit/includes/filerepo/file/LocalFileTest.php
+++ b/www/wiki/tests/phpunit/includes/filerepo/file/LocalFileTest.php
@@ -176,7 +176,7 @@ class LocalFileTest extends MediaWikiTestCase {
$file = wfLocalFile( "File:Some_file_that_probably_doesn't exist.png" );
$this->assertThat(
$file,
- $this->isInstanceOf( 'LocalFile' ),
+ $this->isInstanceOf( LocalFile::class ),
'wfLocalFile() returns LocalFile for valid Titles'
);
}
diff --git a/www/wiki/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php b/www/wiki/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php
index 33e3a257..99bea68d 100644
--- a/www/wiki/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php
+++ b/www/wiki/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php
@@ -4,7 +4,7 @@
*
* @covers HTMLAutoCompleteSelectField
*/
-class HtmlAutoCompleteSelectFieldTest extends MediaWikiTestCase {
+class HTMLAutoCompleteSelectFieldTest extends MediaWikiTestCase {
public $options = [
'Bulgaria' => 'BGR',
diff --git a/www/wiki/tests/phpunit/includes/htmlform/HTMLCheckMatrixTest.php b/www/wiki/tests/phpunit/includes/htmlform/HTMLCheckMatrixTest.php
index f97716b9..e7922fd2 100644
--- a/www/wiki/tests/phpunit/includes/htmlform/HTMLCheckMatrixTest.php
+++ b/www/wiki/tests/phpunit/includes/htmlform/HTMLCheckMatrixTest.php
@@ -4,7 +4,7 @@
* Unit tests for the HTMLCheckMatrix
* @covers HTMLCheckMatrix
*/
-class HtmlCheckMatrixTest extends MediaWikiTestCase {
+class HTMLCheckMatrixTest extends MediaWikiTestCase {
static private $defaultOptions = [
'rows' => [ 'r1', 'r2' ],
'columns' => [ 'c1', 'c2' ],
@@ -15,7 +15,7 @@ class HtmlCheckMatrixTest extends MediaWikiTestCase {
try {
new HTMLCheckMatrix( [] );
} catch ( MWException $e ) {
- $this->assertInstanceOf( 'HTMLFormFieldRequiredOptionsException', $e );
+ $this->assertInstanceOf( HTMLFormFieldRequiredOptionsException::class, $e );
return;
}
diff --git a/www/wiki/tests/phpunit/includes/htmlform/HTMLFormTest.php b/www/wiki/tests/phpunit/includes/htmlform/HTMLFormTest.php
index b7e0053c..e20cf942 100644
--- a/www/wiki/tests/phpunit/includes/htmlform/HTMLFormTest.php
+++ b/www/wiki/tests/phpunit/includes/htmlform/HTMLFormTest.php
@@ -1,21 +1,57 @@
<?php
-
+/**
+ * @covers HTMLForm
+ *
+ * @license GNU GPL v2+
+ * @author Gergő Tisza
+ * @author Thiemo Mättig
+ */
class HTMLFormTest extends MediaWikiTestCase {
- public function testGetHTML_empty() {
+
+ private function newInstance() {
$form = new HTMLForm( [] );
$form->setTitle( Title::newFromText( 'Foo' ) );
+ return $form;
+ }
+
+ public function testGetHTML_empty() {
+ $form = $this->newInstance();
$form->prepareForm();
$html = $form->getHTML( false );
- $this->assertRegExp( '/<form\b/', $html );
+ $this->assertStringStartsWith( '<form ', $html );
}
/**
* @expectedException LogicException
*/
public function testGetHTML_noPrepare() {
- $form = new HTMLForm( [] );
- $form->setTitle( Title::newFromText( 'Foo' ) );
+ $form = $this->newInstance();
$form->getHTML( false );
}
+
+ public function testAutocompleteDefaultsToNull() {
+ $form = $this->newInstance();
+ $this->assertNotContains( 'autocomplete', $form->wrapForm( '' ) );
+ }
+
+ public function testAutocompleteWhenSetToNull() {
+ $form = $this->newInstance();
+ $form->setAutocomplete( null );
+ $this->assertNotContains( 'autocomplete', $form->wrapForm( '' ) );
+ }
+
+ public function testAutocompleteWhenSetToFalse() {
+ $form = $this->newInstance();
+ // Previously false was used instead of null to indicate the attribute should not be set
+ $form->setAutocomplete( false );
+ $this->assertNotContains( 'autocomplete', $form->wrapForm( '' ) );
+ }
+
+ public function testAutocompleteWhenSetToOff() {
+ $form = $this->newInstance();
+ $form->setAutocomplete( 'off' );
+ $this->assertContains( ' autocomplete="off"', $form->wrapForm( '' ) );
+ }
+
}
diff --git a/www/wiki/tests/phpunit/includes/htmlform/HTMLRestrictionsFieldTest.php b/www/wiki/tests/phpunit/includes/htmlform/HTMLRestrictionsFieldTest.php
index 9ec4f97f..c4290e1e 100644
--- a/www/wiki/tests/phpunit/includes/htmlform/HTMLRestrictionsFieldTest.php
+++ b/www/wiki/tests/phpunit/includes/htmlform/HTMLRestrictionsFieldTest.php
@@ -1,6 +1,12 @@
<?php
-class HTMLRestrictionsFieldTest extends PHPUnit_Framework_TestCase {
+/**
+ * @covers HTMLRestrictionsField
+ */
+class HTMLRestrictionsFieldTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
public function testConstruct() {
$field = new HTMLRestrictionsField( [ 'fieldname' => 'restrictions' ] );
$this->assertNotEmpty( $field->getLabel(), 'has a default label' );
diff --git a/www/wiki/tests/phpunit/includes/http/HttpTest.php b/www/wiki/tests/phpunit/includes/http/HttpTest.php
index 3693a277..f80d18c6 100644
--- a/www/wiki/tests/phpunit/includes/http/HttpTest.php
+++ b/www/wiki/tests/phpunit/includes/http/HttpTest.php
@@ -2,6 +2,7 @@
/**
* @group Http
+ * @group small
*/
class HttpTest extends MediaWikiTestCase {
/**
@@ -495,8 +496,11 @@ class HttpTest extends MediaWikiTestCase {
* where it did not define a cURL constant. T72570
*
* @dataProvider provideCurlConstants
+ * @coversNothing
*/
public function testCurlConstants( $value ) {
+ $this->checkPHPExtension( 'curl' );
+
$this->assertTrue( defined( $value ), $value . ' not defined' );
}
}
@@ -507,7 +511,7 @@ class HttpTest extends MediaWikiTestCase {
class MWHttpRequestTester extends MWHttpRequest {
// function derived from the MWHttpRequest factory function but
// returns appropriate tester class here
- public static function factory( $url, $options = null, $caller = __METHOD__ ) {
+ public static function factory( $url, array $options = null, $caller = __METHOD__ ) {
if ( !Http::$httpEngine ) {
Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php';
} elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
diff --git a/www/wiki/tests/phpunit/includes/import/ImportLinkCacheIntegrationTest.php b/www/wiki/tests/phpunit/includes/import/ImportLinkCacheIntegrationTest.php
index bdb4831a..1db2215e 100644
--- a/www/wiki/tests/phpunit/includes/import/ImportLinkCacheIntegrationTest.php
+++ b/www/wiki/tests/phpunit/includes/import/ImportLinkCacheIntegrationTest.php
@@ -5,8 +5,10 @@ use MediaWiki\MediaWikiServices;
* Integration test that checks import success and
* LinkCache integration.
*
- * @group medium
+ * @group large
* @group Database
+ * @covers ImportStreamSource
+ * @covers ImportReporter
*
* @author mwjames
*/
@@ -89,20 +91,11 @@ class ImportLinkCacheIntegrationTest extends MediaWikiTestCase {
$reporter->setContext( new RequestContext() );
$reporter->open();
- $exception = false;
- try {
- $importer->doImport();
- } catch ( Exception $e ) {
- $exception = $e;
- }
+ $importer->doImport();
$result = $reporter->close();
- $this->assertFalse(
- $exception
- );
-
$this->assertTrue(
$result->isGood()
);
diff --git a/www/wiki/tests/phpunit/includes/import/ImportTest.php b/www/wiki/tests/phpunit/includes/import/ImportTest.php
index 53d91c65..3b91f5b3 100644
--- a/www/wiki/tests/phpunit/includes/import/ImportTest.php
+++ b/www/wiki/tests/phpunit/includes/import/ImportTest.php
@@ -37,7 +37,7 @@ class ImportTest extends MediaWikiLangTestCase {
}
public function getUnknownTagsXML() {
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return [
[
<<< EOF
@@ -71,7 +71,7 @@ EOF
'TestImportPage'
]
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
/**
@@ -102,7 +102,7 @@ EOF
}
public function getRedirectXML() {
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return [
[
<<< EOF
@@ -157,7 +157,7 @@ EOF
null
],
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
/**
@@ -185,7 +185,7 @@ EOF
}
public function getSiteInfoXML() {
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return [
[
<<< EOF
@@ -217,7 +217,113 @@ EOF
]
],
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
+ }
+
+ /**
+ * @dataProvider provideUnknownUserHandling
+ * @param bool $assign
+ * @param bool $create
+ */
+ public function testUnknownUserHandling( $assign, $create ) {
+ $hookId = -99;
+ $this->setMwGlobals( 'wgHooks', [
+ 'ImportHandleUnknownUser' => [ function ( $name ) use ( $assign, $create, &$hookId ) {
+ if ( !$assign ) {
+ $this->fail( 'ImportHandleUnknownUser was called unexpectedly' );
+ }
+
+ $this->assertEquals( 'UserDoesNotExist', $name );
+ if ( $create ) {
+ $user = User::createNew( $name );
+ $this->assertNotNull( $user );
+ $hookId = $user->getId();
+ return false;
+ }
+ return true;
+ } ]
+ ] );
+
+ $user = $this->getTestUser()->getUser();
+
+ $n = ( $assign ? 1 : 0 ) + ( $create ? 2 : 0 );
+
+ // phpcs:disable Generic.Files.LineLength
+ $source = $this->getDataSource( <<<EOF
+<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.10/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.10/ http://www.mediawiki.org/xml/export-0.10.xsd" version="0.10" xml:lang="en">
+ <page>
+ <title>TestImportPage</title>
+ <ns>0</ns>
+ <id>14</id>
+ <revision>
+ <id>15</id>
+ <timestamp>2016-01-01T0$n:00:00Z</timestamp>
+ <contributor>
+ <username>UserDoesNotExist</username>
+ <id>1</id>
+ </contributor>
+ <model>wikitext</model>
+ <format>text/x-wiki</format>
+ <text xml:space="preserve" bytes="3">foo</text>
+ <sha1>1e6gpc3ehk0mu2jqu8cg42g009s796b</sha1>
+ </revision>
+ <revision>
+ <id>16</id>
+ <timestamp>2016-01-01T0$n:00:01Z</timestamp>
+ <contributor>
+ <username>{$user->getName()}</username>
+ <id>{$user->getId()}</id>
+ </contributor>
+ <model>wikitext</model>
+ <format>text/x-wiki</format>
+ <text xml:space="preserve" bytes="3">bar</text>
+ <sha1>bjhlo6dxh5wivnszm93u4b78fheiy4t</sha1>
+ </revision>
+ </page>
+</mediawiki>
+EOF
+ );
+ // phpcs:enable
+
+ $importer = new WikiImporter( $source, MediaWikiServices::getInstance()->getMainConfig() );
+ $importer->setUsernamePrefix( 'Xxx', $assign );
+ $importer->doImport();
+
+ $db = wfGetDB( DB_MASTER );
+ $revQuery = Revision::getQueryInfo();
+
+ $row = $db->selectRow(
+ $revQuery['tables'],
+ $revQuery['fields'],
+ [ 'rev_timestamp' => $db->timestamp( "201601010{$n}0000" ) ],
+ __METHOD__,
+ [],
+ $revQuery['joins']
+ );
+ $this->assertSame(
+ $assign && $create ? 'UserDoesNotExist' : 'Xxx>UserDoesNotExist',
+ $row->rev_user_text
+ );
+ $this->assertSame( $assign && $create ? $hookId : 0, (int)$row->rev_user );
+
+ $row = $db->selectRow(
+ $revQuery['tables'],
+ $revQuery['fields'],
+ [ 'rev_timestamp' => $db->timestamp( "201601010{$n}0001" ) ],
+ __METHOD__,
+ [],
+ $revQuery['joins']
+ );
+ $this->assertSame( ( $assign ? '' : 'Xxx>' ) . $user->getName(), $row->rev_user_text );
+ $this->assertSame( $assign ? $user->getId() : 0, (int)$row->rev_user );
+ }
+
+ public static function provideUnknownUserHandling() {
+ return [
+ 'no assign' => [ false, false ],
+ 'assign, no create' => [ true, false ],
+ 'assign, create' => [ true, true ],
+ ];
}
}
diff --git a/www/wiki/tests/phpunit/includes/installer/DatabaseUpdaterTest.php b/www/wiki/tests/phpunit/includes/installer/DatabaseUpdaterTest.php
deleted file mode 100644
index 5e5e921a..00000000
--- a/www/wiki/tests/phpunit/includes/installer/DatabaseUpdaterTest.php
+++ /dev/null
@@ -1,279 +0,0 @@
-<?php
-
-class DatabaseUpdaterTest extends MediaWikiTestCase {
-
- public function testSetAppliedUpdates() {
- $db = new FakeDatabase();
- $dbu = new FakeDatabaseUpdater( $db );
- $dbu->setAppliedUpdates( "test", [] );
- $expected = "updatelist-test-" . time() . "0";
- $actual = $db->lastInsertData['ul_key'];
- $this->assertEquals( $expected, $actual, var_export( $db->lastInsertData, true ) );
- $dbu->setAppliedUpdates( "test", [] );
- $expected = "updatelist-test-" . time() . "1";
- $actual = $db->lastInsertData['ul_key'];
- $this->assertEquals( $expected, $actual, var_export( $db->lastInsertData, true ) );
- }
-}
-
-class FakeDatabase extends DatabaseBase {
- public $lastInsertTable;
- public $lastInsertData;
-
- function __construct() {
- }
-
- function clearFlag( $arg ) {
- }
-
- function setFlag( $arg ) {
- }
-
- public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
- $this->lastInsertTable = $table;
- $this->lastInsertData = $a;
- }
-
- /**
- * Get the type of the DBMS, as it appears in $wgDBtype.
- *
- * @return string
- */
- function getType() {
- // TODO: Implement getType() method.
- }
-
- /**
- * Open a connection to the database. Usually aborts on failure
- *
- * @param string $server Database server host
- * @param string $user Database user name
- * @param string $password Database user password
- * @param string $dbName Database name
- * @return bool
- * @throws DBConnectionError
- */
- function open( $server, $user, $password, $dbName ) {
- // TODO: Implement open() method.
- }
-
- /**
- * Fetch the next row from the given result object, in object form.
- * Fields can be retrieved with $row->fieldname, with fields acting like
- * member variables.
- * If no more rows are available, false is returned.
- *
- * @param ResultWrapper|stdClass $res Object as returned from DatabaseBase::query(), etc.
- * @return stdClass|bool
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- function fetchObject( $res ) {
- // TODO: Implement fetchObject() method.
- }
-
- /**
- * Fetch the next row from the given result object, in associative array
- * form. Fields are retrieved with $row['fieldname'].
- * If no more rows are available, false is returned.
- *
- * @param ResultWrapper $res Result object as returned from DatabaseBase::query(), etc.
- * @return array|bool
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- function fetchRow( $res ) {
- // TODO: Implement fetchRow() method.
- }
-
- /**
- * Get the number of rows in a result object
- *
- * @param mixed $res A SQL result
- * @return int
- */
- function numRows( $res ) {
- // TODO: Implement numRows() method.
- }
-
- /**
- * Get the number of fields in a result object
- * @see http://www.php.net/mysql_num_fields
- *
- * @param mixed $res A SQL result
- * @return int
- */
- function numFields( $res ) {
- // TODO: Implement numFields() method.
- }
-
- /**
- * Get a field name in a result object
- * @see http://www.php.net/mysql_field_name
- *
- * @param mixed $res A SQL result
- * @param int $n
- * @return string
- */
- function fieldName( $res, $n ) {
- // TODO: Implement fieldName() method.
- }
-
- /**
- * Get the inserted value of an auto-increment row
- *
- * The value inserted should be fetched from nextSequenceValue()
- *
- * Example:
- * $id = $dbw->nextSequenceValue( 'page_page_id_seq' );
- * $dbw->insert( 'page', array( 'page_id' => $id ) );
- * $id = $dbw->insertId();
- *
- * @return int
- */
- function insertId() {
- // TODO: Implement insertId() method.
- }
-
- /**
- * Change the position of the cursor in a result object
- * @see http://www.php.net/mysql_data_seek
- *
- * @param mixed $res A SQL result
- * @param int $row
- */
- function dataSeek( $res, $row ) {
- // TODO: Implement dataSeek() method.
- }
-
- /**
- * Get the last error number
- * @see http://www.php.net/mysql_errno
- *
- * @return int
- */
- function lastErrno() {
- // TODO: Implement lastErrno() method.
- }
-
- /**
- * Get a description of the last error
- * @see http://www.php.net/mysql_error
- *
- * @return string
- */
- function lastError() {
- // TODO: Implement lastError() method.
- }
-
- /**
- * mysql_fetch_field() wrapper
- * Returns false if the field doesn't exist
- *
- * @param string $table Table name
- * @param string $field Field name
- *
- * @return Field
- */
- function fieldInfo( $table, $field ) {
- // TODO: Implement fieldInfo() method.
- }
-
- /**
- * Get information about an index into an object
- * @param string $table Table name
- * @param string $index Index name
- * @param string $fname Calling function name
- * @return mixed Database-specific index description class or false if the index does not exist
- */
- function indexInfo( $table, $index, $fname = __METHOD__ ) {
- // TODO: Implement indexInfo() method.
- }
-
- /**
- * Get the number of rows affected by the last write query
- * @see http://www.php.net/mysql_affected_rows
- *
- * @return int
- */
- function affectedRows() {
- // TODO: Implement affectedRows() method.
- }
-
- /**
- * Wrapper for addslashes()
- *
- * @param string $s String to be slashed.
- * @return string Slashed string.
- */
- function strencode( $s ) {
- // TODO: Implement strencode() method.
- }
-
- /**
- * Returns a wikitext link to the DB's website, e.g.,
- * return "[http://www.mysql.com/ MySQL]";
- * Should at least contain plain text, if for some reason
- * your database has no website.
- *
- * @return string Wikitext of a link to the server software's web site
- */
- function getSoftwareLink() {
- // TODO: Implement getSoftwareLink() method.
- }
-
- /**
- * A string describing the current software version, like from
- * mysql_get_server_info().
- *
- * @return string Version information from the database server.
- */
- function getServerVersion() {
- // TODO: Implement getServerVersion() method.
- }
-
- /**
- * Closes underlying database connection
- * @since 1.20
- * @return bool Whether connection was closed successfully
- */
- protected function closeConnection() {
- // TODO: Implement closeConnection() method.
- }
-
- /**
- * The DBMS-dependent part of query()
- *
- * @param string $sql SQL query.
- * @return ResultWrapper|bool Result object to feed to fetchObject,
- * fetchRow, ...; or false on failure
- */
- protected function doQuery( $sql ) {
- // TODO: Implement doQuery() method.
- }
-}
-
-class FakeDatabaseUpdater extends DatabaseUpdater {
- function __construct( $db ) {
- $this->db = $db;
- self::$updateCounter = 0;
- }
-
- /**
- * Get an array of updates to perform on the database. Should return a
- * multi-dimensional array. The main key is the MediaWiki version (1.12,
- * 1.13...) with the values being arrays of updates, identical to how
- * updaters.inc did it (for now)
- *
- * @return array
- */
- protected function getCoreUpdateList() {
- return [];
- }
-
- public function canUseNewUpdatelog() {
- return true;
- }
-
- public function setAppliedUpdates( $version, $updates = [] ) {
- parent::setAppliedUpdates( $version, $updates );
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/installer/InstallDocFormatterTest.php b/www/wiki/tests/phpunit/includes/installer/InstallDocFormatterTest.php
index 36b6d64f..9584d4b8 100644
--- a/www/wiki/tests/phpunit/includes/installer/InstallDocFormatterTest.php
+++ b/www/wiki/tests/phpunit/includes/installer/InstallDocFormatterTest.php
@@ -1,12 +1,8 @@
<?php
-/*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
class InstallDocFormatterTest extends MediaWikiTestCase {
/**
- * @covers InstallDocFormatter::format
+ * @covers InstallDocFormatter
* @dataProvider provideDocFormattingTests
*/
public function testFormat( $expected, $unformattedText, $message = '' ) {
diff --git a/www/wiki/tests/phpunit/includes/installer/OracleInstallerTest.php b/www/wiki/tests/phpunit/includes/installer/OracleInstallerTest.php
index bd1412eb..2811a9cf 100644
--- a/www/wiki/tests/phpunit/includes/installer/OracleInstallerTest.php
+++ b/www/wiki/tests/phpunit/includes/installer/OracleInstallerTest.php
@@ -6,7 +6,6 @@
* @group Database
* @group Installer
*/
-
class OracleInstallerTest extends MediaWikiTestCase {
/**
diff --git a/www/wiki/tests/phpunit/includes/interwiki/ClassicInterwikiLookupTest.php b/www/wiki/tests/phpunit/includes/interwiki/ClassicInterwikiLookupTest.php
index fd3b0b85..7fb2cd49 100644
--- a/www/wiki/tests/phpunit/includes/interwiki/ClassicInterwikiLookupTest.php
+++ b/www/wiki/tests/phpunit/includes/interwiki/ClassicInterwikiLookupTest.php
@@ -68,7 +68,7 @@ class ClassicInterwikiLookupTest extends MediaWikiTestCase {
$this->assertFalse( $lookup->fetch( 'xyz' ), 'unknown prefix' );
$interwiki = $lookup->fetch( 'de' );
- $this->assertInstanceOf( 'Interwiki', $interwiki );
+ $this->assertInstanceOf( Interwiki::class, $interwiki );
$this->assertSame( $interwiki, $lookup->fetch( 'de' ), 'in-process caching' );
$this->assertSame( 'http://de.wikipedia.org/wiki/', $interwiki->getURL(), 'getURL' );
@@ -169,13 +169,13 @@ class ClassicInterwikiLookupTest extends MediaWikiTestCase {
$this->assertTrue( $lookup->isValidInterwiki( 'zz' ), 'known prefix is valid' );
$interwiki = $lookup->fetch( 'de' );
- $this->assertInstanceOf( 'Interwiki', $interwiki );
+ $this->assertInstanceOf( Interwiki::class, $interwiki );
$this->assertSame( 'http://de.wikipedia.org/wiki/', $interwiki->getURL(), 'getURL' );
$this->assertSame( true, $interwiki->isLocal(), 'isLocal' );
$interwiki = $lookup->fetch( 'zz' );
- $this->assertInstanceOf( 'Interwiki', $interwiki );
+ $this->assertInstanceOf( Interwiki::class, $interwiki );
$this->assertSame( 'http://zzwiki.org/wiki/', $interwiki->getURL(), 'getURL' );
$this->assertSame( false, $interwiki->isLocal(), 'isLocal' );
@@ -220,13 +220,13 @@ class ClassicInterwikiLookupTest extends MediaWikiTestCase {
$this->assertTrue( $lookup->isValidInterwiki( 'zz' ), 'known prefix is valid' );
$interwiki = $lookup->fetch( 'de' );
- $this->assertInstanceOf( 'Interwiki', $interwiki );
+ $this->assertInstanceOf( Interwiki::class, $interwiki );
$this->assertSame( 'http://de.wikipedia.org/wiki/', $interwiki->getURL(), 'getURL' );
$this->assertSame( true, $interwiki->isLocal(), 'isLocal' );
$interwiki = $lookup->fetch( 'zz' );
- $this->assertInstanceOf( 'Interwiki', $interwiki );
+ $this->assertInstanceOf( Interwiki::class, $interwiki );
$this->assertSame( 'http://zzwiki.org/wiki/', $interwiki->getURL(), 'getURL' );
$this->assertSame( false, $interwiki->isLocal(), 'isLocal' );
diff --git a/www/wiki/tests/phpunit/includes/interwiki/InterwikiLookupAdapterTest.php b/www/wiki/tests/phpunit/includes/interwiki/InterwikiLookupAdapterTest.php
index 31c9e50c..0a13de1d 100644
--- a/www/wiki/tests/phpunit/includes/interwiki/InterwikiLookupAdapterTest.php
+++ b/www/wiki/tests/phpunit/includes/interwiki/InterwikiLookupAdapterTest.php
@@ -1,13 +1,13 @@
<?php
+use MediaWiki\Interwiki\InterwikiLookupAdapter;
+
/**
* @covers MediaWiki\Interwiki\InterwikiLookupAdapter
*
* @group MediaWiki
* @group Interwiki
*/
-use MediaWiki\Interwiki\InterwikiLookupAdapter;
-
class InterwikiLookupAdapterTest extends MediaWikiTestCase {
/**
diff --git a/www/wiki/tests/phpunit/includes/interwiki/InterwikiTest.php b/www/wiki/tests/phpunit/includes/interwiki/InterwikiTest.php
index 22b1304b..0d41c520 100644
--- a/www/wiki/tests/phpunit/includes/interwiki/InterwikiTest.php
+++ b/www/wiki/tests/phpunit/includes/interwiki/InterwikiTest.php
@@ -106,7 +106,7 @@ class InterwikiTest extends MediaWikiTestCase {
$this->assertFalse( $interwikiLookup->fetch( 'xyz' ), 'unknown prefix' );
$interwiki = $interwikiLookup->fetch( 'de' );
- $this->assertInstanceOf( 'Interwiki', $interwiki );
+ $this->assertInstanceOf( Interwiki::class, $interwiki );
$this->assertSame( $interwiki, $interwikiLookup->fetch( 'de' ), 'in-process caching' );
$this->assertSame( 'http://de.wikipedia.org/wiki/', $interwiki->getURL(), 'getURL' );
diff --git a/www/wiki/tests/phpunit/includes/jobqueue/JobQueueMemoryTest.php b/www/wiki/tests/phpunit/includes/jobqueue/JobQueueMemoryTest.php
index 4b03fda7..bf8603dd 100644
--- a/www/wiki/tests/phpunit/includes/jobqueue/JobQueueMemoryTest.php
+++ b/www/wiki/tests/phpunit/includes/jobqueue/JobQueueMemoryTest.php
@@ -5,17 +5,19 @@
*
* @group JobQueue
*
- * @licence GNU GPL v2+
- * @author Thiemo Mättig
+ * @license GNU GPL v2+
+ * @author Thiemo Kreuz
*/
-class JobQueueMemoryTest extends PHPUnit_Framework_TestCase {
+class JobQueueMemoryTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
/**
* @return JobQueueMemory
*/
private function newJobQueue() {
return JobQueue::factory( [
- 'class' => 'JobQueueMemory',
+ 'class' => JobQueueMemory::class,
'wiki' => wfWikiID(),
'type' => 'null',
] );
@@ -52,7 +54,7 @@ class JobQueueMemoryTest extends PHPUnit_Framework_TestCase {
public function testJobFromSpecInternal() {
$queue = $this->newJobQueue();
$job = $queue->jobFromSpecInternal( $this->newJobSpecification() );
- $this->assertInstanceOf( 'Job', $job );
+ $this->assertInstanceOf( Job::class, $job );
$this->assertSame( 'null', $job->getType() );
$this->assertArrayHasKey( 'customParameter', $job->getParams() );
$this->assertSame( 'Custom title', $job->getTitle()->getText() );
diff --git a/www/wiki/tests/phpunit/includes/jobqueue/JobQueueTest.php b/www/wiki/tests/phpunit/includes/jobqueue/JobQueueTest.php
index 7b34b59b..64dde778 100644
--- a/www/wiki/tests/phpunit/includes/jobqueue/JobQueueTest.php
+++ b/www/wiki/tests/phpunit/includes/jobqueue/JobQueueTest.php
@@ -1,5 +1,7 @@
<?php
+use MediaWiki\MediaWikiServices;
+
/**
* @group JobQueue
* @group medium
@@ -26,7 +28,7 @@ class JobQueueTest extends MediaWikiTestCase {
}
$baseConfig = $wgJobTypeConf[$name];
} else {
- $baseConfig = [ 'class' => 'JobQueueDB' ];
+ $baseConfig = [ 'class' => JobQueueDBSingle::class ];
}
$baseConfig['type'] = 'null';
$baseConfig['wiki'] = wfWikiID();
@@ -232,7 +234,7 @@ class JobQueueTest extends MediaWikiTestCase {
$j = $queue->pop();
// Make sure ack() of the twin did not delete the sibling data
- $this->assertType( 'NullJob', $j );
+ $this->assertType( NullJob::class, $j );
}
/**
@@ -381,3 +383,11 @@ class JobQueueTest extends MediaWikiTestCase {
[ 'lives' => 0, 'usleep' => 0, 'removeDuplicates' => 1, 'i' => $i ] + $rootJob );
}
}
+
+class JobQueueDBSingle extends JobQueueDB {
+ protected function getDB( $index ) {
+ $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+ // Override to not use CONN_TRX_AUTOCOMMIT so that we see the same temporary `job` table
+ return $lb->getConnection( $index, [], $this->wiki );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/jobqueue/JobTest.php b/www/wiki/tests/phpunit/includes/jobqueue/JobTest.php
index 6723a0bf..0cab7024 100644
--- a/www/wiki/tests/phpunit/includes/jobqueue/JobTest.php
+++ b/www/wiki/tests/phpunit/includes/jobqueue/JobTest.php
@@ -18,7 +18,7 @@ class JobTest extends MediaWikiTestCase {
}
public function provideTestToString() {
- $mockToStringObj = $this->getMockBuilder( 'stdClass' )
+ $mockToStringObj = $this->getMockBuilder( stdClass::class )
->setMethods( [ '__toString' ] )->getMock();
$mockToStringObj->expects( $this->any() )
->method( '__toString' )
@@ -75,8 +75,10 @@ class JobTest extends MediaWikiTestCase {
'someCommand pages={"932737":[0,"Robert_James_Waller"]} ' .
'rootJobSignature=45868e99bba89064e4483743ebb9b682ef95c1a7 ' .
'rootJobTimestamp=20160309110158 masterPos=' .
- '{"file":"db1023-bin.001288","pos":"308257743","asOfTime":1457521464.3814} ' .
- 'triggeredRecursive=1 ' .
+ '{"file":"db1023-bin.001288","pos":"308257743","asOfTime":' .
+ // Embed dynamically because TestSetup sets serialize_precision=17
+ // which, in PHP 7.1 and 7.2, produces 1457521464.3814001 instead
+ json_encode( 1457521464.3814 ) . '} ' . 'triggeredRecursive=1 ' .
$requestId
],
];
@@ -84,7 +86,7 @@ class JobTest extends MediaWikiTestCase {
public function getMockJob( $params ) {
$mock = $this->getMockForAbstractClass(
- 'Job',
+ Job::class,
[ 'someCommand', new Title(), $params ],
'SomeJob'
);
@@ -99,7 +101,7 @@ class JobTest extends MediaWikiTestCase {
* @covers Job::factory
*/
public function testJobFactory( $handler ) {
- $this->mergeMWGlobalArrayValue( 'wgJobClasses', [ 'testdummy' => $handler ] );
+ $this->mergeMwGlobalArrayValue( 'wgJobClasses', [ 'testdummy' => $handler ] );
$job = Job::factory( 'testdummy', Title::newMainPage(), [] );
$this->assertInstanceOf( NullJob::class, $job );
diff --git a/www/wiki/tests/phpunit/includes/jobqueue/RefreshLinksPartitionTest.php b/www/wiki/tests/phpunit/includes/jobqueue/RefreshLinksPartitionTest.php
index 43d626db..f874f6de 100644
--- a/www/wiki/tests/phpunit/includes/jobqueue/RefreshLinksPartitionTest.php
+++ b/www/wiki/tests/phpunit/includes/jobqueue/RefreshLinksPartitionTest.php
@@ -16,6 +16,7 @@ class RefreshLinksPartitionTest extends MediaWikiTestCase {
/**
* @dataProvider provider_backlinks
+ * @covers BacklinkJobUtils::partitionBacklinkJob
*/
public function testRefreshLinks( $ns, $dbKey, $pages ) {
$title = Title::makeTitle( $ns, $dbKey );
diff --git a/www/wiki/tests/phpunit/includes/jobqueue/jobs/CategoryMembershipChangeJobTest.php b/www/wiki/tests/phpunit/includes/jobqueue/jobs/CategoryMembershipChangeJobTest.php
index 656be381..5960a16b 100644
--- a/www/wiki/tests/phpunit/includes/jobqueue/jobs/CategoryMembershipChangeJobTest.php
+++ b/www/wiki/tests/phpunit/includes/jobqueue/jobs/CategoryMembershipChangeJobTest.php
@@ -6,7 +6,7 @@
* @group JobQueue
* @group Database
*
- * @licence GNU GPL v2+
+ * @license GNU GPL v2+
* @author Addshore
*/
class CategoryMembershipChangeJobTest extends MediaWikiTestCase {
diff --git a/www/wiki/tests/phpunit/includes/jobqueue/jobs/ClearUserWatchlistJobTest.php b/www/wiki/tests/phpunit/includes/jobqueue/jobs/ClearUserWatchlistJobTest.php
new file mode 100644
index 00000000..6ae7d605
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/jobqueue/jobs/ClearUserWatchlistJobTest.php
@@ -0,0 +1,79 @@
+<?php
+use MediaWiki\MediaWikiServices;
+
+/**
+ * @covers ClearUserWatchlistJob
+ *
+ * @group JobQueue
+ * @group Database
+ *
+ * @license GNU GPL v2+
+ * @author Addshore
+ */
+class ClearUserWatchlistJobTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ parent::setUp();
+ self::$users['ClearUserWatchlistJobTestUser']
+ = new TestUser( 'ClearUserWatchlistJobTestUser' );
+ $this->runJobs();
+ JobQueueGroup::destroySingletons();
+ }
+
+ private function getUser() {
+ return self::$users['ClearUserWatchlistJobTestUser']->getUser();
+ }
+
+ private function runJobs( $jobLimit = 9999 ) {
+ $runJobs = new RunJobs;
+ $runJobs->loadParamsAndArgs( null, [ 'quiet' => true, 'maxjobs' => $jobLimit ] );
+ $runJobs->execute();
+ }
+
+ private function getWatchedItemStore() {
+ return MediaWikiServices::getInstance()->getWatchedItemStore();
+ }
+
+ public function testRun() {
+ $user = $this->getUser();
+ $watchedItemStore = $this->getWatchedItemStore();
+
+ $watchedItemStore->addWatch( $user, new TitleValue( 0, 'A' ) );
+ $watchedItemStore->addWatch( $user, new TitleValue( 1, 'A' ) );
+ $watchedItemStore->addWatch( $user, new TitleValue( 0, 'B' ) );
+ $watchedItemStore->addWatch( $user, new TitleValue( 1, 'B' ) );
+
+ $maxId = $watchedItemStore->getMaxId();
+
+ $watchedItemStore->addWatch( $user, new TitleValue( 0, 'C' ) );
+ $watchedItemStore->addWatch( $user, new TitleValue( 1, 'C' ) );
+
+ $this->setMwGlobals( 'wgUpdateRowsPerQuery', 2 );
+
+ JobQueueGroup::singleton()->push(
+ new ClearUserWatchlistJob(
+ null,
+ [
+ 'userId' => $user->getId(),
+ 'maxWatchlistId' => $maxId,
+ ]
+ )
+ );
+
+ $this->assertEquals( 1, JobQueueGroup::singleton()->getQueueSizes()['clearUserWatchlist'] );
+ $this->assertEquals( 6, $watchedItemStore->countWatchedItems( $user ) );
+ $this->runJobs( 1 );
+ $this->assertEquals( 1, JobQueueGroup::singleton()->getQueueSizes()['clearUserWatchlist'] );
+ $this->assertEquals( 4, $watchedItemStore->countWatchedItems( $user ) );
+ $this->runJobs( 1 );
+ $this->assertEquals( 1, JobQueueGroup::singleton()->getQueueSizes()['clearUserWatchlist'] );
+ $this->assertEquals( 2, $watchedItemStore->countWatchedItems( $user ) );
+ $this->runJobs( 1 );
+ $this->assertEquals( 0, JobQueueGroup::singleton()->getQueueSizes()['clearUserWatchlist'] );
+ $this->assertEquals( 2, $watchedItemStore->countWatchedItems( $user ) );
+
+ $this->assertTrue( $watchedItemStore->isWatched( $user, new TitleValue( 0, 'C' ) ) );
+ $this->assertTrue( $watchedItemStore->isWatched( $user, new TitleValue( 1, 'C' ) ) );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/json/FormatJsonTest.php b/www/wiki/tests/phpunit/includes/json/FormatJsonTest.php
index d252c807..a4ab879f 100644
--- a/www/wiki/tests/phpunit/includes/json/FormatJsonTest.php
+++ b/www/wiki/tests/phpunit/includes/json/FormatJsonTest.php
@@ -159,12 +159,12 @@ class FormatJsonTest extends MediaWikiTestCase {
$this->assertJson( $json );
$st = FormatJson::parse( $json );
- $this->assertInstanceOf( 'Status', $st );
+ $this->assertInstanceOf( Status::class, $st );
$this->assertTrue( $st->isGood() );
$this->assertEquals( $expected, $st->getValue() );
$st = FormatJson::parse( $json, FormatJson::FORCE_ASSOC );
- $this->assertInstanceOf( 'Status', $st );
+ $this->assertInstanceOf( Status::class, $st );
$this->assertTrue( $st->isGood() );
$this->assertEquals( $value, $st->getValue() );
}
@@ -230,7 +230,7 @@ class FormatJsonTest extends MediaWikiTestCase {
}
$st = FormatJson::parse( $value, FormatJson::TRY_FIXING );
- $this->assertInstanceOf( 'Status', $st );
+ $this->assertInstanceOf( Status::class, $st );
if ( $expected === false ) {
$this->assertFalse( $st->isOK(), 'Expected isOK() == false' );
} else {
@@ -256,7 +256,7 @@ class FormatJsonTest extends MediaWikiTestCase {
*/
public function testParseErrors( $value ) {
$st = FormatJson::parse( $value );
- $this->assertInstanceOf( 'Status', $st );
+ $this->assertInstanceOf( Status::class, $st );
$this->assertFalse( $st->isOK() );
}
@@ -313,7 +313,7 @@ class FormatJsonTest extends MediaWikiTestCase {
*/
public function testParseStripComments( $json, $expect ) {
$st = FormatJson::parse( $json, FormatJson::STRIP_COMMENTS );
- $this->assertInstanceOf( 'Status', $st );
+ $this->assertInstanceOf( Status::class, $st );
$this->assertTrue( $st->isGood() );
$this->assertEquals( $expect, $st->getValue() );
}
diff --git a/www/wiki/tests/phpunit/includes/libs/ArrayUtilsTest.php b/www/wiki/tests/phpunit/includes/libs/ArrayUtilsTest.php
index 21472d55..d5ac77bb 100644
--- a/www/wiki/tests/phpunit/includes/libs/ArrayUtilsTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/ArrayUtilsTest.php
@@ -4,9 +4,9 @@
*
* @group Database
*/
+class ArrayUtilsTest extends PHPUnit\Framework\TestCase {
-class ArrayUtilsTest extends PHPUnit_Framework_TestCase {
- private $search;
+ use MediaWikiCoversValidator;
/**
* @covers ArrayUtils::findLowerBound
diff --git a/www/wiki/tests/phpunit/includes/libs/CSSMinTest.php b/www/wiki/tests/phpunit/includes/libs/CSSMinTest.php
index b06df976..46bf2c6c 100644
--- a/www/wiki/tests/phpunit/includes/libs/CSSMinTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/CSSMinTest.php
@@ -1,9 +1,6 @@
<?php
-/**
- * This file test the CSSMin library shipped with Mediawiki.
- *
- * @author Timo Tijhof
- */
+
+use Wikimedia\TestingAccessWrapper;
/**
* @group ResourceLoader
@@ -14,8 +11,8 @@ class CSSMinTest extends MediaWikiTestCase {
protected function setUp() {
parent::setUp();
- $server = 'http://doc.example.org';
-
+ // For wfExpandUrl
+ $server = 'https://expand.example';
$this->setMwGlobals( [
'wgServer' => $server,
'wgCanonicalServer' => $server,
@@ -23,7 +20,44 @@ class CSSMinTest extends MediaWikiTestCase {
}
/**
- * @dataProvider mimeTypeProvider
+ * @dataProvider provideSerializeStringValue
+ * @covers CSSMin::serializeStringValue
+ */
+ public function testSerializeStringValue( $input, $expected ) {
+ $output = CSSMin::serializeStringValue( $input );
+ $this->assertEquals(
+ $expected,
+ $output,
+ 'Serialized output must be in the expected form.'
+ );
+ }
+
+ public static function provideSerializeStringValue() {
+ return [
+ [ 'Hello World!', '"Hello World!"' ],
+ [ "Null\0Null", "\"Null\\fffd Null\"" ],
+ [ '"', '"\\""' ],
+ [ "'", '"\'"' ],
+ [ "\\", '"\\\\"' ],
+ [ "Tab\tTab", '"Tab\\9 Tab"' ],
+ [ "Space tab \t space", '"Space tab \\9 space"' ],
+ [ "Line\nfeed", '"Line\\a feed"' ],
+ [ "Return\rreturn", '"Return\\d return"' ],
+ [ "Next\xc2\x85line", "\"Next\xc2\x85line\"" ],
+ [ "Del\x7fDel", '"Del\\7f Del"' ],
+ [ "nb\xc2\xa0sp", "\"nb\xc2\xa0sp\"" ],
+ [ "AMP&amp;AMP", "\"AMP&amp;AMP\"" ],
+ [ '!"#$%&\'()*+,-./0123456789:;<=>?', '"!\\"#$%&\'()*+,-./0123456789:;<=>?"' ],
+ [ '@[\\]^_`{|}~', '"@[\\\\]^_`{|}~"' ],
+ [ 'ä', '"ä"' ],
+ [ 'Ä', '"Ä"' ],
+ [ '€', '"€"' ],
+ [ '𝒞', '"𝒞"' ], // U+1D49E 'MATHEMATICAL SCRIPT CAPITAL C'
+ ];
+ }
+
+ /**
+ * @dataProvider provideMimeType
* @covers CSSMin::getMimeType
*/
public function testGetMimeType( $fileContents, $fileExtension, $expected ) {
@@ -34,7 +68,7 @@ class CSSMinTest extends MediaWikiTestCase {
$this->assertSame( $expected, CSSMin::getMimeType( $fileName ) );
}
- public function mimeTypeProvider() {
+ public static function provideMimeType() {
return [
'JPEG with short extension' => [
"\xFF\xD8\xFF",
@@ -149,6 +183,12 @@ class CSSMinTest extends MediaWikiTestCase {
[ "foo { content: '\"'; }", "foo{content:'\"'}" ],
// - Whitespace in string values
[ 'foo { content: " "; }', 'foo{content:" "}' ],
+
+ // Whitespaces after opening and before closing parentheses and brackets
+ [ 'a:not( [ href ] ) { prop: url( foobar.png ); }', 'a:not([href]){prop:url(foobar.png)}' ],
+
+ // Ensure that the invalid "url (" will not become the valid "url(" by minification
+ [ 'foo { prop: url ( foobar.png ); }', 'foo{prop:url (foobar.png)}' ],
];
}
@@ -171,7 +211,8 @@ class CSSMinTest extends MediaWikiTestCase {
* @covers CSSMin::isRemoteUrl
*/
public function testIsRemoteUrl( $expect, $url ) {
- $this->assertEquals( CSSMinTestable::isRemoteUrl( $url ), $expect );
+ $class = TestingAccessWrapper::newFromClass( CSSMin::class );
+ $this->assertEquals( $class->isRemoteUrl( $url ), $expect );
}
public static function provideIsLocalUrls() {
@@ -188,7 +229,8 @@ class CSSMinTest extends MediaWikiTestCase {
* @covers CSSMin::isLocalUrl
*/
public function testIsLocalUrl( $expect, $url ) {
- $this->assertEquals( CSSMinTestable::isLocalUrl( $url ), $expect );
+ $class = TestingAccessWrapper::newFromClass( CSSMin::class );
+ $this->assertEquals( $class->isLocalUrl( $url ), $expect );
}
/**
@@ -237,7 +279,7 @@ class CSSMinTest extends MediaWikiTestCase {
[
'Expand absolute paths',
[ 'foo { prop: url(/w/skin/images/bar.png); }', false, 'http://example.org/quux', false ],
- 'foo { prop: url(http://doc.example.org/w/skin/images/bar.png); }',
+ 'foo { prop: url(https://expand.example/w/skin/images/bar.png); }',
],
[
"Don't barf at behavior: url(#default#behaviorName) - T162973",
@@ -248,6 +290,36 @@ class CSSMinTest extends MediaWikiTestCase {
}
/**
+ * Cases with empty url() for CSSMin::remap.
+ *
+ * Regression test for T191237.
+ *
+ * @dataProvider provideRemapEmptyUrl
+ * @covers CSSMin
+ */
+ public function testRemapEmptyUrl( $params, $expected ) {
+ $remapped = call_user_func_array( 'CSSMin::remap', $params );
+ $this->assertEquals( $expected, $remapped, 'Ignore empty url' );
+ }
+
+ public static function provideRemapEmptyUrl() {
+ return [
+ 'Empty' => [
+ [ "background-image: url();", false, '/example', false ],
+ "background-image: url();",
+ ],
+ 'Single quote' => [
+ [ "background-image: url('');", false, '/example', false ],
+ "background-image: url('');",
+ ],
+ 'Double quote' => [
+ [ 'background-image: url("");', false, '/example', false ],
+ 'background-image: url("");',
+ ],
+ ];
+ }
+
+ /**
* This tests the basic functionality of CSSMin::remap.
*
* @see testRemap for testing of funky parameters
@@ -271,11 +343,12 @@ class CSSMinTest extends MediaWikiTestCase {
// data: URIs for red.gif, green.gif, circle.svg
$red = 'data:image/gif;base64,R0lGODlhAQABAIAAAP8AADAAACwAAAAAAQABAAACAkQBADs=';
$green = 'data:image/gif;base64,R0lGODlhAQABAIAAAACAADAAACwAAAAAAQABAAACAkQBADs=';
- $svg = 'data:image/svg+xml,%3C%3Fxml version=%221.0%22 encoding=%22UTF-8%22%3F%3E%0A'
- . '%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%228%22 height='
- . '%228%22%3E%0A%09%3Ccircle cx=%224%22 cy=%224%22 r=%222%22/%3E%0A%3C/svg%3E%0A';
+ $svg = 'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%228'
+ . '%22 height=%228%22 viewBox=%220 0 8 8%22%3E %3Ccircle cx=%224%22 cy=%224%22 '
+ . 'r=%222%22/%3E %3Ca xmlns:xlink=%22http://www.w3.org/1999/xlink%22 xlink:title='
+ . '%22%3F%3E%22%3Etest%3C/a%3E %3C/svg%3E';
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return [
[
'Regular file',
@@ -310,12 +383,12 @@ class CSSMinTest extends MediaWikiTestCase {
[
'Domain-relative URL',
'foo { background: url(/static/foo.png); }',
- 'foo { background: url(http://doc.example.org/static/foo.png); }',
+ 'foo { background: url(https://expand.example/static/foo.png); }',
],
[
'Domain-relative URL with query',
'foo { background: url(/static/foo.png?query=yes); }',
- 'foo { background: url(http://doc.example.org/static/foo.png?query=yes); }',
+ 'foo { background: url(https://expand.example/static/foo.png?query=yes); }',
],
[
'Remote URL (unnecessary quotes not preserved)',
@@ -419,12 +492,12 @@ class CSSMinTest extends MediaWikiTestCase {
[
'@import rule to local file (should we remap this?)',
'@import url(/styles.css)',
- '@import url(http://doc.example.org/styles.css)',
+ '@import url(https://expand.example/styles.css)',
],
[
'@import rule to local file (should we remap this?)',
'@import url(/styles.css)',
- '@import url(http://doc.example.org/styles.css)',
+ '@import url(https://expand.example/styles.css)',
],
[
'@import rule to URL',
@@ -442,7 +515,7 @@ class CSSMinTest extends MediaWikiTestCase {
'foo { background: url(//localhost/styles.css?quoted=single) }',
],
[
- 'Background URL (containing parentheses; T60473)',
+ 'Background URL (double quoted, containing parentheses; T60473)',
'foo { background: url("//localhost/styles.css?query=(parens)") }',
'foo { background: url("//localhost/styles.css?query=(parens)") }',
],
@@ -457,6 +530,11 @@ class CSSMinTest extends MediaWikiTestCase {
'foo { background: url("//localhost/styles.css?quote=\"") }',
],
[
+ 'Background URL (double quoted with outer spacing)',
+ 'foo { background: url( "http://localhost/styles.css?quoted=double" ) }',
+ 'foo { background: url(http://localhost/styles.css?quoted=double) }',
+ ],
+ [
'Simple case with comments before url',
'foo { prop: /* some {funny;} comment */ url(bar.png); }',
'foo { prop: /* some {funny;} comment */ url(http://localhost/w/bar.png); }',
@@ -492,7 +570,7 @@ class CSSMinTest extends MediaWikiTestCase {
'.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3/*{borderColorDefault}*/; background: #e6e6e6/*{bgColorDefault}*/ url(http://localhost/w/images/ui-bg_glass_75_e6e6e6_1x400.png)/*{bgImgUrlDefault}*/ 50%/*{bgDefaultXPos}*/ 50%/*{bgDefaultYPos}*/ repeat-x/*{bgDefaultRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #555555/*{fcDefault}*/; }',
],
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
/**
@@ -560,13 +638,3 @@ class CSSMinTest extends MediaWikiTestCase {
];
}
}
-
-class CSSMinTestable extends CSSMin {
- // Make some protected methods public
- public static function isRemoteUrl( $maybeUrl ) {
- return parent::isRemoteUrl( $maybeUrl );
- }
- public static function isLocalUrl( $maybeUrl ) {
- return parent::isLocalUrl( $maybeUrl );
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/libs/DeferredStringifierTest.php b/www/wiki/tests/phpunit/includes/libs/DeferredStringifierTest.php
index cba29392..c9cdf583 100644
--- a/www/wiki/tests/phpunit/includes/libs/DeferredStringifierTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/DeferredStringifierTest.php
@@ -1,13 +1,17 @@
<?php
-class DeferredStringifierTest extends PHPUnit_Framework_TestCase {
+/**
+ * @covers DeferredStringifier
+ */
+class DeferredStringifierTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
/**
- * @covers DeferredStringifier
* @dataProvider provideToString
*/
public function testToString( $params, $expected ) {
- $class = new ReflectionClass( 'DeferredStringifier' );
+ $class = new ReflectionClass( DeferredStringifier::class );
$ds = $class->newInstanceArgs( $params );
$this->assertEquals( $expected, (string)$ds );
}
diff --git a/www/wiki/tests/phpunit/includes/libs/DnsSrvDiscovererTest.php b/www/wiki/tests/phpunit/includes/libs/DnsSrvDiscovererTest.php
index cfc2d91b..1b3397c1 100644
--- a/www/wiki/tests/phpunit/includes/libs/DnsSrvDiscovererTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/DnsSrvDiscovererTest.php
@@ -1,8 +1,13 @@
<?php
-class DnsSrvDiscovererTest extends PHPUnit_Framework_TestCase {
+/**
+ * @covers DnsSrvDiscoverer
+ */
+class DnsSrvDiscovererTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
/**
- * @covers DnsSrvDiscoverer
* @dataProvider provideRecords
*/
public function testPickServer( $params, $expected ) {
diff --git a/www/wiki/tests/phpunit/includes/libs/GenericArrayObjectTest.php b/www/wiki/tests/phpunit/includes/libs/GenericArrayObjectTest.php
index 12c57871..3be2b064 100644
--- a/www/wiki/tests/phpunit/includes/libs/GenericArrayObjectTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/GenericArrayObjectTest.php
@@ -26,7 +26,9 @@
*
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
-abstract class GenericArrayObjectTest extends PHPUnit_Framework_TestCase {
+abstract class GenericArrayObjectTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
/**
* Returns objects that can serve as elements in the concrete
@@ -171,8 +173,6 @@ abstract class GenericArrayObjectTest extends PHPUnit_Framework_TestCase {
* @since 1.20
*
* @param callable $function
- *
- * @covers GenericArrayObject::getObjectType
*/
protected function checkTypeChecks( $function ) {
$excption = null;
@@ -204,7 +204,7 @@ abstract class GenericArrayObjectTest extends PHPUnit_Framework_TestCase {
* @since 1.20
*
* @param array $elements
- *
+ * @covers GenericArrayObject::getObjectType
* @covers GenericArrayObject::offsetSet
*/
public function testOffsetSet( array $elements ) {
diff --git a/www/wiki/tests/phpunit/includes/libs/HashRingTest.php b/www/wiki/tests/phpunit/includes/libs/HashRingTest.php
index 0822a8ac..ba288281 100644
--- a/www/wiki/tests/phpunit/includes/libs/HashRingTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/HashRingTest.php
@@ -3,7 +3,10 @@
/**
* @group HashRing
*/
-class HashRingTest extends PHPUnit_Framework_TestCase {
+class HashRingTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
/**
* @covers HashRing
*/
diff --git a/www/wiki/tests/phpunit/includes/libs/HtmlArmorTest.php b/www/wiki/tests/phpunit/includes/libs/HtmlArmorTest.php
index 5f176e0c..c5e87e4e 100644
--- a/www/wiki/tests/phpunit/includes/libs/HtmlArmorTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/HtmlArmorTest.php
@@ -3,9 +3,26 @@
/**
* @covers HtmlArmor
*/
-class HtmlArmorTest extends PHPUnit_Framework_TestCase {
+class HtmlArmorTest extends PHPUnit\Framework\TestCase {
- public static function provideHtmlArmor() {
+ use MediaWikiCoversValidator;
+
+ public static function provideConstructor() {
+ return [
+ [ 'test' ],
+ [ null ],
+ [ '<em>some html!</em>' ]
+ ];
+ }
+
+ /**
+ * @dataProvider provideConstructor
+ */
+ public function testConstructor( $value ) {
+ $this->assertInstanceOf( HtmlArmor::class, new HtmlArmor( $value ) );
+ }
+
+ public static function provideGetHtml() {
return [
[
'foobar',
@@ -19,13 +36,17 @@ class HtmlArmorTest extends PHPUnit_Framework_TestCase {
new HtmlArmor( '<script>alert("evil!");</script>' ),
'<script>alert("evil!");</script>',
],
+ [
+ new HtmlArmor( null ),
+ null,
+ ]
];
}
/**
- * @dataProvider provideHtmlArmor
+ * @dataProvider provideGetHtml
*/
- public function testHtmlArmor( $input, $expected ) {
+ public function testGetHtml( $input, $expected ) {
$this->assertEquals(
$expected,
HtmlArmor::getHtml( $input )
diff --git a/www/wiki/tests/phpunit/includes/libs/IEUrlExtensionTest.php b/www/wiki/tests/phpunit/includes/libs/IEUrlExtensionTest.php
index 57668e50..03c7b0c0 100644
--- a/www/wiki/tests/phpunit/includes/libs/IEUrlExtensionTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/IEUrlExtensionTest.php
@@ -5,7 +5,10 @@
* @todo tests below for findIE6Extension should be split into...
* ...a dataprovider and test method.
*/
-class IEUrlExtensionTest extends PHPUnit_Framework_TestCase {
+class IEUrlExtensionTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
/**
* @covers IEUrlExtension::findIE6Extension
*/
diff --git a/www/wiki/tests/phpunit/includes/libs/IPTest.php b/www/wiki/tests/phpunit/includes/libs/IPTest.php
index e47c4ba1..9702c82c 100644
--- a/www/wiki/tests/phpunit/includes/libs/IPTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/IPTest.php
@@ -8,13 +8,15 @@
* @todo Test methods in this call should be split into a method and a
* dataprovider.
*/
+class IPTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
-class IPTest extends PHPUnit_Framework_TestCase {
/**
* @covers IP::isIPAddress
* @dataProvider provideInvalidIPs
*/
- public function isNotIPAddress( $val, $desc ) {
+ public function testIsNotIPAddress( $val, $desc ) {
$this->assertFalse( IP::isIPAddress( $val ), $desc );
}
@@ -561,7 +563,7 @@ class IPTest extends PHPUnit_Framework_TestCase {
}
/**
- * Test for IP::splitHostAndPort().
+ * @covers IP::splitHostAndPort()
* @dataProvider provideSplitHostAndPort
*/
public function testSplitHostAndPort( $expected, $input, $description ) {
@@ -588,7 +590,7 @@ class IPTest extends PHPUnit_Framework_TestCase {
}
/**
- * Test for IP::combineHostAndPort()
+ * @covers IP::combineHostAndPort()
* @dataProvider provideCombineHostAndPort
*/
public function testCombineHostAndPort( $expected, $input, $description ) {
@@ -612,7 +614,7 @@ class IPTest extends PHPUnit_Framework_TestCase {
}
/**
- * Test for IP::sanitizeRange()
+ * @covers IP::sanitizeRange()
* @dataProvider provideIPCIDRs
*/
public function testSanitizeRange( $input, $expected, $description ) {
@@ -636,7 +638,7 @@ class IPTest extends PHPUnit_Framework_TestCase {
}
/**
- * Test for IP::prettifyIP()
+ * @covers IP::prettifyIP()
* @dataProvider provideIPsToPrettify
*/
public function testPrettifyIP( $ip, $prettified ) {
diff --git a/www/wiki/tests/phpunit/includes/libs/JavaScriptMinifierTest.php b/www/wiki/tests/phpunit/includes/libs/JavaScriptMinifierTest.php
index ca12f6c7..61056784 100644
--- a/www/wiki/tests/phpunit/includes/libs/JavaScriptMinifierTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/JavaScriptMinifierTest.php
@@ -1,6 +1,8 @@
<?php
-class JavaScriptMinifierTest extends PHPUnit_Framework_TestCase {
+class JavaScriptMinifierTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
public static function provideCases() {
return [
@@ -59,6 +61,20 @@ class JavaScriptMinifierTest extends PHPUnit_Framework_TestCase {
[ "0xFF.\nx;", "0xFF.x;" ],
[ "5.3.\nx;", "5.3.x;" ],
+ // Cover failure case for incomplete hex literal
+ [ "0x;", false, false ],
+
+ // Cover failure case for number with no digits after E
+ [ "1.4E", false, false ],
+
+ // Cover failure case for number with several E
+ [ "1.4EE2", false, false ],
+ [ "1.4EE", false, false ],
+
+ // Cover failure case for number with several E (nonconsecutive)
+ // FIXME: This is invalid, but currently tolerated
+ [ "1.4E2E3", "1.4E2 E3", false ],
+
// Semicolon insertion between an expression having an inline
// comment after it, and a statement on the next line (T29046).
[
@@ -66,6 +82,18 @@ class JavaScriptMinifierTest extends PHPUnit_Framework_TestCase {
"var a=this\nfor(b=0;c<d;b++){}"
],
+ // Cover failure case of incomplete regexp at end of file (T75556)
+ // FIXME: This is invalid, but currently tolerated
+ [ "*/", "*/", false ],
+
+ // Cover failure case of incomplete char class in regexp (T75556)
+ // FIXME: This is invalid, but currently tolerated
+ [ "/a[b/.test", "/a[b/.test", false ],
+
+ // Cover failure case of incomplete string at end of file (T75556)
+ // FIXME: This is invalid, but currently tolerated
+ [ "'a", "'a", false ],
+
// Token separation
[ "x in y", "x in y" ],
[ "/x/g in y", "/x/g in y" ],
@@ -138,6 +166,7 @@ class JavaScriptMinifierTest extends PHPUnit_Framework_TestCase {
[ "var a = 5.;", "var a=5.;" ],
[ "5.0.toString();", "5.0.toString();" ],
[ "5..toString();", "5..toString();" ],
+ // Cover failure case for too many decimal points
[ "5...toString();", false ],
[ "5.\n.toString();", '5..toString();' ],
@@ -153,16 +182,17 @@ class JavaScriptMinifierTest extends PHPUnit_Framework_TestCase {
/**
* @dataProvider provideCases
* @covers JavaScriptMinifier::minify
+ * @covers JavaScriptMinifier::parseError
*/
- public function testJavaScriptMinifierOutput( $code, $expectedOutput, $expectedValid = true ) {
+ public function testMinifyOutput( $code, $expectedOutput, $expectedValid = true ) {
$minified = JavaScriptMinifier::minify( $code );
// JSMin+'s parser will throw an exception if output is not valid JS.
// suppression of warnings needed for stupid crap
if ( $expectedValid ) {
- MediaWiki\suppressWarnings();
+ Wikimedia\suppressWarnings();
$parser = new JSParser();
- MediaWiki\restoreWarnings();
+ Wikimedia\restoreWarnings();
$parser->parse( $minified, 'minify-test.js', 1 );
}
diff --git a/www/wiki/tests/phpunit/includes/libs/MWMessagePackTest.php b/www/wiki/tests/phpunit/includes/libs/MWMessagePackTest.php
index 88dc67aa..695a7341 100644
--- a/www/wiki/tests/phpunit/includes/libs/MWMessagePackTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/MWMessagePackTest.php
@@ -3,7 +3,9 @@
* PHP Unit tests for MWMessagePack
* @covers MWMessagePack
*/
-class MWMessagePackTest extends PHPUnit_Framework_TestCase {
+class MWMessagePackTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
/**
* Provides test cases for MWMessagePackTest::testMessagePack
diff --git a/www/wiki/tests/phpunit/includes/libs/MapCacheLRUTest.php b/www/wiki/tests/phpunit/includes/libs/MapCacheLRUTest.php
new file mode 100644
index 00000000..2a962b79
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/libs/MapCacheLRUTest.php
@@ -0,0 +1,117 @@
+<?php
+/**
+ * @group Cache
+ */
+class MapCacheLRUTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
+ /**
+ * @covers MapCacheLRU::newFromArray()
+ * @covers MapCacheLRU::toArray()
+ * @covers MapCacheLRU::getAllKeys()
+ * @covers MapCacheLRU::clear()
+ */
+ function testArrayConversion() {
+ $raw = [ 'd' => 4, 'c' => 3, 'b' => 2, 'a' => 1 ];
+ $cache = MapCacheLRU::newFromArray( $raw, 3 );
+
+ $this->assertSame( true, $cache->has( 'a' ) );
+ $this->assertSame( true, $cache->has( 'b' ) );
+ $this->assertSame( true, $cache->has( 'c' ) );
+ $this->assertSame( 1, $cache->get( 'a' ) );
+ $this->assertSame( 2, $cache->get( 'b' ) );
+ $this->assertSame( 3, $cache->get( 'c' ) );
+
+ $this->assertSame(
+ [ 'a' => 1, 'b' => 2, 'c' => 3 ],
+ $cache->toArray()
+ );
+ $this->assertSame(
+ [ 'a', 'b', 'c' ],
+ $cache->getAllKeys()
+ );
+
+ $cache->clear( 'a' );
+ $this->assertSame(
+ [ 'b' => 2, 'c' => 3 ],
+ $cache->toArray()
+ );
+
+ $cache->clear();
+ $this->assertSame(
+ [],
+ $cache->toArray()
+ );
+ }
+
+ /**
+ * @covers MapCacheLRU::has()
+ * @covers MapCacheLRU::get()
+ * @covers MapCacheLRU::set()
+ */
+ function testLRU() {
+ $raw = [ 'a' => 1, 'b' => 2, 'c' => 3 ];
+ $cache = MapCacheLRU::newFromArray( $raw, 3 );
+
+ $this->assertSame( true, $cache->has( 'c' ) );
+ $this->assertSame(
+ [ 'a' => 1, 'b' => 2, 'c' => 3 ],
+ $cache->toArray()
+ );
+
+ $this->assertSame( 3, $cache->get( 'c' ) );
+ $this->assertSame(
+ [ 'a' => 1, 'b' => 2, 'c' => 3 ],
+ $cache->toArray()
+ );
+
+ $this->assertSame( 1, $cache->get( 'a' ) );
+ $this->assertSame(
+ [ 'b' => 2, 'c' => 3, 'a' => 1 ],
+ $cache->toArray()
+ );
+
+ $cache->set( 'a', 1 );
+ $this->assertSame(
+ [ 'b' => 2, 'c' => 3, 'a' => 1 ],
+ $cache->toArray()
+ );
+
+ $cache->set( 'b', 22 );
+ $this->assertSame(
+ [ 'c' => 3, 'a' => 1, 'b' => 22 ],
+ $cache->toArray()
+ );
+
+ $cache->set( 'd', 4 );
+ $this->assertSame(
+ [ 'a' => 1, 'b' => 22, 'd' => 4 ],
+ $cache->toArray()
+ );
+
+ $cache->set( 'e', 5, 0.33 );
+ $this->assertSame(
+ [ 'e' => 5, 'b' => 22, 'd' => 4 ],
+ $cache->toArray()
+ );
+
+ $cache->set( 'f', 6, 0.66 );
+ $this->assertSame(
+ [ 'b' => 22, 'f' => 6, 'd' => 4 ],
+ $cache->toArray()
+ );
+
+ $cache->set( 'g', 7, 0.90 );
+ $this->assertSame(
+ [ 'f' => 6, 'g' => 7, 'd' => 4 ],
+ $cache->toArray()
+ );
+
+ $cache->set( 'g', 7, 1.0 );
+ $this->assertSame(
+ [ 'f' => 6, 'd' => 4, 'g' => 7 ],
+ $cache->toArray()
+ );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/libs/MemoizedCallableTest.php b/www/wiki/tests/phpunit/includes/libs/MemoizedCallableTest.php
index d99c5878..9127a30f 100644
--- a/www/wiki/tests/phpunit/includes/libs/MemoizedCallableTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/MemoizedCallableTest.php
@@ -24,14 +24,16 @@ class ArrayBackedMemoizedCallable extends MemoizedCallable {
* PHP Unit tests for MemoizedCallable class.
* @covers MemoizedCallable
*/
-class MemoizedCallableTest extends PHPUnit_Framework_TestCase {
+class MemoizedCallableTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
/**
* The memoized callable should relate inputs to outputs in the same
* way as the original underlying callable.
*/
public function testReturnValuePassedThrough() {
- $mock = $this->getMockBuilder( 'stdClass' )
+ $mock = $this->getMockBuilder( stdClass::class )
->setMethods( [ 'reverse' ] )->getMock();
$mock->expects( $this->any() )
->method( 'reverse' )
@@ -45,10 +47,10 @@ class MemoizedCallableTest extends PHPUnit_Framework_TestCase {
* Consecutive calls to the memoized callable with the same arguments
* should result in just one invocation of the underlying callable.
*
- * @requires function apc_store/apcu_store
+ * @requires extension apcu
*/
public function testCallableMemoized() {
- $observer = $this->getMockBuilder( 'stdClass' )
+ $observer = $this->getMockBuilder( stdClass::class )
->setMethods( [ 'computeSomething' ] )->getMock();
$observer->expects( $this->once() )
->method( 'computeSomething' )
diff --git a/www/wiki/tests/phpunit/includes/libs/ObjectFactoryTest.php b/www/wiki/tests/phpunit/includes/libs/ObjectFactoryTest.php
deleted file mode 100644
index 35a7b602..00000000
--- a/www/wiki/tests/phpunit/includes/libs/ObjectFactoryTest.php
+++ /dev/null
@@ -1,180 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-class ObjectFactoryTest extends PHPUnit_Framework_TestCase {
-
- /**
- * @covers ObjectFactory::getObjectFromSpec
- */
- public function testClosureExpansionDisabled() {
- $obj = ObjectFactory::getObjectFromSpec( [
- 'class' => 'ObjectFactoryTestFixture',
- 'args' => [
- function () {
- return 'wrapped';
- },
- 'unwrapped',
- ],
- 'calls' => [
- 'setter' => [ function () {
- return 'wrapped';
- }, ],
- ],
- 'closure_expansion' => false,
- ] );
- $this->assertInstanceOf( 'Closure', $obj->args[0] );
- $this->assertSame( 'wrapped', $obj->args[0]() );
- $this->assertSame( 'unwrapped', $obj->args[1] );
- $this->assertInstanceOf( 'Closure', $obj->setterArgs[0] );
- $this->assertSame( 'wrapped', $obj->setterArgs[0]() );
- }
-
- /**
- * @covers ObjectFactory::getObjectFromSpec
- * @covers ObjectFactory::expandClosures
- */
- public function testClosureExpansionEnabled() {
- $obj = ObjectFactory::getObjectFromSpec( [
- 'class' => 'ObjectFactoryTestFixture',
- 'args' => [
- function () {
- return 'wrapped';
- },
- 'unwrapped',
- ],
- 'calls' => [
- 'setter' => [ function () {
- return 'wrapped';
- }, ],
- ],
- 'closure_expansion' => true,
- ] );
- $this->assertInternalType( 'string', $obj->args[0] );
- $this->assertSame( 'wrapped', $obj->args[0] );
- $this->assertSame( 'unwrapped', $obj->args[1] );
- $this->assertInternalType( 'string', $obj->setterArgs[0] );
- $this->assertSame( 'wrapped', $obj->setterArgs[0] );
-
- $obj = ObjectFactory::getObjectFromSpec( [
- 'class' => 'ObjectFactoryTestFixture',
- 'args' => [ function () {
- return 'unwrapped';
- }, ],
- 'calls' => [
- 'setter' => [ function () {
- return 'unwrapped';
- }, ],
- ],
- ] );
- $this->assertInternalType( 'string', $obj->args[0] );
- $this->assertSame( 'unwrapped', $obj->args[0] );
- $this->assertInternalType( 'string', $obj->setterArgs[0] );
- $this->assertSame( 'unwrapped', $obj->setterArgs[0] );
- }
-
- /**
- * @covers ObjectFactory::getObjectFromSpec
- */
- public function testGetObjectFromFactory() {
- $args = [ 'a', 'b' ];
- $obj = ObjectFactory::getObjectFromSpec( [
- 'factory' => function ( $a, $b ) {
- return new ObjectFactoryTestFixture( $a, $b );
- },
- 'args' => $args,
- ] );
- $this->assertSame( $args, $obj->args );
- }
-
- /**
- * @covers ObjectFactory::getObjectFromSpec
- * @expectedException InvalidArgumentException
- */
- public function testGetObjectFromInvalid() {
- $args = [ 'a', 'b' ];
- $obj = ObjectFactory::getObjectFromSpec( [
- // Missing 'class' or 'factory'
- 'args' => $args,
- ] );
- }
-
- /**
- * @covers ObjectFactory::getObjectFromSpec
- * @dataProvider provideConstructClassInstance
- */
- public function testGetObjectFromClass( $args ) {
- $obj = ObjectFactory::getObjectFromSpec( [
- 'class' => 'ObjectFactoryTestFixture',
- 'args' => $args,
- ] );
- $this->assertSame( $args, $obj->args );
- }
-
- /**
- * @covers ObjectFactory::constructClassInstance
- * @dataProvider provideConstructClassInstance
- */
- public function testConstructClassInstance( $args ) {
- $obj = ObjectFactory::constructClassInstance(
- 'ObjectFactoryTestFixture', $args
- );
- $this->assertSame( $args, $obj->args );
- }
-
- public static function provideConstructClassInstance() {
- // These args go to 11. I thought about making 10 one louder, but 11!
- return [
- '0 args' => [ [] ],
- '1 args' => [ [ 1, ] ],
- '2 args' => [ [ 1, 2, ] ],
- '3 args' => [ [ 1, 2, 3, ] ],
- '4 args' => [ [ 1, 2, 3, 4, ] ],
- '5 args' => [ [ 1, 2, 3, 4, 5, ] ],
- '6 args' => [ [ 1, 2, 3, 4, 5, 6, ] ],
- '7 args' => [ [ 1, 2, 3, 4, 5, 6, 7, ] ],
- '8 args' => [ [ 1, 2, 3, 4, 5, 6, 7, 8, ] ],
- '9 args' => [ [ 1, 2, 3, 4, 5, 6, 7, 8, 9, ] ],
- '10 args' => [ [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ] ],
- '11 args' => [ [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ] ],
- ];
- }
-
- /**
- * @covers ObjectFactory::constructClassInstance
- * @expectedException InvalidArgumentException
- */
- public function testNamedArgs() {
- $args = [ 'foo' => 1, 'bar' => 2, 'baz' => 3 ];
- $obj = ObjectFactory::constructClassInstance(
- 'ObjectFactoryTestFixture', $args
- );
- }
-}
-
-class ObjectFactoryTestFixture {
- public $args;
- public $setterArgs;
- public function __construct( /*...*/ ) {
- $this->args = func_get_args();
- }
- public function setter( /*...*/ ) {
- $this->setterArgs = func_get_args();
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/libs/ProcessCacheLRUTest.php b/www/wiki/tests/phpunit/includes/libs/ProcessCacheLRUTest.php
index 9c189d12..c8940e5f 100644
--- a/www/wiki/tests/phpunit/includes/libs/ProcessCacheLRUTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/ProcessCacheLRUTest.php
@@ -9,7 +9,9 @@
*
* @group Cache
*/
-class ProcessCacheLRUTest extends PHPUnit_Framework_TestCase {
+class ProcessCacheLRUTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
/**
* Helper to verify emptiness of a cache object.
@@ -58,6 +60,7 @@ class ProcessCacheLRUTest extends PHPUnit_Framework_TestCase {
/**
* Highlight diff between assertEquals and assertNotSame
+ * @coversNothing
*/
public function testPhpUnitArrayEquality() {
$one = [ 'A' => 1, 'B' => 2 ];
diff --git a/www/wiki/tests/phpunit/includes/libs/SamplingStatsdClientTest.php b/www/wiki/tests/phpunit/includes/libs/SamplingStatsdClientTest.php
index c5bc03ea..7bd16115 100644
--- a/www/wiki/tests/phpunit/includes/libs/SamplingStatsdClientTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/SamplingStatsdClientTest.php
@@ -3,7 +3,13 @@
use Liuggio\StatsdClient\Entity\StatsdData;
use Liuggio\StatsdClient\Sender\SenderInterface;
-class SamplingStatsdClientTest extends PHPUnit_Framework_TestCase {
+/**
+ * @covers SamplingStatsdClient
+ */
+class SamplingStatsdClientTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
/**
* @dataProvider samplingDataProvider
*/
@@ -16,7 +22,11 @@ class SamplingStatsdClientTest extends PHPUnit_Framework_TestCase {
} else {
$sender->expects( $this->never() )->method( 'write' );
}
- mt_srand( $seed );
+ if ( defined( 'MT_RAND_PHP' ) ) {
+ mt_srand( $seed, MT_RAND_PHP );
+ } else {
+ mt_srand( $seed );
+ }
$client = new SamplingStatsdClient( $sender );
$client->send( $data, $sampleRate );
}
diff --git a/www/wiki/tests/phpunit/includes/libs/StringUtilsTest.php b/www/wiki/tests/phpunit/includes/libs/StringUtilsTest.php
index 8075944e..fcfa53e2 100644
--- a/www/wiki/tests/phpunit/includes/libs/StringUtilsTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/StringUtilsTest.php
@@ -1,6 +1,8 @@
<?php
-class StringUtilsTest extends PHPUnit_Framework_TestCase {
+class StringUtilsTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
/**
* @covers StringUtils::isUtf8
diff --git a/www/wiki/tests/phpunit/includes/libs/TimingTest.php b/www/wiki/tests/phpunit/includes/libs/TimingTest.php
index 4d719440..581a5186 100644
--- a/www/wiki/tests/phpunit/includes/libs/TimingTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/TimingTest.php
@@ -19,7 +19,9 @@
* @author Ori Livneh <ori@wikimedia.org>
*/
-class TimingTest extends PHPUnit_Framework_TestCase {
+class TimingTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
/**
* @covers Timing::clearMarks
diff --git a/www/wiki/tests/phpunit/includes/libs/XhprofDataTest.php b/www/wiki/tests/phpunit/includes/libs/XhprofDataTest.php
index 35e90052..1cbd86f1 100644
--- a/www/wiki/tests/phpunit/includes/libs/XhprofDataTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/XhprofDataTest.php
@@ -24,7 +24,9 @@
* @copyright © 2014 Wikimedia Foundation and contributors
* @since 1.25
*/
-class XhprofDataTest extends PHPUnit_Framework_TestCase {
+class XhprofDataTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
/**
* @covers XhprofData::splitKey
diff --git a/www/wiki/tests/phpunit/includes/libs/XhprofTest.php b/www/wiki/tests/phpunit/includes/libs/XhprofTest.php
index 67481154..0ea13289 100644
--- a/www/wiki/tests/phpunit/includes/libs/XhprofTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/XhprofTest.php
@@ -18,7 +18,10 @@
* @file
*/
-class XhprofTest extends PHPUnit_Framework_TestCase {
+class XhprofTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
/**
* Trying to enable Xhprof when it is already enabled causes an exception
* to be thrown.
@@ -28,7 +31,7 @@ class XhprofTest extends PHPUnit_Framework_TestCase {
* @covers Xhprof::enable
*/
public function testEnable() {
- $xhprof = new ReflectionClass( 'Xhprof' );
+ $xhprof = new ReflectionClass( Xhprof::class );
$enabled = $xhprof->getProperty( 'enabled' );
$enabled->setAccessible( true );
$enabled->setValue( true );
diff --git a/www/wiki/tests/phpunit/includes/libs/XmlTypeCheckTest.php b/www/wiki/tests/phpunit/includes/libs/XmlTypeCheckTest.php
index 5c5eeaa7..8616b419 100644
--- a/www/wiki/tests/phpunit/includes/libs/XmlTypeCheckTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/XmlTypeCheckTest.php
@@ -5,12 +5,14 @@
* @group Xml
* @covers XMLTypeCheck
*/
-class XmlTypeCheckTest extends PHPUnit_Framework_TestCase {
+class XmlTypeCheckTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
const WELL_FORMED_XML = "<root><child /></root>";
const MAL_FORMED_XML = "<root><child /></error>";
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:ignore Generic.Files.LineLength
const XML_WITH_PIH = '<?xml version="1.0"?><?xml-stylesheet type="text/xsl" href="/w/index.php"?><svg><child /></svg>';
- // @codingStandardsIgnoreEnd
/**
* @covers XMLTypeCheck::newFromString
diff --git a/www/wiki/tests/phpunit/includes/libs/composer/ComposerInstalledTest.php b/www/wiki/tests/phpunit/includes/libs/composer/ComposerInstalledTest.php
new file mode 100644
index 00000000..05ae2a37
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/libs/composer/ComposerInstalledTest.php
@@ -0,0 +1,499 @@
+<?php
+
+class ComposerInstalledTest extends MediaWikiTestCase {
+
+ private $installed;
+
+ public function setUp() {
+ parent::setUp();
+ global $IP;
+ $this->installed = "$IP/tests/phpunit/data/composer/installed.json";
+ }
+
+ /**
+ * @covers ComposerInstalled::__construct
+ * @covers ComposerInstalled::getInstalledDependencies
+ */
+ public function testGetInstalledDependencies() {
+ $installed = new ComposerInstalled( $this->installed );
+ $this->assertArrayEquals( [
+ 'leafo/lessphp' => [
+ 'version' => '0.5.0',
+ 'type' => 'library',
+ 'licenses' => [ 'MIT', 'GPL-3.0-only' ],
+ 'authors' => [
+ [
+ 'name' => 'Leaf Corcoran',
+ 'email' => 'leafot@gmail.com',
+ 'homepage' => 'http://leafo.net',
+ ],
+ ],
+ 'description' => 'lessphp is a compiler for LESS written in PHP.',
+ ],
+ 'psr/log' => [
+ 'version' => '1.0.0',
+ 'type' => 'library',
+ 'licenses' => [ 'MIT' ],
+ 'authors' => [
+ [
+ 'name' => 'PHP-FIG',
+ 'homepage' => 'http://www.php-fig.org/',
+ ],
+ ],
+ 'description' => 'Common interface for logging libraries',
+ ],
+ 'cssjanus/cssjanus' => [
+ 'version' => '1.1.1',
+ 'type' => 'library',
+ 'licenses' => [ 'Apache-2.0' ],
+ 'authors' => [
+ ],
+ 'description' => 'Convert CSS stylesheets between left-to-right ' .
+ 'and right-to-left.',
+ ],
+ 'cdb/cdb' => [
+ 'version' => '1.0.0',
+ 'type' => 'library',
+ 'licenses' => [ 'GPLv2' ],
+ 'authors' => [
+ [
+ 'name' => 'Tim Starling',
+ 'email' => 'tstarling@wikimedia.org',
+ ],
+ [
+ 'name' => 'Chad Horohoe',
+ 'email' => 'chad@wikimedia.org',
+ ],
+ ],
+ 'description' => 'Constant Database (CDB) wrapper library for PHP. ' .
+ 'Provides pure-PHP fallback when dba_* functions are absent.',
+ ],
+ 'sebastian/version' => [
+ 'version' => '2.0.1',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ 'role' => 'lead',
+ ],
+ ],
+ 'description' => 'Library that helps with managing the version ' .
+ 'number of Git-hosted PHP projects',
+ ],
+ 'sebastian/resource-operations' => [
+ 'version' => '1.0.0',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ ],
+ ],
+ 'description' => 'Provides a list of PHP built-in functions that ' .
+ 'operate on resources',
+ ],
+ 'sebastian/recursion-context' => [
+ 'version' => '3.0.0',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Jeff Welch',
+ 'email' => 'whatthejeff@gmail.com',
+ ],
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ ],
+ [
+ 'name' => 'Adam Harvey',
+ 'email' => 'aharvey@php.net',
+ ],
+ ],
+ 'description' => 'Provides functionality to recursively process PHP ' .
+ 'variables',
+ ],
+ 'sebastian/object-reflector' => [
+ 'version' => '1.1.1',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ ],
+ ],
+ 'description' => 'Allows reflection of object attributes, including ' .
+ 'inherited and non-public ones',
+ ],
+ 'sebastian/object-enumerator' => [
+ 'version' => '3.0.3',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ ],
+ ],
+ 'description' => 'Traverses array structures and object graphs ' .
+ 'to enumerate all referenced objects',
+ ],
+ 'sebastian/global-state' => [
+ 'version' => '2.0.0',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ ],
+ ],
+ 'description' => 'Snapshotting of global state',
+ ],
+ 'sebastian/exporter' => [
+ 'version' => '3.1.0',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Jeff Welch',
+ 'email' => 'whatthejeff@gmail.com',
+ ],
+ [
+ 'name' => 'Volker Dusch',
+ 'email' => 'github@wallbash.com',
+ ],
+ [
+ 'name' => 'Bernhard Schussek',
+ 'email' => 'bschussek@2bepublished.at',
+ ],
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ ],
+ [
+ 'name' => 'Adam Harvey',
+ 'email' => 'aharvey@php.net',
+ ],
+ ],
+ 'description' => 'Provides the functionality to export PHP ' .
+ 'variables for visualization',
+ ],
+ 'sebastian/environment' => [
+ 'version' => '3.1.0',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ ],
+ ],
+ 'description' => 'Provides functionality to handle HHVM/PHP ' .
+ 'environments',
+ ],
+ 'sebastian/diff' => [
+ 'version' => '2.0.1',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Kore Nordmann',
+ 'email' => 'mail@kore-nordmann.de',
+ ],
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ ],
+ ],
+ 'description' => 'Diff implementation',
+ ],
+ 'sebastian/comparator' => [
+ 'version' => '2.1.1',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Jeff Welch',
+ 'email' => 'whatthejeff@gmail.com',
+ ],
+ [
+ 'name' => 'Volker Dusch',
+ 'email' => 'github@wallbash.com',
+ ],
+ [
+ 'name' => 'Bernhard Schussek',
+ 'email' => 'bschussek@2bepublished.at',
+ ],
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ ],
+ ],
+ 'description' => 'Provides the functionality to compare PHP ' .
+ 'values for equality',
+ ],
+ 'doctrine/instantiator' => [
+ 'version' => '1.1.0',
+ 'type' => 'library',
+ 'licenses' => [ 'MIT' ],
+ 'authors' => [
+ [
+ 'name' => 'Marco Pivetta',
+ 'email' => 'ocramius@gmail.com',
+ 'homepage' => 'http://ocramius.github.com/',
+ ],
+ ],
+ 'description' => 'A small, lightweight utility to instantiate ' .
+ 'objects in PHP without invoking their constructors',
+ ],
+ 'phpunit/php-text-template' => [
+ 'version' => '1.2.1',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ 'role' => 'lead',
+ ],
+ ],
+ 'description' => 'Simple template engine.',
+ ],
+ 'phpunit/phpunit-mock-objects' => [
+ 'version' => '5.0.6',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ 'role' => 'lead',
+ ],
+ ],
+ 'description' => 'Mock Object library for PHPUnit',
+ ],
+ 'phpunit/php-timer' => [
+ 'version' => '1.0.9',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sb@sebastian-bergmann.de',
+ 'role' => 'lead',
+ ],
+ ],
+ 'description' => 'Utility class for timing',
+ ],
+ 'phpunit/php-file-iterator' => [
+ 'version' => '1.4.5',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sb@sebastian-bergmann.de',
+ 'role' => 'lead',
+ ],
+ ],
+ 'description' => 'FilterIterator implementation that filters ' .
+ 'files based on a list of suffixes.',
+ ],
+ 'theseer/tokenizer' => [
+ 'version' => '1.1.0',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Arne Blankerts',
+ 'email' => 'arne@blankerts.de',
+ 'role' => 'Developer',
+ ],
+ ],
+ 'description' => 'A small library for converting tokenized PHP ' .
+ 'source code into XML and potentially other formats',
+ ],
+ 'sebastian/code-unit-reverse-lookup' => [
+ 'version' => '1.0.1',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ ],
+ ],
+ 'description' => 'Looks up which function or method a line of ' .
+ 'code belongs to',
+ ],
+ 'phpunit/php-token-stream' => [
+ 'version' => '2.0.2',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ ],
+ ],
+ 'description' => 'Wrapper around PHP\'s tokenizer extension.',
+ ],
+ 'phpunit/php-code-coverage' => [
+ 'version' => '5.3.0',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ 'role' => 'lead',
+ ],
+ ],
+ 'description' => 'Library that provides collection, processing, ' .
+ 'and rendering functionality for PHP code coverage information.',
+ ],
+ 'webmozart/assert' => [
+ 'version' => '1.2.0',
+ 'type' => 'library',
+ 'licenses' => [ 'MIT' ],
+ 'authors' => [
+ [
+ 'name' => 'Bernhard Schussek',
+ 'email' => 'bschussek@gmail.com',
+ ],
+ ],
+ 'description' => 'Assertions to validate method input/output with ' .
+ 'nice error messages.',
+ ],
+ 'phpdocumentor/reflection-common' => [
+ 'version' => '1.0.1',
+ 'type' => 'library',
+ 'licenses' => [ 'MIT' ],
+ 'authors' => [
+ [
+ 'name' => 'Jaap van Otterdijk',
+ 'email' => 'opensource@ijaap.nl',
+ ],
+ ],
+ 'description' => 'Common reflection classes used by phpdocumentor to ' .
+ 'reflect the code structure',
+ ],
+ 'phpdocumentor/type-resolver' => [
+ 'version' => '0.4.0',
+ 'type' => 'library',
+ 'licenses' => [ 'MIT' ],
+ 'authors' => [
+ [
+ 'name' => 'Mike van Riel',
+ 'email' => 'me@mikevanriel.com',
+ ],
+ ],
+ 'description' => '',
+ ],
+ 'phpdocumentor/reflection-docblock' => [
+ 'version' => '4.2.0',
+ 'type' => 'library',
+ 'licenses' => [ 'MIT' ],
+ 'authors' => [
+ [
+ 'name' => 'Mike van Riel',
+ 'email' => 'me@mikevanriel.com',
+ ],
+ ],
+ 'description' => 'With this component, a library can provide support for ' .
+ 'annotations via DocBlocks or otherwise retrieve information that ' .
+ 'is embedded in a DocBlock.',
+ ],
+ 'phpspec/prophecy' => [
+ 'version' => '1.7.3',
+ 'type' => 'library',
+ 'licenses' => [ 'MIT' ],
+ 'authors' => [
+ [
+ 'name' => 'Konstantin Kudryashov',
+ 'email' => 'ever.zet@gmail.com',
+ 'homepage' => 'http://everzet.com',
+ ],
+ [
+ 'name' => 'Marcello Duarte',
+ 'email' => 'marcello.duarte@gmail.com',
+ ],
+ ],
+ 'description' => 'Highly opinionated mocking framework for PHP 5.3+',
+ ],
+ 'phar-io/version' => [
+ 'version' => '1.0.1',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Arne Blankerts',
+ 'email' => 'arne@blankerts.de',
+ 'role' => 'Developer',
+ ],
+ [
+ 'name' => 'Sebastian Heuer',
+ 'email' => 'sebastian@phpeople.de',
+ 'role' => 'Developer',
+ ],
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ 'role' => 'Developer',
+ ],
+ ],
+ 'description' => 'Library for handling version information and constraints',
+ ],
+ 'phar-io/manifest' => [
+ 'version' => '1.0.1',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Arne Blankerts',
+ 'email' => 'arne@blankerts.de',
+ 'role' => 'Developer',
+ ],
+ [
+ 'name' => 'Sebastian Heuer',
+ 'email' => 'sebastian@phpeople.de',
+ 'role' => 'Developer',
+ ],
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ 'role' => 'Developer',
+ ],
+ ],
+ 'description' => 'Component for reading phar.io manifest ' .
+ 'information from a PHP Archive (PHAR)',
+ ],
+ 'myclabs/deep-copy' => [
+ 'version' => '1.7.0',
+ 'type' => 'library',
+ 'licenses' => [ 'MIT' ],
+ 'authors' => [
+ ],
+ 'description' => 'Create deep copies (clones) of your objects',
+ ],
+ 'phpunit/phpunit' => [
+ 'version' => '6.5.5',
+ 'type' => 'library',
+ 'licenses' => [ 'BSD-3-Clause' ],
+ 'authors' => [
+ [
+ 'name' => 'Sebastian Bergmann',
+ 'email' => 'sebastian@phpunit.de',
+ 'role' => 'lead',
+ ],
+ ],
+ 'description' => 'The PHP Unit Testing framework.',
+ ],
+ ], $installed->getInstalledDependencies(), false, true );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/libs/composer/ComposerLockTest.php b/www/wiki/tests/phpunit/includes/libs/composer/ComposerLockTest.php
index eef7e274..dc81e1d3 100644
--- a/www/wiki/tests/phpunit/includes/libs/composer/ComposerLockTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/composer/ComposerLockTest.php
@@ -20,7 +20,7 @@ class ComposerLockTest extends MediaWikiTestCase {
'wikimedia/cdb' => [
'version' => '1.0.1',
'type' => 'library',
- 'licenses' => [ 'GPL-2.0' ],
+ 'licenses' => [ 'GPL-2.0-only' ],
'authors' => [
[
'name' => 'Tim Starling',
@@ -44,7 +44,7 @@ class ComposerLockTest extends MediaWikiTestCase {
'leafo/lessphp' => [
'version' => '0.5.0',
'type' => 'library',
- 'licenses' => [ 'MIT', 'GPL-3.0' ],
+ 'licenses' => [ 'MIT', 'GPL-3.0-only' ],
'authors' => [
[
'name' => 'Leaf Corcoran',
@@ -89,7 +89,7 @@ class ComposerLockTest extends MediaWikiTestCase {
'mediawiki/translate' => [
'version' => '2014.12',
'type' => 'mediawiki-extension',
- 'licenses' => [ 'GPL-2.0+' ],
+ 'licenses' => [ 'GPL-2.0-or-later' ],
'authors' => [
[
'name' => 'Niklas Laxström',
@@ -109,7 +109,7 @@ class ComposerLockTest extends MediaWikiTestCase {
'mediawiki/universal-language-selector' => [
'version' => '2014.12',
'type' => 'mediawiki-extension',
- 'licenses' => [ 'GPL-2.0+', 'MIT' ],
+ 'licenses' => [ 'GPL-2.0-or-later', 'MIT' ],
'authors' => [],
'description' => 'The primary aim is to allow users to select a language ' .
'and configure its support in an easy way. ' .
diff --git a/www/wiki/tests/phpunit/includes/libs/http/HttpAcceptNegotiatorTest.php b/www/wiki/tests/phpunit/includes/libs/http/HttpAcceptNegotiatorTest.php
index 4415bc97..02eac118 100644
--- a/www/wiki/tests/phpunit/includes/libs/http/HttpAcceptNegotiatorTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/http/HttpAcceptNegotiatorTest.php
@@ -5,10 +5,9 @@ use Wikimedia\Http\HttpAcceptNegotiator;
/**
* @covers Wikimedia\Http\HttpAcceptNegotiator
*
- * @license GPL-2.0+
* @author Daniel Kinzler
*/
-class HttpAcceptNegotiatorTest extends \PHPUnit_Framework_TestCase {
+class HttpAcceptNegotiatorTest extends \PHPUnit\Framework\TestCase {
public function provideGetFirstSupportedValue() {
return [
diff --git a/www/wiki/tests/phpunit/includes/libs/http/HttpAcceptParserTest.php b/www/wiki/tests/phpunit/includes/libs/http/HttpAcceptParserTest.php
index 5bd94253..e4b47b46 100644
--- a/www/wiki/tests/phpunit/includes/libs/http/HttpAcceptParserTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/http/HttpAcceptParserTest.php
@@ -5,10 +5,9 @@ use Wikimedia\Http\HttpAcceptParser;
/**
* @covers Wikimedia\Http\HttpAcceptParser
*
- * @license GPL-2.0+
* @author Daniel Kinzler
*/
-class HttpAcceptParserTest extends \PHPUnit_Framework_TestCase {
+class HttpAcceptParserTest extends \PHPUnit\Framework\TestCase {
public function provideParseWeights() {
return [
diff --git a/www/wiki/tests/phpunit/includes/libs/mime/MimeAnalyzerTest.php b/www/wiki/tests/phpunit/includes/libs/mime/MimeAnalyzerTest.php
index 64f49604..fbe5a2ba 100644
--- a/www/wiki/tests/phpunit/includes/libs/mime/MimeAnalyzerTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/mime/MimeAnalyzerTest.php
@@ -1,9 +1,12 @@
<?php
-/*
+/**
* @group Media
* @covers MimeAnalyzer
*/
-class MimeMagicTest extends PHPUnit_Framework_TestCase {
+class MimeAnalyzerTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
/** @var MimeAnalyzer */
private $mimeAnalyzer;
diff --git a/www/wiki/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php b/www/wiki/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php
index 51883fb1..10fba835 100644
--- a/www/wiki/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php
@@ -163,6 +163,9 @@ class BagOStuffTest extends MediaWikiTestCase {
$this->assertTrue( $this->cache->add( $key, 'test' ) );
}
+ /**
+ * @covers BagOStuff::get
+ */
public function testGet() {
$value = [ 'this' => 'is', 'a' => 'test' ];
diff --git a/www/wiki/tests/phpunit/includes/libs/objectcache/CachedBagOStuffTest.php b/www/wiki/tests/phpunit/includes/libs/objectcache/CachedBagOStuffTest.php
index 8b9abbc6..d0360a99 100644
--- a/www/wiki/tests/phpunit/includes/libs/objectcache/CachedBagOStuffTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/objectcache/CachedBagOStuffTest.php
@@ -5,7 +5,9 @@ use Wikimedia\TestingAccessWrapper;
/**
* @group BagOStuff
*/
-class CachedBagOStuffTest extends PHPUnit_Framework_TestCase {
+class CachedBagOStuffTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
/**
* @covers CachedBagOStuff::__construct
diff --git a/www/wiki/tests/phpunit/includes/libs/objectcache/HashBagOStuffTest.php b/www/wiki/tests/phpunit/includes/libs/objectcache/HashBagOStuffTest.php
index b2278c34..332e23b2 100644
--- a/www/wiki/tests/phpunit/includes/libs/objectcache/HashBagOStuffTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/objectcache/HashBagOStuffTest.php
@@ -5,7 +5,9 @@ use Wikimedia\TestingAccessWrapper;
/**
* @group BagOStuff
*/
-class HashBagOStuffTest extends PHPUnit_Framework_TestCase {
+class HashBagOStuffTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
/**
* @covers HashBagOStuff::__construct
@@ -103,7 +105,7 @@ class HashBagOStuffTest extends PHPUnit_Framework_TestCase {
for ( $i = 10; $i < 20; $i++ ) {
$cache->set( "key$i", 1 );
$this->assertEquals( 1, $cache->get( "key$i" ) );
- $this->assertEquals( false, $cache->get( "key" . $i - 10 ) );
+ $this->assertEquals( false, $cache->get( "key" . ( $i - 10 ) ) );
}
}
diff --git a/www/wiki/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php b/www/wiki/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php
index c762aa7d..662bb961 100644
--- a/www/wiki/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php
@@ -2,10 +2,28 @@
use Wikimedia\TestingAccessWrapper;
-class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
+/**
+ * @covers WANObjectCache::wrap
+ * @covers WANObjectCache::unwrap
+ * @covers WANObjectCache::worthRefreshExpiring
+ * @covers WANObjectCache::worthRefreshPopular
+ * @covers WANObjectCache::isValid
+ * @covers WANObjectCache::getWarmupKeyMisses
+ * @covers WANObjectCache::prefixCacheKeys
+ * @covers WANObjectCache::getProcessCache
+ * @covers WANObjectCache::getNonProcessCachedKeys
+ * @covers WANObjectCache::getRawKeysForWarmup
+ * @covers WANObjectCache::getInterimValue
+ * @covers WANObjectCache::setInterimValue
+ */
+class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+ use PHPUnit4And6Compat;
+
/** @var WANObjectCache */
private $cache;
- /**@var BagOStuff */
+ /** @var BagOStuff */
private $internalCache;
protected function setUp() {
@@ -145,8 +163,9 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$this->assertEquals( 9, $hit, "Values evicted" );
$key = reset( $keys );
- // Get into cache
+ // Get into cache (default process cache group)
$this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
+ $this->assertEquals( 10, $hit, "Value calculated" );
$this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
$this->assertEquals( 10, $hit, "Value cached" );
$outerCallback = function () use ( &$callback, $key ) {
@@ -154,7 +173,8 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
return 43 + $v;
};
- $this->cache->getWithSetCallback( $key, 100, $outerCallback );
+ // Outer key misses and refuses inner key process cache value
+ $this->cache->getWithSetCallback( "$key-miss-outer", 100, $outerCallback );
$this->assertEquals( 11, $hit, "Nested callback value process cache skipped" );
}
@@ -199,15 +219,17 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
$wasSet = 0;
- $v = $cache->getWithSetCallback( $key, 30, $func, [
- 'lowTTL' => 0,
- 'lockTSE' => 5,
- ] + $extOpts );
+ $v = $cache->getWithSetCallback(
+ $key, 30, $func, [ 'lowTTL' => 0, 'lockTSE' => 5 ] + $extOpts );
$this->assertEquals( $value, $v, "Value returned" );
$this->assertEquals( 0, $wasSet, "Value not regenerated" );
- $priorTime = microtime( true );
- usleep( 1 );
+ $mockWallClock = microtime( true );
+ $priorTime = $mockWallClock; // reference time
+ $cache->setMockTime( $mockWallClock );
+
+ $mockWallClock += 1;
+
$wasSet = 0;
$v = $cache->getWithSetCallback(
$key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
@@ -221,7 +243,8 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$t2 = $cache->getCheckKeyTime( $cKey2 );
$this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
- $priorTime = microtime( true );
+ $mockWallClock += 0.01;
+ $priorTime = $mockWallClock; // reference time
$wasSet = 0;
$v = $cache->getWithSetCallback(
$key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
@@ -250,6 +273,96 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$v = $cache->getWithSetCallback( $key, 30, $func, [ 'pcTTL' => 5 ] + $extOpts );
$this->assertEquals( $value, $v, "Value still returned after deleted" );
$this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
+
+ $oldValReceived = -1;
+ $oldAsOfReceived = -1;
+ $checkFunc = function ( $oldVal, &$ttl, array $setOpts, $oldAsOf )
+ use ( &$oldValReceived, &$oldAsOfReceived, &$wasSet ) {
+ ++$wasSet;
+ $oldValReceived = $oldVal;
+ $oldAsOfReceived = $oldAsOf;
+
+ return 'xxx' . $wasSet;
+ };
+
+ $mockWallClock = microtime( true );
+ $priorTime = $mockWallClock; // reference time
+
+ $wasSet = 0;
+ $key = wfRandomString();
+ $v = $cache->getWithSetCallback(
+ $key, 30, $checkFunc, [ 'staleTTL' => 50 ] + $extOpts );
+ $this->assertEquals( 'xxx1', $v, "Value returned" );
+ $this->assertEquals( false, $oldValReceived, "Callback got no stale value" );
+ $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" );
+
+ $mockWallClock += 40;
+ $v = $cache->getWithSetCallback(
+ $key, 30, $checkFunc, [ 'staleTTL' => 50 ] + $extOpts );
+ $this->assertEquals( 'xxx2', $v, "Value still returned after expired" );
+ $this->assertEquals( 2, $wasSet, "Value recalculated while expired" );
+ $this->assertEquals( 'xxx1', $oldValReceived, "Callback got stale value" );
+ $this->assertNotEquals( null, $oldAsOfReceived, "Callback got stale value" );
+
+ $mockWallClock += 260;
+ $v = $cache->getWithSetCallback(
+ $key, 30, $checkFunc, [ 'staleTTL' => 50 ] + $extOpts );
+ $this->assertEquals( 'xxx3', $v, "Value still returned after expired" );
+ $this->assertEquals( 3, $wasSet, "Value recalculated while expired" );
+ $this->assertEquals( false, $oldValReceived, "Callback got no stale value" );
+ $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" );
+
+ $mockWallClock = ( $priorTime - $cache::HOLDOFF_TTL - 1 );
+ $wasSet = 0;
+ $key = wfRandomString();
+ $checkKey = $cache->makeKey( 'template', 'X' );
+ $cache->touchCheckKey( $checkKey ); // init check key
+ $mockWallClock = $priorTime;
+ $v = $cache->getWithSetCallback(
+ $key,
+ $cache::TTL_INDEFINITE,
+ $checkFunc,
+ [ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts
+ );
+ $this->assertEquals( 'xxx1', $v, "Value returned" );
+ $this->assertEquals( 1, $wasSet, "Value computed" );
+ $this->assertEquals( false, $oldValReceived, "Callback got no stale value" );
+ $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" );
+
+ $mockWallClock += $cache::TTL_HOUR; // some time passes
+ $v = $cache->getWithSetCallback(
+ $key,
+ $cache::TTL_INDEFINITE,
+ $checkFunc,
+ [ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts
+ );
+ $this->assertEquals( 'xxx1', $v, "Cached value returned" );
+ $this->assertEquals( 1, $wasSet, "Cached value returned" );
+
+ $cache->touchCheckKey( $checkKey ); // make key stale
+ $mockWallClock += 0.01; // ~1 week left of grace (barely stale to avoid refreshes)
+
+ $v = $cache->getWithSetCallback(
+ $key,
+ $cache::TTL_INDEFINITE,
+ $checkFunc,
+ [ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts
+ );
+ $this->assertEquals( 'xxx1', $v, "Value still returned after expired (in grace)" );
+ $this->assertEquals( 1, $wasSet, "Value still returned after expired (in grace)" );
+
+ // Change of refresh increase to unity as staleness approaches graceTTL
+ $mockWallClock += $cache::TTL_WEEK; // 8 days of being stale
+ $v = $cache->getWithSetCallback(
+ $key,
+ $cache::TTL_INDEFINITE,
+ $checkFunc,
+ [ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts
+ );
+ $this->assertEquals( 'xxx2', $v, "Value was recomputed (past grace)" );
+ $this->assertEquals( 2, $wasSet, "Value was recomputed (past grace)" );
+ $this->assertEquals( 'xxx1', $oldValReceived, "Callback got post-grace stale value" );
+ $this->assertNotEquals( null, $oldAsOfReceived, "Callback got post-grace stale value" );
}
public static function getWithSetCallback_provider() {
@@ -259,10 +372,124 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
];
}
+ public function testPreemtiveRefresh() {
+ $value = 'KatCafe';
+ $wasSet = 0;
+ $func = function ( $old, &$ttl, &$opts, $asOf ) use ( &$wasSet, &$value )
+ {
+ ++$wasSet;
+ return $value;
+ };
+
+ $cache = new NearExpiringWANObjectCache( [
+ 'cache' => new HashBagOStuff(),
+ 'pool' => 'empty',
+ ] );
+
+ $wasSet = 0;
+ $key = wfRandomString();
+ $opts = [ 'lowTTL' => 30 ];
+ $v = $cache->getWithSetCallback( $key, 20, $func, $opts );
+ $this->assertEquals( $value, $v, "Value returned" );
+ $this->assertEquals( 1, $wasSet, "Value calculated" );
+ $v = $cache->getWithSetCallback( $key, 20, $func, $opts );
+ $this->assertEquals( 2, $wasSet, "Value re-calculated" );
+
+ $wasSet = 0;
+ $key = wfRandomString();
+ $opts = [ 'lowTTL' => 1 ];
+ $v = $cache->getWithSetCallback( $key, 30, $func, $opts );
+ $this->assertEquals( $value, $v, "Value returned" );
+ $this->assertEquals( 1, $wasSet, "Value calculated" );
+ $v = $cache->getWithSetCallback( $key, 30, $func, $opts );
+ $this->assertEquals( 1, $wasSet, "Value cached" );
+
+ $asycList = [];
+ $asyncHandler = function ( $callback ) use ( &$asycList ) {
+ $asycList[] = $callback;
+ };
+ $cache = new NearExpiringWANObjectCache( [
+ 'cache' => new HashBagOStuff(),
+ 'pool' => 'empty',
+ 'asyncHandler' => $asyncHandler
+ ] );
+
+ $mockWallClock = microtime( true );
+ $priorTime = $mockWallClock; // reference time
+ $cache->setMockTime( $mockWallClock );
+
+ $wasSet = 0;
+ $key = wfRandomString();
+ $opts = [ 'lowTTL' => 100 ];
+ $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
+ $this->assertEquals( $value, $v, "Value returned" );
+ $this->assertEquals( 1, $wasSet, "Value calculated" );
+ $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
+ $this->assertEquals( 1, $wasSet, "Cached value used" );
+ $this->assertEquals( $v, $value, "Value cached" );
+
+ $mockWallClock += 250;
+ $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
+ $this->assertEquals( $value, $v, "Value returned" );
+ $this->assertEquals( 1, $wasSet, "Stale value used" );
+ $this->assertEquals( 1, count( $asycList ), "Refresh deferred." );
+ $value = 'NewCatsInTown'; // change callback return value
+ $asycList[0](); // run the refresh callback
+ $asycList = [];
+ $this->assertEquals( 2, $wasSet, "Value calculated at later time" );
+ $this->assertEquals( 0, count( $asycList ), "No deferred refreshes added." );
+ $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
+ $this->assertEquals( $value, $v, "New value stored" );
+
+ $cache = new PopularityRefreshingWANObjectCache( [
+ 'cache' => new HashBagOStuff(),
+ 'pool' => 'empty'
+ ] );
+
+ $mockWallClock = $priorTime;
+ $cache->setMockTime( $mockWallClock );
+
+ $wasSet = 0;
+ $key = wfRandomString();
+ $opts = [ 'hotTTR' => 900 ];
+ $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
+ $this->assertEquals( $value, $v, "Value returned" );
+ $this->assertEquals( 1, $wasSet, "Value calculated" );
+
+ $mockWallClock += 30;
+
+ $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
+ $this->assertEquals( 1, $wasSet, "Value cached" );
+
+ $mockWallClock = $priorTime;
+ $wasSet = 0;
+ $key = wfRandomString();
+ $opts = [ 'hotTTR' => 10 ];
+ $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
+ $this->assertEquals( $value, $v, "Value returned" );
+ $this->assertEquals( 1, $wasSet, "Value calculated" );
+
+ $mockWallClock += 30;
+
+ $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
+ $this->assertEquals( $value, $v, "Value returned" );
+ $this->assertEquals( 2, $wasSet, "Value re-calculated" );
+ }
+
+ /**
+ * @covers WANObjectCache::getWithSetCallback()
+ * @covers WANObjectCache::doGetWithSetCallback()
+ */
+ public function testGetWithSetCallback_invalidCallback() {
+ $this->setExpectedException( InvalidArgumentException::class );
+ $this->cache->getWithSetCallback( 'key', 30, 'invalid callback' );
+ }
+
/**
* @dataProvider getMultiWithSetCallback_provider
- * @covers WANObjectCache::getMultiWithSetCallback()
- * @covers WANObjectCache::makeMultiKeys()
+ * @covers WANObjectCache::getMultiWithSetCallback
+ * @covers WANObjectCache::makeMultiKeys
+ * @covers WANObjectCache::getMulti
* @param array $extOpts
* @param bool $versioned
*/
@@ -317,8 +544,12 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$this->assertEquals( 1, $wasSet, "Value not regenerated" );
$this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" );
- $priorTime = microtime( true );
- usleep( 1 );
+ $mockWallClock = microtime( true );
+ $priorTime = $mockWallClock; // reference time
+ $cache->setMockTime( $mockWallClock );
+
+ $mockWallClock += 1;
+
$wasSet = 0;
$keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
$v = $cache->getMultiWithSetCallback(
@@ -333,7 +564,8 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$t2 = $cache->getCheckKeyTime( $cKey2 );
$this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
- $priorTime = microtime( true );
+ $mockWallClock += 0.01;
+ $priorTime = $mockWallClock;
$value = "@43636$";
$wasSet = 0;
$keyedIds = new ArrayIterator( [ $keyC => 43636 ] );
@@ -398,7 +630,7 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$this->assertEquals( count( $ids ), $calls, "Values cached" );
// Mock the BagOStuff to assure only one getMulti() call given process caching
- $localBag = $this->getMockBuilder( 'HashBagOStuff' )
+ $localBag = $this->getMockBuilder( HashBagOStuff::class )
->setMethods( [ 'getMulti' ] )->getMock();
$localBag->expects( $this->exactly( 1 ) )->method( 'getMulti' )->willReturn( [
WANObjectCache::VALUE_KEY_PREFIX . 'k1' => 'val-id1',
@@ -483,8 +715,12 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$this->assertEquals( 1, $wasSet, "Value not regenerated" );
$this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" );
- $priorTime = microtime( true );
- usleep( 1 );
+ $mockWallClock = microtime( true );
+ $priorTime = $mockWallClock; // reference time
+ $cache->setMockTime( $mockWallClock );
+
+ $mockWallClock += 1;
+
$wasSet = 0;
$keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
$v = $cache->getMultiWithUnionSetCallback(
@@ -497,7 +733,8 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$t2 = $cache->getCheckKeyTime( $cKey2 );
$this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
- $priorTime = microtime( true );
+ $mockWallClock += 0.01;
+ $priorTime = $mockWallClock;
$value = "@43636$";
$wasSet = 0;
$keyedIds = new ArrayIterator( [ $keyC => 43636 ] );
@@ -585,8 +822,6 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$calls = 0;
$func = function () use ( &$calls, $value, $cache, $key ) {
++$calls;
- // Immediately kill any mutex rather than waiting a second
- $cache->delete( $cache::MUTEX_KEY_PREFIX . $key );
return $value;
};
@@ -594,7 +829,7 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$this->assertEquals( $value, $ret );
$this->assertEquals( 1, $calls, 'Value was populated' );
- // Acquire a lock to verify that getWithSetCallback uses lockTSE properly
+ // Acquire the mutex to verify that getWithSetCallback uses lockTSE properly
$this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
$checkKeys = [ wfRandomString() ]; // new check keys => force misses
@@ -611,13 +846,14 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$ret = $cache->getWithSetCallback( $key, 30, $func,
[ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
- $this->assertEquals( $value, $ret, 'Callback was not used; used interim' );
- $this->assertEquals( 2, $calls, 'Callback was not used; used interim' );
+ $this->assertEquals( $value, $ret, 'Callback was not used; used interim (mutex failed)' );
+ $this->assertEquals( 2, $calls, 'Callback was not used; used interim (mutex failed)' );
}
/**
* @covers WANObjectCache::getWithSetCallback()
* @covers WANObjectCache::doGetWithSetCallback()
+ * @covers WANObjectCache::set()
*/
public function testLockTSESlow() {
$cache = $this->cache;
@@ -733,8 +969,12 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$cKey1 = wfRandomString();
$cKey2 = wfRandomString();
- $priorTime = microtime( true );
- usleep( 1 );
+ $mockWallClock = microtime( true );
+ $priorTime = $mockWallClock; // reference time
+ $cache->setMockTime( $mockWallClock );
+
+ $mockWallClock += 1;
+
$curTTLs = [];
$this->assertEquals(
[ $key1 => $value1, $key2 => $value2 ],
@@ -749,7 +989,8 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$this->assertLessThanOrEqual( 0, $curTTLs[$key1], 'Key 1 has current TTL <= 0' );
$this->assertLessThanOrEqual( 0, $curTTLs[$key2], 'Key 2 has current TTL <= 0' );
- usleep( 1 );
+ $mockWallClock += 1;
+
$curTTLs = [];
$this->assertEquals(
[ $key1 => $value1, $key2 => $value2 ],
@@ -775,12 +1016,16 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$value1 = wfRandomString();
$value2 = wfRandomString();
+ $mockWallClock = microtime( true );
+ $cache->setMockTime( $mockWallClock );
+
// Fake initial check key to be set in the past. Otherwise we'd have to sleep for
// several seconds during the test to assert the behaviour.
foreach ( [ $checkAll, $check1, $check2 ] as $checkKey ) {
$cache->touchCheckKey( $checkKey, WANObjectCache::HOLDOFF_NONE );
}
- usleep( 100 );
+
+ $mockWallClock += 0.100;
$cache->set( 'key1', $value1, 10 );
$cache->set( 'key2', $value2, 10 );
@@ -802,6 +1047,7 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$this->assertGreaterThanOrEqual( 9.5, $curTTLs['key2'], 'Initial ttls' );
$this->assertLessThanOrEqual( 10.5, $curTTLs['key2'], 'Initial ttls' );
+ $mockWallClock += 0.100;
$cache->touchCheckKey( $check1 );
$curTTLs = [];
@@ -871,7 +1117,9 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
}
/**
- * @covers WANObjectCache::delete()
+ * @covers WANObjectCache::delete
+ * @covers WANObjectCache::relayDelete
+ * @covers WANObjectCache::relayPurge
*/
public function testDelete() {
$key = wfRandomString();
@@ -911,6 +1159,8 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
/**
* @dataProvider getWithSetCallback_versions_provider
+ * @covers WANObjectCache::getWithSetCallback()
+ * @covers WANObjectCache::doGetWithSetCallback()
* @param array $extOpts
* @param bool $versioned
*/
@@ -918,53 +1168,69 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$cache = $this->cache;
$key = wfRandomString();
- $value = wfRandomString();
+ $valueV1 = wfRandomString();
+ $valueV2 = [ wfRandomString() ];
$wasSet = 0;
- $func = function ( $old, &$ttl ) use ( &$wasSet, $value ) {
+ $funcV1 = function () use ( &$wasSet, $valueV1 ) {
++$wasSet;
- return $value;
+
+ return $valueV1;
+ };
+
+ $priorValue = false;
+ $priorAsOf = null;
+ $funcV2 = function ( $oldValue, &$ttl, $setOpts, $oldAsOf )
+ use ( &$wasSet, $valueV2, &$priorValue, &$priorAsOf ) {
+ $priorValue = $oldValue;
+ $priorAsOf = $oldAsOf;
+ ++$wasSet;
+
+ return $valueV2; // new array format
};
// Set the main key (version N if versioned)
$wasSet = 0;
- $v = $cache->getWithSetCallback( $key, 30, $func, $extOpts );
- $this->assertEquals( $value, $v, "Value returned" );
+ $v = $cache->getWithSetCallback( $key, 30, $funcV1, $extOpts );
+ $this->assertEquals( $valueV1, $v, "Value returned" );
$this->assertEquals( 1, $wasSet, "Value regenerated" );
- $cache->getWithSetCallback( $key, 30, $func, $extOpts );
+ $cache->getWithSetCallback( $key, 30, $funcV1, $extOpts );
$this->assertEquals( 1, $wasSet, "Value not regenerated" );
- // Set the key for version N+1 (if versioned)
+ $this->assertEquals( $valueV1, $v, "Value not regenerated" );
+
if ( $versioned ) {
+ // Set the key for version N+1 format
$verOpts = [ 'version' => $extOpts['version'] + 1 ];
-
- $wasSet = 0;
- $v = $cache->getWithSetCallback( $key, 30, $func, $verOpts + $extOpts );
- $this->assertEquals( $value, $v, "Value returned" );
- $this->assertEquals( 1, $wasSet, "Value regenerated" );
-
- $wasSet = 0;
- $v = $cache->getWithSetCallback( $key, 30, $func, $verOpts + $extOpts );
- $this->assertEquals( $value, $v, "Value returned" );
- $this->assertEquals( 0, $wasSet, "Value not regenerated" );
+ } else {
+ // Start versioning now with the unversioned key still there
+ $verOpts = [ 'version' => 1 ];
}
+ // Value goes to secondary key since V1 already used $key
$wasSet = 0;
- $cache->getWithSetCallback( $key, 30, $func, $extOpts );
- $this->assertEquals( 0, $wasSet, "Value not regenerated" );
+ $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts );
+ $this->assertEquals( $valueV2, $v, "Value returned" );
+ $this->assertEquals( 1, $wasSet, "Value regenerated" );
+ $this->assertEquals( false, $priorValue, "Old value not given due to old format" );
+ $this->assertEquals( null, $priorAsOf, "Old value not given due to old format" );
$wasSet = 0;
- $cache->delete( $key );
- $v = $cache->getWithSetCallback( $key, 30, $func, $extOpts );
- $this->assertEquals( $value, $v, "Value returned" );
+ $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts );
+ $this->assertEquals( $valueV2, $v, "Value not regenerated (secondary key)" );
+ $this->assertEquals( 0, $wasSet, "Value not regenerated (secondary key)" );
+
+ // Clear out the older or unversioned key
+ $cache->delete( $key, 0 );
+
+ // Set the key for next/first versioned format
+ $wasSet = 0;
+ $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts );
+ $this->assertEquals( $valueV2, $v, "Value returned" );
$this->assertEquals( 1, $wasSet, "Value regenerated" );
- if ( $versioned ) {
- $wasSet = 0;
- $verOpts = [ 'version' => $extOpts['version'] + 1 ];
- $v = $cache->getWithSetCallback( $key, 30, $func, $verOpts + $extOpts );
- $this->assertEquals( $value, $v, "Value returned" );
- $this->assertEquals( 1, $wasSet, "Value regenerated" );
- }
+ $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts );
+ $this->assertEquals( $valueV2, $v, "Value not regenerated (main key)" );
+ $this->assertEquals( 1, $wasSet, "Value not regenerated (main key)" );
}
public static function getWithSetCallback_versions_provider() {
@@ -975,41 +1241,100 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
}
/**
- * @covers WANObjectCache::touchCheckKey()
- * @covers WANObjectCache::resetCheckKey()
- * @covers WANObjectCache::getCheckKeyTime()
+ * @covers WANObjectCache::useInterimHoldOffCaching
+ * @covers WANObjectCache::getInterimValue
+ */
+ public function testInterimHoldOffCaching() {
+ $cache = $this->cache;
+
+ $value = 'CRL-40-940';
+ $wasCalled = 0;
+ $func = function () use ( &$wasCalled, $value ) {
+ $wasCalled++;
+
+ return $value;
+ };
+
+ $cache->useInterimHoldOffCaching( true );
+
+ $key = wfRandomString( 32 );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 1, $wasCalled, 'Value cached' );
+ $cache->delete( $key );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 2, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 3, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim
+ // Lock up the mutex so interim cache is used
+ $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 3, $wasCalled, 'Value interim cached (failed mutex)' );
+ $this->internalCache->delete( $cache::MUTEX_KEY_PREFIX . $key );
+
+ $cache->useInterimHoldOffCaching( false );
+
+ $wasCalled = 0;
+ $key = wfRandomString( 32 );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 1, $wasCalled, 'Value cached' );
+ $cache->delete( $key );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 2, $wasCalled, 'Value regenerated (got mutex)' );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 3, $wasCalled, 'Value still regenerated (got mutex)' );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 4, $wasCalled, 'Value still regenerated (got mutex)' );
+ // Lock up the mutex so interim cache is used
+ $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 5, $wasCalled, 'Value still regenerated (failed mutex)' );
+ }
+
+ /**
+ * @covers WANObjectCache::touchCheckKey
+ * @covers WANObjectCache::resetCheckKey
+ * @covers WANObjectCache::getCheckKeyTime
+ * @covers WANObjectCache::getMultiCheckKeyTime
+ * @covers WANObjectCache::makePurgeValue
+ * @covers WANObjectCache::parsePurgeValue
*/
public function testTouchKeys() {
+ $cache = $this->cache;
$key = wfRandomString();
- $priorTime = microtime( true );
- usleep( 100 );
- $t0 = $this->cache->getCheckKeyTime( $key );
+ $mockWallClock = microtime( true );
+ $priorTime = $mockWallClock; // reference time
+ $cache->setMockTime( $mockWallClock );
+
+ $mockWallClock += 0.100;
+ $t0 = $cache->getCheckKeyTime( $key );
$this->assertGreaterThanOrEqual( $priorTime, $t0, 'Check key auto-created' );
- $priorTime = microtime( true );
- usleep( 100 );
- $this->cache->touchCheckKey( $key );
- $t1 = $this->cache->getCheckKeyTime( $key );
+ $priorTime = $mockWallClock;
+ $mockWallClock += 0.100;
+ $cache->touchCheckKey( $key );
+ $t1 = $cache->getCheckKeyTime( $key );
$this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key created' );
- $t2 = $this->cache->getCheckKeyTime( $key );
+ $t2 = $cache->getCheckKeyTime( $key );
$this->assertEquals( $t1, $t2, 'Check key time did not change' );
- usleep( 100 );
- $this->cache->touchCheckKey( $key );
- $t3 = $this->cache->getCheckKeyTime( $key );
+ $mockWallClock += 0.100;
+ $cache->touchCheckKey( $key );
+ $t3 = $cache->getCheckKeyTime( $key );
$this->assertGreaterThan( $t2, $t3, 'Check key time increased' );
- $t4 = $this->cache->getCheckKeyTime( $key );
+ $t4 = $cache->getCheckKeyTime( $key );
$this->assertEquals( $t3, $t4, 'Check key time did not change' );
- usleep( 100 );
- $this->cache->resetCheckKey( $key );
- $t5 = $this->cache->getCheckKeyTime( $key );
+ $mockWallClock += 0.100;
+ $cache->resetCheckKey( $key );
+ $t5 = $cache->getCheckKeyTime( $key );
$this->assertGreaterThan( $t4, $t5, 'Check key time increased' );
- $t6 = $this->cache->getCheckKeyTime( $key );
+ $t6 = $cache->getCheckKeyTime( $key );
$this->assertEquals( $t5, $t6, 'Check key time did not change' );
}
@@ -1101,6 +1426,34 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
}
/**
+ * @covers WANObjectCache::reap()
+ */
+ public function testReap_fail() {
+ $backend = $this->getMockBuilder( EmptyBagOStuff::class )
+ ->setMethods( [ 'get', 'changeTTL' ] )->getMock();
+ $backend->expects( $this->once() )->method( 'get' )
+ ->willReturn( [
+ WANObjectCache::FLD_VERSION => WANObjectCache::VERSION,
+ WANObjectCache::FLD_VALUE => 'value',
+ WANObjectCache::FLD_TTL => 3600,
+ WANObjectCache::FLD_TIME => 300,
+ ] );
+ $backend->expects( $this->once() )->method( 'changeTTL' )
+ ->willReturn( false );
+
+ $wanCache = new WANObjectCache( [
+ 'cache' => $backend,
+ 'pool' => 'testcache-hash',
+ 'relayer' => new EventRelayerNull( [] )
+ ] );
+
+ $isStale = null;
+ $ret = $wanCache->reap( 'key', 360, $isStale );
+ $this->assertTrue( $isStale, 'value was stale' );
+ $this->assertFalse( $ret, 'changeTTL failed' );
+ }
+
+ /**
* @covers WANObjectCache::set()
*/
public function testSetWithLag() {
@@ -1135,14 +1488,17 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
}
public function testMcRouterSupport() {
- $localBag = $this->getMockBuilder( 'EmptyBagOStuff' )
+ $localBag = $this->getMockBuilder( EmptyBagOStuff::class )
->setMethods( [ 'set', 'delete' ] )->getMock();
$localBag->expects( $this->never() )->method( 'set' );
$localBag->expects( $this->never() )->method( 'delete' );
$wanCache = new WANObjectCache( [
'cache' => $localBag,
'pool' => 'testcache-hash',
- 'relayer' => new EventRelayerNull( [] )
+ 'relayer' => new EventRelayerNull( [] ),
+ 'mcrouterAware' => true,
+ 'region' => 'pmtpa',
+ 'cluster' => 'mw-wan'
] );
$valFunc = function () {
return 1;
@@ -1159,6 +1515,60 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$wanCache->reap( 'zzz', time() - 300 );
}
+ public function testMcRouterSupportBroadcastDelete() {
+ $localBag = $this->getMockBuilder( EmptyBagOStuff::class )
+ ->setMethods( [ 'set' ] )->getMock();
+ $wanCache = new WANObjectCache( [
+ 'cache' => $localBag,
+ 'pool' => 'testcache-hash',
+ 'relayer' => new EventRelayerNull( [] ),
+ 'mcrouterAware' => true,
+ 'region' => 'pmtpa',
+ 'cluster' => 'mw-wan'
+ ] );
+
+ $localBag->expects( $this->once() )->method( 'set' )
+ ->with( "/*/mw-wan/" . $wanCache::VALUE_KEY_PREFIX . "test" );
+
+ $wanCache->delete( 'test' );
+ }
+
+ public function testMcRouterSupportBroadcastTouchCK() {
+ $localBag = $this->getMockBuilder( EmptyBagOStuff::class )
+ ->setMethods( [ 'set' ] )->getMock();
+ $wanCache = new WANObjectCache( [
+ 'cache' => $localBag,
+ 'pool' => 'testcache-hash',
+ 'relayer' => new EventRelayerNull( [] ),
+ 'mcrouterAware' => true,
+ 'region' => 'pmtpa',
+ 'cluster' => 'mw-wan'
+ ] );
+
+ $localBag->expects( $this->once() )->method( 'set' )
+ ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX . "test" );
+
+ $wanCache->touchCheckKey( 'test' );
+ }
+
+ public function testMcRouterSupportBroadcastResetCK() {
+ $localBag = $this->getMockBuilder( EmptyBagOStuff::class )
+ ->setMethods( [ 'delete' ] )->getMock();
+ $wanCache = new WANObjectCache( [
+ 'cache' => $localBag,
+ 'pool' => 'testcache-hash',
+ 'relayer' => new EventRelayerNull( [] ),
+ 'mcrouterAware' => true,
+ 'region' => 'pmtpa',
+ 'cluster' => 'mw-wan'
+ ] );
+
+ $localBag->expects( $this->once() )->method( 'delete' )
+ ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX . "test" );
+
+ $wanCache->resetCheckKey( 'test' );
+ }
+
/**
* @dataProvider provideAdaptiveTTL
* @covers WANObjectCache::adaptiveTTL()
@@ -1193,6 +1603,40 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
}
/**
+ * @covers WANObjectCache::__construct
+ * @covers WANObjectCache::newEmpty
+ */
+ public function testNewEmpty() {
+ $this->assertInstanceOf(
+ WANObjectCache::class,
+ WANObjectCache::newEmpty()
+ );
+ }
+
+ /**
+ * @covers WANObjectCache::setLogger
+ */
+ public function testSetLogger() {
+ $this->assertSame( null, $this->cache->setLogger( new Psr\Log\NullLogger ) );
+ }
+
+ /**
+ * @covers WANObjectCache::getQoS
+ */
+ public function testGetQoS() {
+ $backend = $this->getMockBuilder( HashBagOStuff::class )
+ ->setMethods( [ 'getQoS' ] )->getMock();
+ $backend->expects( $this->once() )->method( 'getQoS' )
+ ->willReturn( BagOStuff::QOS_UNKNOWN );
+ $wanCache = new WANObjectCache( [ 'cache' => $backend ] );
+
+ $this->assertSame(
+ $wanCache::QOS_UNKNOWN,
+ $wanCache->getQoS( $wanCache::ATTR_EMULATION )
+ );
+ }
+
+ /**
* @covers WANObjectCache::makeKey
*/
public function testMakeKey() {
@@ -1227,4 +1671,41 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
$this->assertSame( 'special', $wanCache->makeGlobalKey( 'a', 'b' ) );
}
+
+ public static function statsKeyProvider() {
+ return [
+ [ 'domain:page:5', 'page' ],
+ [ 'domain:main-key', 'main-key' ],
+ [ 'domain:page:history', 'page' ],
+ [ 'missingdomainkey', 'missingdomainkey' ]
+ ];
+ }
+
+ /**
+ * @dataProvider statsKeyProvider
+ * @covers WANObjectCache::determineKeyClass
+ */
+ public function testStatsKeyClass( $key, $class ) {
+ $wanCache = TestingAccessWrapper::newFromObject( new WANObjectCache( [
+ 'cache' => new HashBagOStuff,
+ 'pool' => 'testcache-hash',
+ 'relayer' => new EventRelayerNull( [] )
+ ] ) );
+
+ $this->assertEquals( $class, $wanCache->determineKeyClass( $key ) );
+ }
+}
+
+class NearExpiringWANObjectCache extends WANObjectCache {
+ const CLOCK_SKEW = 1;
+
+ protected function worthRefreshExpiring( $curTTL, $lowTTL ) {
+ return ( $curTTL > 0 && ( $curTTL + self::CLOCK_SKEW ) < $lowTTL );
+ }
+}
+
+class PopularityRefreshingWANObjectCache extends WANObjectCache {
+ protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) {
+ return ( ( $now - $asOf ) > $timeTillRefresh );
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/libs/rdbms/TransactionProfilerTest.php b/www/wiki/tests/phpunit/includes/libs/rdbms/TransactionProfilerTest.php
index b6ea4265..538d625c 100644
--- a/www/wiki/tests/phpunit/includes/libs/rdbms/TransactionProfilerTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/rdbms/TransactionProfilerTest.php
@@ -3,10 +3,16 @@
use Wikimedia\Rdbms\TransactionProfiler;
use Psr\Log\LoggerInterface;
-class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
+/**
+ * @covers \Wikimedia\Rdbms\TransactionProfiler
+ */
+class TransactionProfilerTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
public function testAffected() {
$logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
- $logger->expects( $this->exactly( 3 ) )->method( 'info' );
+ $logger->expects( $this->exactly( 3 ) )->method( 'warning' );
$tp = new TransactionProfiler();
$tp->setLogger( $logger );
@@ -21,7 +27,7 @@ class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
public function testReadTime() {
$logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
// 1 per query
- $logger->expects( $this->exactly( 2 ) )->method( 'info' );
+ $logger->expects( $this->exactly( 2 ) )->method( 'warning' );
$tp = new TransactionProfiler();
$tp->setLogger( $logger );
@@ -36,7 +42,7 @@ class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
public function testWriteTime() {
$logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
// 1 per query, 1 per trx, and one "sub-optimal trx" entry
- $logger->expects( $this->exactly( 4 ) )->method( 'info' );
+ $logger->expects( $this->exactly( 4 ) )->method( 'warning' );
$tp = new TransactionProfiler();
$tp->setLogger( $logger );
@@ -50,7 +56,7 @@ class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
public function testAffectedTrx() {
$logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
- $logger->expects( $this->exactly( 1 ) )->method( 'info' );
+ $logger->expects( $this->exactly( 1 ) )->method( 'warning' );
$tp = new TransactionProfiler();
$tp->setLogger( $logger );
@@ -63,7 +69,7 @@ class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
public function testWriteTimeTrx() {
$logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
// 1 per trx, and one "sub-optimal trx" entry
- $logger->expects( $this->exactly( 2 ) )->method( 'info' );
+ $logger->expects( $this->exactly( 2 ) )->method( 'warning' );
$tp = new TransactionProfiler();
$tp->setLogger( $logger );
@@ -75,7 +81,7 @@ class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
public function testConns() {
$logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
- $logger->expects( $this->exactly( 2 ) )->method( 'info' );
+ $logger->expects( $this->exactly( 2 ) )->method( 'warning' );
$tp = new TransactionProfiler();
$tp->setLogger( $logger );
@@ -89,7 +95,7 @@ class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
public function testMasterConns() {
$logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
- $logger->expects( $this->exactly( 2 ) )->method( 'info' );
+ $logger->expects( $this->exactly( 2 ) )->method( 'warning' );
$tp = new TransactionProfiler();
$tp->setLogger( $logger );
@@ -106,7 +112,7 @@ class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
public function testReadQueryCount() {
$logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
- $logger->expects( $this->exactly( 2 ) )->method( 'info' );
+ $logger->expects( $this->exactly( 2 ) )->method( 'warning' );
$tp = new TransactionProfiler();
$tp->setLogger( $logger );
@@ -120,7 +126,7 @@ class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
public function testWriteQueryCount() {
$logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
- $logger->expects( $this->exactly( 2 ) )->method( 'info' );
+ $logger->expects( $this->exactly( 2 ) )->method( 'warning' );
$tp = new TransactionProfiler();
$tp->setLogger( $logger );
diff --git a/www/wiki/tests/phpunit/includes/libs/rdbms/connectionmanager/ConnectionManagerTest.php b/www/wiki/tests/phpunit/includes/libs/rdbms/connectionmanager/ConnectionManagerTest.php
index a3f39810..dd86a73e 100644
--- a/www/wiki/tests/phpunit/includes/libs/rdbms/connectionmanager/ConnectionManagerTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/rdbms/connectionmanager/ConnectionManagerTest.php
@@ -2,7 +2,7 @@
namespace Wikimedia\Tests\Rdbms;
-use IDatabase;
+use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\LoadBalancer;
use PHPUnit_Framework_MockObject_MockObject;
use Wikimedia\Rdbms\ConnectionManager;
@@ -10,10 +10,9 @@ use Wikimedia\Rdbms\ConnectionManager;
/**
* @covers Wikimedia\Rdbms\ConnectionManager
*
- * @license GPL-2.0+
* @author Daniel Kinzler
*/
-class ConnectionManagerTest extends \PHPUnit_Framework_TestCase {
+class ConnectionManagerTest extends \PHPUnit\Framework\TestCase {
/**
* @return IDatabase|PHPUnit_Framework_MockObject_MockObject
diff --git a/www/wiki/tests/phpunit/includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManagerTest.php b/www/wiki/tests/phpunit/includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManagerTest.php
index 4e76f2a8..8d7d104c 100644
--- a/www/wiki/tests/phpunit/includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManagerTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManagerTest.php
@@ -2,7 +2,7 @@
namespace Wikimedia\Tests\Rdbms;
-use IDatabase;
+use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\LoadBalancer;
use PHPUnit_Framework_MockObject_MockObject;
use Wikimedia\Rdbms\SessionConsistentConnectionManager;
@@ -10,10 +10,9 @@ use Wikimedia\Rdbms\SessionConsistentConnectionManager;
/**
* @covers Wikimedia\Rdbms\SessionConsistentConnectionManager
*
- * @license GPL-2.0+
* @author Daniel Kinzler
*/
-class SessionConsistentConnectionManagerTest extends \PHPUnit_Framework_TestCase {
+class SessionConsistentConnectionManagerTest extends \PHPUnit\Framework\TestCase {
/**
* @return IDatabase|PHPUnit_Framework_MockObject_MockObject
diff --git a/www/wiki/tests/phpunit/includes/libs/rdbms/database/DBConnRefTest.php b/www/wiki/tests/phpunit/includes/libs/rdbms/database/DBConnRefTest.php
new file mode 100644
index 00000000..c3cddc61
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/libs/rdbms/database/DBConnRefTest.php
@@ -0,0 +1,148 @@
+<?php
+
+use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\DBConnRef;
+use Wikimedia\Rdbms\FakeResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\ILoadBalancer;
+use Wikimedia\Rdbms\ResultWrapper;
+
+/**
+ * @covers Wikimedia\Rdbms\DBConnRef
+ */
+class DBConnRefTest extends PHPUnit\Framework\TestCase {
+
+ use PHPUnit4And6Compat;
+
+ /**
+ * @return ILoadBalancer
+ */
+ private function getLoadBalancerMock() {
+ $lb = $this->getMock( ILoadBalancer::class );
+
+ $lb->method( 'getConnection' )->willReturnCallback(
+ function () {
+ return $this->getDatabaseMock();
+ }
+ );
+
+ $lb->method( 'getConnectionRef' )->willReturnCallback(
+ function () use ( $lb ) {
+ return $this->getDBConnRef( $lb );
+ }
+ );
+
+ return $lb;
+ }
+
+ /**
+ * @return IDatabase
+ */
+ private function getDatabaseMock() {
+ $db = $this->getMockBuilder( Database::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $db->method( 'select' )->willReturn( new FakeResultWrapper( [] ) );
+ $db->method( '__toString' )->willReturn( 'MOCK_DB' );
+
+ return $db;
+ }
+
+ /**
+ * @return IDatabase
+ */
+ private function getDBConnRef( ILoadBalancer $lb = null ) {
+ $lb = $lb ?: $this->getLoadBalancerMock();
+ return new DBConnRef( $lb, $this->getDatabaseMock() );
+ }
+
+ public function testConstruct() {
+ $lb = $this->getLoadBalancerMock();
+ $ref = new DBConnRef( $lb, $this->getDatabaseMock() );
+
+ $this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) );
+ }
+
+ public function testConstruct_params() {
+ $lb = $this->getMock( ILoadBalancer::class );
+
+ $lb->expects( $this->once() )
+ ->method( 'getConnection' )
+ ->with( DB_MASTER, [ 'test' ], 'dummy', ILoadBalancer::CONN_TRX_AUTOCOMMIT )
+ ->willReturnCallback(
+ function () {
+ return $this->getDatabaseMock();
+ }
+ );
+
+ $ref = new DBConnRef(
+ $lb,
+ [ DB_MASTER, [ 'test' ], 'dummy', ILoadBalancer::CONN_TRX_AUTOCOMMIT ]
+ );
+
+ $this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) );
+ }
+
+ public function testDestruct() {
+ $lb = $this->getLoadBalancerMock();
+
+ $lb->expects( $this->once() )
+ ->method( 'reuseConnection' );
+
+ $this->innerMethodForTestDestruct( $lb );
+ }
+
+ private function innerMethodForTestDestruct( ILoadBalancer $lb ) {
+ $ref = $lb->getConnectionRef( DB_REPLICA );
+
+ $this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) );
+ }
+
+ public function testConstruct_failure() {
+ $this->setExpectedException( InvalidArgumentException::class, '' );
+
+ $lb = $this->getLoadBalancerMock();
+ new DBConnRef( $lb, 17 ); // bad constructor argument
+ }
+
+ public function testGetWikiID() {
+ $lb = $this->getMock( ILoadBalancer::class );
+
+ // getWikiID is optimized to not create a connection
+ $lb->expects( $this->never() )
+ ->method( 'getConnection' );
+
+ $ref = new DBConnRef( $lb, [ DB_REPLICA, [], 'dummy', 0 ] );
+
+ $this->assertSame( 'dummy', $ref->getWikiID() );
+ }
+
+ public function testGetDomainID() {
+ $lb = $this->getMock( ILoadBalancer::class );
+
+ // getDomainID is optimized to not create a connection
+ $lb->expects( $this->never() )
+ ->method( 'getConnection' );
+
+ $ref = new DBConnRef( $lb, [ DB_REPLICA, [], 'dummy', 0 ] );
+
+ $this->assertSame( 'dummy', $ref->getDomainID() );
+ }
+
+ public function testSelect() {
+ // select should get passed through normally
+ $ref = $this->getDBConnRef();
+ $this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) );
+ }
+
+ public function testToString() {
+ $ref = $this->getDBConnRef();
+ $this->assertInternalType( 'string', $ref->__toString() );
+
+ $lb = $this->getLoadBalancerMock();
+ $ref = new DBConnRef( $lb, [ DB_MASTER, [], 'test', 0 ] );
+ $this->assertInternalType( 'string', $ref->__toString() );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseDomainTest.php b/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseDomainTest.php
index a8dbdd39..b2e71554 100644
--- a/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseDomainTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseDomainTest.php
@@ -5,7 +5,11 @@ use Wikimedia\Rdbms\DatabaseDomain;
/**
* @covers Wikimedia\Rdbms\DatabaseDomain
*/
-class DatabaseDomainTest extends PHPUnit_Framework_TestCase {
+class DatabaseDomainTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+ use PHPUnit4And6Compat;
+
public static function provideConstruct() {
return [
'All strings' =>
diff --git a/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseMssqlTest.php b/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseMssqlTest.php
new file mode 100644
index 00000000..b28a5b9e
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseMssqlTest.php
@@ -0,0 +1,55 @@
+<?php
+
+use Wikimedia\Rdbms\DatabaseMssql;
+
+class DatabaseMssqlTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+ use PHPUnit4And6Compat;
+
+ /**
+ * @return PHPUnit_Framework_MockObject_MockObject|DatabaseMssql
+ */
+ private function getMockDb() {
+ return $this->getMockBuilder( DatabaseMssql::class )
+ ->disableOriginalConstructor()
+ ->setMethods( null )
+ ->getMock();
+ }
+
+ public function provideBuildSubstring() {
+ yield [ 'someField', 1, 2, 'SUBSTRING(someField,1,2)' ];
+ yield [ 'someField', 1, null, 'SUBSTRING(someField,1,2147483647)' ];
+ yield [ 'someField', 1, 3333333333, 'SUBSTRING(someField,1,3333333333)' ];
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\DatabaseMssql::buildSubstring
+ * @dataProvider provideBuildSubstring
+ */
+ public function testBuildSubstring( $input, $start, $length, $expected ) {
+ $mockDb = $this->getMockDb();
+ $output = $mockDb->buildSubstring( $input, $start, $length );
+ $this->assertSame( $expected, $output );
+ }
+
+ public function provideBuildSubstring_invalidParams() {
+ yield [ -1, 1 ];
+ yield [ 1, -1 ];
+ yield [ 1, 'foo' ];
+ yield [ 'foo', 1 ];
+ yield [ null, 1 ];
+ yield [ 0, 1 ];
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\DatabaseMssql::buildSubstring
+ * @dataProvider provideBuildSubstring_invalidParams
+ */
+ public function testBuildSubstring_invalidParams( $start, $length ) {
+ $mockDb = $this->getMockDb();
+ $this->setExpectedException( InvalidArgumentException::class );
+ $mockDb->buildSubstring( 'foo', $start, $length );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php b/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php
index 456447f0..93192d01 100644
--- a/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php
@@ -23,94 +23,24 @@
* @copyright © 2013 Wikimedia Foundation and contributors
*/
-use Wikimedia\Rdbms\TransactionProfiler;
-use Wikimedia\Rdbms\DatabaseDomain;
use Wikimedia\Rdbms\MySQLMasterPos;
-use Wikimedia\Rdbms\DatabaseMysqlBase;
+use Wikimedia\TestingAccessWrapper;
-/**
- * Fake class around abstract class so we can call concrete methods.
- */
-class FakeDatabaseMysqlBase extends DatabaseMysqlBase {
- // From Database
- function __construct() {
- $this->profiler = new ProfilerStub( [] );
- $this->trxProfiler = new TransactionProfiler();
- $this->cliMode = true;
- $this->connLogger = new \Psr\Log\NullLogger();
- $this->queryLogger = new \Psr\Log\NullLogger();
- $this->errorLogger = function ( Exception $e ) {
- wfWarn( get_class( $e ) . ": {$e->getMessage()}" );
- };
- $this->currentDomain = DatabaseDomain::newUnspecified();
- }
-
- protected function closeConnection() {
- }
-
- protected function doQuery( $sql ) {
- }
-
- // From DatabaseMysql
- protected function mysqlConnect( $realServer ) {
- }
-
- protected function mysqlSetCharset( $charset ) {
- }
-
- protected function mysqlFreeResult( $res ) {
- }
-
- protected function mysqlFetchObject( $res ) {
- }
-
- protected function mysqlFetchArray( $res ) {
- }
-
- protected function mysqlNumRows( $res ) {
- }
-
- protected function mysqlNumFields( $res ) {
- }
+class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase {
- protected function mysqlFieldName( $res, $n ) {
- }
-
- protected function mysqlFieldType( $res, $n ) {
- }
+ use MediaWikiCoversValidator;
+ use PHPUnit4And6Compat;
- protected function mysqlDataSeek( $res, $row ) {
- }
-
- protected function mysqlError( $conn = null ) {
- }
-
- protected function mysqlFetchField( $res, $n ) {
- }
-
- protected function mysqlRealEscapeString( $s ) {
- }
-
- function insertId() {
- }
-
- function lastErrno() {
- }
-
- function affectedRows() {
- }
-
- function getServerVersion() {
- }
-}
-
-class DatabaseMysqlBaseTest extends PHPUnit_Framework_TestCase {
/**
* @dataProvider provideDiapers
* @covers Wikimedia\Rdbms\DatabaseMysqlBase::addIdentifierQuotes
*/
public function testAddIdentifierQuotes( $expected, $in ) {
- $db = new FakeDatabaseMysqlBase();
+ $db = $this->getMockBuilder( DatabaseMysqli::class )
+ ->disableOriginalConstructor()
+ ->setMethods( null )
+ ->getMock();
+
$quoted = $db->addIdentifierQuotes( $in );
$this->assertEquals( $expected, $quoted );
}
@@ -172,7 +102,7 @@ class DatabaseMysqlBaseTest extends PHPUnit_Framework_TestCase {
}
private function getMockForViews() {
- $db = $this->getMockBuilder( 'DatabaseMysqli' )
+ $db = $this->getMockBuilder( DatabaseMysqli::class )
->disableOriginalConstructor()
->setMethods( [ 'fetchRow', 'query' ] )
->getMock();
@@ -209,16 +139,34 @@ class DatabaseMysqlBaseTest extends PHPUnit_Framework_TestCase {
}
/**
+ * @covers Wikimedia\Rdbms\MySQLMasterPos
+ */
+ public function testBinLogName() {
+ $pos = new MySQLMasterPos( "db1052.2424/4643", 1 );
+
+ $this->assertEquals( "db1052", $pos->getLogName() );
+ $this->assertEquals( "db1052.2424", $pos->getLogFile() );
+ $this->assertEquals( [ 2424, 4643 ], $pos->getLogPosition() );
+ }
+
+ /**
* @dataProvider provideComparePositions
* @covers Wikimedia\Rdbms\MySQLMasterPos
*/
- public function testHasReached( MySQLMasterPos $lowerPos, MySQLMasterPos $higherPos, $match ) {
+ public function testHasReached(
+ MySQLMasterPos $lowerPos, MySQLMasterPos $higherPos, $match, $hetero
+ ) {
if ( $match ) {
$this->assertTrue( $lowerPos->channelsMatch( $higherPos ) );
- $this->assertTrue( $higherPos->hasReached( $lowerPos ) );
- $this->assertTrue( $higherPos->hasReached( $higherPos ) );
+ if ( $hetero ) {
+ // Each position is has one channel higher than the other
+ $this->assertFalse( $higherPos->hasReached( $lowerPos ) );
+ } else {
+ $this->assertTrue( $higherPos->hasReached( $lowerPos ) );
+ }
$this->assertTrue( $lowerPos->hasReached( $lowerPos ) );
+ $this->assertTrue( $higherPos->hasReached( $higherPos ) );
$this->assertFalse( $lowerPos->hasReached( $higherPos ) );
} else { // channels don't match
$this->assertFalse( $lowerPos->channelsMatch( $higherPos ) );
@@ -229,53 +177,100 @@ class DatabaseMysqlBaseTest extends PHPUnit_Framework_TestCase {
}
public static function provideComparePositions() {
+ $now = microtime( true );
+
return [
// Binlog style
[
- new MySQLMasterPos( 'db1034-bin.000976', '843431247' ),
- new MySQLMasterPos( 'db1034-bin.000976', '843431248' ),
- true
+ new MySQLMasterPos( 'db1034-bin.000976/843431247', $now ),
+ new MySQLMasterPos( 'db1034-bin.000976/843431248', $now ),
+ true,
+ false
],
[
- new MySQLMasterPos( 'db1034-bin.000976', '999' ),
- new MySQLMasterPos( 'db1034-bin.000976', '1000' ),
- true
+ new MySQLMasterPos( 'db1034-bin.000976/999', $now ),
+ new MySQLMasterPos( 'db1034-bin.000976/1000', $now ),
+ true,
+ false
],
[
- new MySQLMasterPos( 'db1034-bin.000976', '999' ),
- new MySQLMasterPos( 'db1035-bin.000976', '1000' ),
+ new MySQLMasterPos( 'db1034-bin.000976/999', $now ),
+ new MySQLMasterPos( 'db1035-bin.000976/1000', $now ),
+ false,
false
],
// MySQL GTID style
[
- new MySQLMasterPos( 'db1-bin.2', '1', '3E11FA47-71CA-11E1-9E33-C80AA9429562:23' ),
- new MySQLMasterPos( 'db1-bin.2', '2', '3E11FA47-71CA-11E1-9E33-C80AA9429562:24' ),
- true
+ new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:1-23', $now ),
+ new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:5-24', $now ),
+ true,
+ false
],
[
- new MySQLMasterPos( 'db1-bin.2', '1', '3E11FA47-71CA-11E1-9E33-C80AA9429562:99' ),
- new MySQLMasterPos( 'db1-bin.2', '2', '3E11FA47-71CA-11E1-9E33-C80AA9429562:100' ),
- true
+ new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:5-99', $now ),
+ new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:1-100', $now ),
+ true,
+ false
],
[
- new MySQLMasterPos( 'db1-bin.2', '1', '3E11FA47-71CA-11E1-9E33-C80AA9429562:99' ),
- new MySQLMasterPos( 'db1-bin.2', '2', '1E11FA47-71CA-11E1-9E33-C80AA9429562:100' ),
+ new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:1-99', $now ),
+ new MySQLMasterPos( '1E11FA47-71CA-11E1-9E33-C80AA9429562:1-100', $now ),
+ false,
false
],
// MariaDB GTID style
[
- new MySQLMasterPos( 'db1-bin.2', '1', '255-11-23' ),
- new MySQLMasterPos( 'db1-bin.2', '2', '255-11-24' ),
- true
+ new MySQLMasterPos( '255-11-23', $now ),
+ new MySQLMasterPos( '255-11-24', $now ),
+ true,
+ false
+ ],
+ [
+ new MySQLMasterPos( '255-11-99', $now ),
+ new MySQLMasterPos( '255-11-100', $now ),
+ true,
+ false
+ ],
+ [
+ new MySQLMasterPos( '255-11-999', $now ),
+ new MySQLMasterPos( '254-11-1000', $now ),
+ false,
+ false
+ ],
+ [
+ new MySQLMasterPos( '255-11-23,256-12-50', $now ),
+ new MySQLMasterPos( '255-11-24', $now ),
+ true,
+ false
+ ],
+ [
+ new MySQLMasterPos( '255-11-99,256-12-50,257-12-50', $now ),
+ new MySQLMasterPos( '255-11-1000', $now ),
+ true,
+ false
+ ],
+ [
+ new MySQLMasterPos( '255-11-23,256-12-50', $now ),
+ new MySQLMasterPos( '255-11-24,155-52-63', $now ),
+ true,
+ false
+ ],
+ [
+ new MySQLMasterPos( '255-11-99,256-12-50,257-12-50', $now ),
+ new MySQLMasterPos( '255-11-1000,256-12-51', $now ),
+ true,
+ false
],
[
- new MySQLMasterPos( 'db1-bin.2', '1', '255-11-99' ),
- new MySQLMasterPos( 'db1-bin.2', '2', '255-11-100' ),
+ new MySQLMasterPos( '255-11-99,256-12-50', $now ),
+ new MySQLMasterPos( '255-13-1000,256-14-49', $now ),
+ true,
true
],
[
- new MySQLMasterPos( 'db1-bin.2', '1', '255-11-999' ),
- new MySQLMasterPos( 'db1-bin.2', '2', '254-11-1000' ),
+ new MySQLMasterPos( '253-11-999,255-11-999', $now ),
+ new MySQLMasterPos( '254-11-1000', $now ),
+ false,
false
],
];
@@ -288,40 +283,77 @@ class DatabaseMysqlBaseTest extends PHPUnit_Framework_TestCase {
public function testChannelsMatch( MySQLMasterPos $pos1, MySQLMasterPos $pos2, $matches ) {
$this->assertEquals( $matches, $pos1->channelsMatch( $pos2 ) );
$this->assertEquals( $matches, $pos2->channelsMatch( $pos1 ) );
+
+ $roundtripPos = new MySQLMasterPos( (string)$pos1, 1 );
+ $this->assertEquals( (string)$pos1, (string)$roundtripPos );
}
public static function provideChannelPositions() {
+ $now = microtime( true );
+
return [
[
- new MySQLMasterPos( 'db1034-bin.000876', '44' ),
- new MySQLMasterPos( 'db1034-bin.000976', '74' ),
+ new MySQLMasterPos( 'db1034-bin.000876/44', $now ),
+ new MySQLMasterPos( 'db1034-bin.000976/74', $now ),
true
],
[
- new MySQLMasterPos( 'db1052-bin.000976', '999' ),
- new MySQLMasterPos( 'db1052-bin.000976', '1000' ),
+ new MySQLMasterPos( 'db1052-bin.000976/999', $now ),
+ new MySQLMasterPos( 'db1052-bin.000976/1000', $now ),
true
],
[
- new MySQLMasterPos( 'db1066-bin.000976', '9999' ),
- new MySQLMasterPos( 'db1035-bin.000976', '10000' ),
+ new MySQLMasterPos( 'db1066-bin.000976/9999', $now ),
+ new MySQLMasterPos( 'db1035-bin.000976/10000', $now ),
false
],
[
- new MySQLMasterPos( 'db1066-bin.000976', '9999' ),
- new MySQLMasterPos( 'trump2016.000976', '10000' ),
+ new MySQLMasterPos( 'db1066-bin.000976/9999', $now ),
+ new MySQLMasterPos( 'trump2016.000976/10000', $now ),
false
],
];
}
/**
+ * @dataProvider provideCommonDomainGTIDs
+ * @covers Wikimedia\Rdbms\MySQLMasterPos
+ */
+ public function testCommonGtidDomains( MySQLMasterPos $pos, MySQLMasterPos $ref, $gtids ) {
+ $this->assertEquals( $gtids, MySQLMasterPos::getCommonDomainGTIDs( $pos, $ref ) );
+ }
+
+ public static function provideCommonDomainGTIDs() {
+ return [
+ [
+ new MySQLMasterPos( '255-13-99,256-12-50,257-14-50', 1 ),
+ new MySQLMasterPos( '255-11-1000', 1 ),
+ [ '255-13-99' ]
+ ],
+ [
+ new MySQLMasterPos(
+ '2E11FA47-71CA-11E1-9E33-C80AA9429562:1-5,' .
+ '3E11FA47-71CA-11E1-9E33-C80AA9429562:20-99,' .
+ '7E11FA47-71CA-11E1-9E33-C80AA9429562:1-30',
+ 1
+ ),
+ new MySQLMasterPos(
+ '1E11FA47-71CA-11E1-9E33-C80AA9429562:30-100,' .
+ '3E11FA47-71CA-11E1-9E33-C80AA9429562:30-66',
+ 1
+ ),
+ [ '3E11FA47-71CA-11E1-9E33-C80AA9429562:20-99' ]
+ ]
+ ];
+ }
+
+ /**
* @dataProvider provideLagAmounts
* @covers Wikimedia\Rdbms\DatabaseMysqlBase::getLag
* @covers Wikimedia\Rdbms\DatabaseMysqlBase::getLagFromPtHeartbeat
*/
public function testPtHeartbeat( $lag ) {
- $db = $this->getMockBuilder( 'DatabaseMysqli' )
+ $db = $this->getMockBuilder( DatabaseMysqli::class )
->disableOriginalConstructor()
->setMethods( [
'getLagDetectionMethod', 'getHeartbeatData', 'getMasterServerInfo' ] )
@@ -368,4 +400,344 @@ class DatabaseMysqlBaseTest extends PHPUnit_Framework_TestCase {
[ 1000.77 ],
];
}
+
+ /**
+ * @dataProvider provideGtidData
+ * @covers Wikimedia\Rdbms\MySQLMasterPos
+ * @covers Wikimedia\Rdbms\DatabaseMysqlBase::getReplicaPos
+ * @covers Wikimedia\Rdbms\DatabaseMysqlBase::getMasterPos
+ */
+ public function testServerGtidTable( $gtable, $rBLtable, $mBLtable, $rGTIDs, $mGTIDs ) {
+ $db = $this->getMockBuilder( DatabaseMysqli::class )
+ ->disableOriginalConstructor()
+ ->setMethods( [
+ 'useGTIDs',
+ 'getServerGTIDs',
+ 'getServerRoleStatus',
+ 'getServerId',
+ 'getServerUUID'
+ ] )
+ ->getMock();
+
+ $db->method( 'useGTIDs' )->willReturn( true );
+ $db->method( 'getServerGTIDs' )->willReturn( $gtable );
+ $db->method( 'getServerRoleStatus' )->willReturnCallback(
+ function ( $role ) use ( $rBLtable, $mBLtable ) {
+ if ( $role === 'SLAVE' ) {
+ return $rBLtable;
+ } elseif ( $role === 'MASTER' ) {
+ return $mBLtable;
+ }
+
+ return null;
+ }
+ );
+ $db->method( 'getServerId' )->willReturn( 1 );
+ $db->method( 'getServerUUID' )->willReturn( '2E11FA47-71CA-11E1-9E33-C80AA9429562' );
+
+ if ( is_array( $rGTIDs ) ) {
+ $this->assertEquals( $rGTIDs, $db->getReplicaPos()->getGTIDs() );
+ } else {
+ $this->assertEquals( false, $db->getReplicaPos() );
+ }
+ if ( is_array( $mGTIDs ) ) {
+ $this->assertEquals( $mGTIDs, $db->getMasterPos()->getGTIDs() );
+ } else {
+ $this->assertEquals( false, $db->getMasterPos() );
+ }
+ }
+
+ public static function provideGtidData() {
+ return [
+ // MariaDB
+ [
+ [
+ 'gtid_domain_id' => 100,
+ 'gtid_current_pos' => '100-13-77',
+ 'gtid_binlog_pos' => '100-13-77',
+ 'gtid_slave_pos' => null // master
+ ],
+ [
+ 'Relay_Master_Log_File' => 'host.1600',
+ 'Exec_Master_Log_Pos' => '77'
+ ],
+ [
+ 'File' => 'host.1600',
+ 'Position' => '77'
+ ],
+ [],
+ [ '100' => '100-13-77' ]
+ ],
+ [
+ [
+ 'gtid_domain_id' => 100,
+ 'gtid_current_pos' => '100-13-77',
+ 'gtid_binlog_pos' => '100-13-77',
+ 'gtid_slave_pos' => '100-13-77' // replica
+ ],
+ [
+ 'Relay_Master_Log_File' => 'host.1600',
+ 'Exec_Master_Log_Pos' => '77'
+ ],
+ [],
+ [ '100' => '100-13-77' ],
+ [ '100' => '100-13-77' ]
+ ],
+ [
+ [
+ 'gtid_current_pos' => '100-13-77',
+ 'gtid_binlog_pos' => '100-13-77',
+ 'gtid_slave_pos' => '100-13-77' // replica
+ ],
+ [
+ 'Relay_Master_Log_File' => 'host.1600',
+ 'Exec_Master_Log_Pos' => '77'
+ ],
+ [],
+ [ '100' => '100-13-77' ],
+ [ '100' => '100-13-77' ]
+ ],
+ // MySQL
+ [
+ [
+ 'gtid_executed' => '2E11FA47-71CA-11E1-9E33-C80AA9429562:1-77'
+ ],
+ [
+ 'Relay_Master_Log_File' => 'host.1600',
+ 'Exec_Master_Log_Pos' => '77'
+ ],
+ [], // only a replica
+ [ '2E11FA47-71CA-11E1-9E33-C80AA9429562'
+ => '2E11FA47-71CA-11E1-9E33-C80AA9429562:1-77' ],
+ // replica/master use same var
+ [ '2E11FA47-71CA-11E1-9E33-C80AA9429562'
+ => '2E11FA47-71CA-11E1-9E33-C80AA9429562:1-77' ],
+ ],
+ [
+ [
+ 'gtid_executed' => '2E11FA47-71CA-11E1-9E33-C80AA9429562:1-49,' .
+ '2E11FA47-71CA-11E1-9E33-C80AA9429562:51-77'
+ ],
+ [
+ 'Relay_Master_Log_File' => 'host.1600',
+ 'Exec_Master_Log_Pos' => '77'
+ ],
+ [], // only a replica
+ [ '2E11FA47-71CA-11E1-9E33-C80AA9429562'
+ => '2E11FA47-71CA-11E1-9E33-C80AA9429562:51-77' ],
+ // replica/master use same var
+ [ '2E11FA47-71CA-11E1-9E33-C80AA9429562'
+ => '2E11FA47-71CA-11E1-9E33-C80AA9429562:51-77' ],
+ ],
+ [
+ [
+ 'gtid_executed' => null, // not enabled?
+ 'gtid_binlog_pos' => null
+ ],
+ [
+ 'Relay_Master_Log_File' => 'host.1600',
+ 'Exec_Master_Log_Pos' => '77'
+ ],
+ [], // only a replica
+ [], // binlog fallback
+ false
+ ],
+ [
+ [
+ 'gtid_executed' => null, // not enabled?
+ 'gtid_binlog_pos' => null
+ ],
+ [], // no replication
+ [], // no replication
+ false,
+ false
+ ]
+ ];
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\MySQLMasterPos
+ */
+ public function testSerialize() {
+ $pos = new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:99', 53636363 );
+ $roundtripPos = unserialize( serialize( $pos ) );
+
+ $this->assertEquals( $pos, $roundtripPos );
+
+ $pos = new MySQLMasterPos( '255-11-23', 53636363 );
+ $roundtripPos = unserialize( serialize( $pos ) );
+
+ $this->assertEquals( $pos, $roundtripPos );
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\DatabaseMysqlBase::isInsertSelectSafe
+ * @dataProvider provideInsertSelectCases
+ */
+ public function testInsertSelectIsSafe( $insertOpts, $selectOpts, $row, $safe ) {
+ $db = $this->getMockBuilder( DatabaseMysqli::class )
+ ->disableOriginalConstructor()
+ ->setMethods( [ 'getReplicationSafetyInfo' ] )
+ ->getMock();
+ $db->method( 'getReplicationSafetyInfo' )->willReturn( (object)$row );
+ $dbw = TestingAccessWrapper::newFromObject( $db );
+
+ $this->assertEquals( $safe, $dbw->isInsertSelectSafe( $insertOpts, $selectOpts ) );
+ }
+
+ public function provideInsertSelectCases() {
+ return [
+ [
+ [],
+ [],
+ [
+ 'innodb_autoinc_lock_mode' => '2',
+ 'binlog_format' => 'ROW',
+ ],
+ true
+ ],
+ [
+ [],
+ [ 'LIMIT' => 100 ],
+ [
+ 'innodb_autoinc_lock_mode' => '2',
+ 'binlog_format' => 'ROW',
+ ],
+ true
+ ],
+ [
+ [],
+ [ 'LIMIT' => 100 ],
+ [
+ 'innodb_autoinc_lock_mode' => '0',
+ 'binlog_format' => 'STATEMENT',
+ ],
+ false
+ ],
+ [
+ [],
+ [],
+ [
+ 'innodb_autoinc_lock_mode' => '2',
+ 'binlog_format' => 'STATEMENT',
+ ],
+ false
+ ],
+ [
+ [ 'NO_AUTO_COLUMNS' ],
+ [ 'LIMIT' => 100 ],
+ [
+ 'innodb_autoinc_lock_mode' => '0',
+ 'binlog_format' => 'STATEMENT',
+ ],
+ false
+ ],
+ [
+ [],
+ [],
+ [
+ 'innodb_autoinc_lock_mode' => 0,
+ 'binlog_format' => 'STATEMENT',
+ ],
+ true
+ ],
+ [
+ [ 'NO_AUTO_COLUMNS' ],
+ [],
+ [
+ 'innodb_autoinc_lock_mode' => 2,
+ 'binlog_format' => 'STATEMENT',
+ ],
+ true
+ ],
+ [
+ [ 'NO_AUTO_COLUMNS' ],
+ [],
+ [
+ 'innodb_autoinc_lock_mode' => 0,
+ 'binlog_format' => 'STATEMENT',
+ ],
+ true
+ ],
+
+ ];
+ }
+
+ /**
+ * @covers \Wikimedia\Rdbms\DatabaseMysqlBase::buildIntegerCast
+ */
+ public function testBuildIntegerCast() {
+ $db = $this->getMockBuilder( DatabaseMysqli::class )
+ ->disableOriginalConstructor()
+ ->setMethods( null )
+ ->getMock();
+ $output = $db->buildIntegerCast( 'fieldName' );
+ $this->assertSame( 'CAST( fieldName AS SIGNED )', $output );
+ }
+
+ /*
+ * @covers Wikimedia\Rdbms\Database::setIndexAliases
+ */
+ public function testIndexAliases() {
+ $db = $this->getMockBuilder( DatabaseMysqli::class )
+ ->disableOriginalConstructor()
+ ->setMethods( [ 'mysqlRealEscapeString' ] )
+ ->getMock();
+ $db->method( 'mysqlRealEscapeString' )->willReturnCallback(
+ function ( $s ) {
+ return str_replace( "'", "\\'", $s );
+ }
+ );
+
+ $db->setIndexAliases( [ 'a_b_idx' => 'a_c_idx' ] );
+ $sql = $db->selectSQLText(
+ 'zend', 'field', [ 'a' => 'x' ], __METHOD__, [ 'USE INDEX' => 'a_b_idx' ] );
+
+ $this->assertEquals(
+ "SELECT field FROM `zend` FORCE INDEX (a_c_idx) WHERE a = 'x' ",
+ $sql
+ );
+
+ $db->setIndexAliases( [] );
+ $sql = $db->selectSQLText(
+ 'zend', 'field', [ 'a' => 'x' ], __METHOD__, [ 'USE INDEX' => 'a_b_idx' ] );
+
+ $this->assertEquals(
+ "SELECT field FROM `zend` FORCE INDEX (a_b_idx) WHERE a = 'x' ",
+ $sql
+ );
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\Database::setTableAliases
+ */
+ public function testTableAliases() {
+ $db = $this->getMockBuilder( DatabaseMysqli::class )
+ ->disableOriginalConstructor()
+ ->setMethods( [ 'mysqlRealEscapeString' ] )
+ ->getMock();
+ $db->method( 'mysqlRealEscapeString' )->willReturnCallback(
+ function ( $s ) {
+ return str_replace( "'", "\\'", $s );
+ }
+ );
+
+ $db->setTableAliases( [
+ 'meow' => [ 'dbname' => 'feline', 'schema' => null, 'prefix' => 'cat_' ]
+ ] );
+ $sql = $db->selectSQLText( 'meow', 'field', [ 'a' => 'x' ], __METHOD__ );
+
+ $this->assertEquals(
+ "SELECT field FROM `feline`.`cat_meow` WHERE a = 'x' ",
+ $sql
+ );
+
+ $db->setTableAliases( [] );
+ $sql = $db->selectSQLText( 'meow', 'field', [ 'a' => 'x' ], __METHOD__ );
+
+ $this->assertEquals(
+ "SELECT field FROM `meow` WHERE a = 'x' ",
+ $sql
+ );
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php b/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php
index 7b841172..ab2f11b5 100644
--- a/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php
@@ -1,13 +1,23 @@
<?php
+use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\LikeMatch;
+use Wikimedia\Rdbms\Database;
+use Wikimedia\TestingAccessWrapper;
+use Wikimedia\Rdbms\DBTransactionStateError;
+use Wikimedia\Rdbms\DBUnexpectedError;
+use Wikimedia\Rdbms\DBTransactionError;
/**
* Test the parts of the Database abstract class that deal
* with creating SQL text.
*/
-class DatabaseSQLTest extends PHPUnit_Framework_TestCase {
- /** @var DatabaseTestHelper */
+class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+ use PHPUnit4And6Compat;
+
+ /** @var DatabaseTestHelper|Database */
private $database;
protected function setUp() {
@@ -63,6 +73,44 @@ class DatabaseSQLTest extends PHPUnit_Framework_TestCase {
],
[
[
+ 'tables' => 'table',
+ 'fields' => [ 'field', 'alias' => 'field2' ],
+ 'conds' => 'alias = \'text\'',
+ ],
+ "SELECT field,field2 AS alias " .
+ "FROM table " .
+ "WHERE alias = 'text'"
+ ],
+ [
+ [
+ 'tables' => 'table',
+ 'fields' => [ 'field', 'alias' => 'field2' ],
+ 'conds' => [],
+ ],
+ "SELECT field,field2 AS alias " .
+ "FROM table"
+ ],
+ [
+ [
+ 'tables' => 'table',
+ 'fields' => [ 'field', 'alias' => 'field2' ],
+ 'conds' => '',
+ ],
+ "SELECT field,field2 AS alias " .
+ "FROM table"
+ ],
+ [
+ [
+ 'tables' => 'table',
+ 'fields' => [ 'field', 'alias' => 'field2' ],
+ 'conds' => '0', // T188314
+ ],
+ "SELECT field,field2 AS alias " .
+ "FROM table " .
+ "WHERE 0"
+ ],
+ [
+ [
// 'tables' with space prepended indicates pre-escaped table name
'tables' => ' table LEFT JOIN table2',
'fields' => [ 'field' ],
@@ -199,6 +247,101 @@ class DatabaseSQLTest extends PHPUnit_Framework_TestCase {
}
/**
+ * @covers Wikimedia\Rdbms\Subquery
+ * @dataProvider provideSelectRowCount
+ * @param $sql
+ * @param $sqlText
+ */
+ public function testSelectRowCount( $sql, $sqlText ) {
+ $this->database->selectRowCount(
+ $sql['tables'],
+ $sql['field'],
+ isset( $sql['conds'] ) ? $sql['conds'] : [],
+ __METHOD__,
+ isset( $sql['options'] ) ? $sql['options'] : [],
+ isset( $sql['join_conds'] ) ? $sql['join_conds'] : []
+ );
+ $this->assertLastSql( $sqlText );
+ }
+
+ public static function provideSelectRowCount() {
+ return [
+ [
+ [
+ 'tables' => 'table',
+ 'field' => [ '*' ],
+ 'conds' => [ 'field' => 'text' ],
+ ],
+ "SELECT COUNT(*) AS rowcount FROM " .
+ "(SELECT 1 FROM table WHERE field = 'text' ) tmp_count"
+ ],
+ [
+ [
+ 'tables' => 'table',
+ 'field' => [ 'column' ],
+ 'conds' => [ 'field' => 'text' ],
+ ],
+ "SELECT COUNT(*) AS rowcount FROM " .
+ "(SELECT 1 FROM table WHERE field = 'text' AND (column IS NOT NULL) ) tmp_count"
+ ],
+ [
+ [
+ 'tables' => 'table',
+ 'field' => [ 'alias' => 'column' ],
+ 'conds' => [ 'field' => 'text' ],
+ ],
+ "SELECT COUNT(*) AS rowcount FROM " .
+ "(SELECT 1 FROM table WHERE field = 'text' AND (column IS NOT NULL) ) tmp_count"
+ ],
+ [
+ [
+ 'tables' => 'table',
+ 'field' => [ 'alias' => 'column' ],
+ 'conds' => '',
+ ],
+ "SELECT COUNT(*) AS rowcount FROM " .
+ "(SELECT 1 FROM table WHERE (column IS NOT NULL) ) tmp_count"
+ ],
+ [
+ [
+ 'tables' => 'table',
+ 'field' => [ 'alias' => 'column' ],
+ 'conds' => false,
+ ],
+ "SELECT COUNT(*) AS rowcount FROM " .
+ "(SELECT 1 FROM table WHERE (column IS NOT NULL) ) tmp_count"
+ ],
+ [
+ [
+ 'tables' => 'table',
+ 'field' => [ 'alias' => 'column' ],
+ 'conds' => null,
+ ],
+ "SELECT COUNT(*) AS rowcount FROM " .
+ "(SELECT 1 FROM table WHERE (column IS NOT NULL) ) tmp_count"
+ ],
+ [
+ [
+ 'tables' => 'table',
+ 'field' => [ 'alias' => 'column' ],
+ 'conds' => '1',
+ ],
+ "SELECT COUNT(*) AS rowcount FROM " .
+ "(SELECT 1 FROM table WHERE (1) AND (column IS NOT NULL) ) tmp_count"
+ ],
+ [
+ [
+ 'tables' => 'table',
+ 'field' => [ 'alias' => 'column' ],
+ 'conds' => '0',
+ ],
+ "SELECT COUNT(*) AS rowcount FROM " .
+ "(SELECT 1 FROM table WHERE (0) AND (column IS NOT NULL) ) tmp_count"
+ ],
+ ];
+ }
+
+ /**
* @dataProvider provideUpdate
* @covers Wikimedia\Rdbms\Database::update
* @covers Wikimedia\Rdbms\Database::makeUpdateOptions
@@ -454,7 +597,7 @@ class DatabaseSQLTest extends PHPUnit_Framework_TestCase {
isset( $sql['selectOptions'] ) ? $sql['selectOptions'] : [],
isset( $sql['selectJoinConds'] ) ? $sql['selectJoinConds'] : []
);
- $this->assertLastSqlDb( implode( '; ', [ $sqlSelect, $sqlInsert ] ), $dbWeb );
+ $this->assertLastSqlDb( implode( '; ', [ $sqlSelect, 'BEGIN', $sqlInsert, 'COMMIT' ] ), $dbWeb );
}
public static function provideInsertSelect() {
@@ -515,6 +658,7 @@ class DatabaseSQLTest extends PHPUnit_Framework_TestCase {
'srcTable' => [ 'select_table1', 'select_table2' ],
'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ],
'conds' => [ 'field' => 2 ],
+ 'insertOptions' => [ 'NO_AUTO_COLUMNS' ],
'selectOptions' => [ 'ORDER BY' => 'field', 'FORCE INDEX' => [ 'select_table1' => 'index1' ] ],
'selectJoinConds' => [
'select_table2' => [ 'LEFT JOIN', [ 'select_table1.foo = select_table2.bar' ] ],
@@ -534,6 +678,30 @@ class DatabaseSQLTest extends PHPUnit_Framework_TestCase {
];
}
+ public function testInsertSelectBatching() {
+ $dbWeb = new DatabaseTestHelper( __CLASS__, [ 'cliMode' => false ] );
+ $rows = [];
+ for ( $i = 0; $i <= 25000; $i++ ) {
+ $rows[] = [ 'field' => $i ];
+ }
+ $dbWeb->forceNextResult( $rows );
+ $dbWeb->insertSelect(
+ 'insert_table',
+ 'select_table',
+ [ 'field' => 'field2' ],
+ '*',
+ __METHOD__
+ );
+ $this->assertLastSqlDb( implode( '; ', [
+ 'SELECT field2 AS field FROM select_table WHERE * FOR UPDATE',
+ 'BEGIN',
+ "INSERT INTO insert_table (field) VALUES ('" . implode( "'),('", range( 0, 9999 ) ) . "')",
+ "INSERT INTO insert_table (field) VALUES ('" . implode( "'),('", range( 10000, 19999 ) ) . "')",
+ "INSERT INTO insert_table (field) VALUES ('" . implode( "'),('", range( 20000, 25000 ) ) . "')",
+ 'COMMIT'
+ ] ), $dbWeb );
+ }
+
/**
* @dataProvider provideReplace
* @covers Wikimedia\Rdbms\Database::replace
@@ -556,11 +724,11 @@ class DatabaseSQLTest extends PHPUnit_Framework_TestCase {
'uniqueIndexes' => [ 'field' ],
'rows' => [ 'field' => 'text', 'field2' => 'text2' ],
],
- "DELETE FROM replace_table " .
- "WHERE ( field='text' ); " .
+ "BEGIN; DELETE FROM replace_table " .
+ "WHERE (field = 'text'); " .
"INSERT INTO replace_table " .
"(field,field2) " .
- "VALUES ('text','text2')"
+ "VALUES ('text','text2'); COMMIT"
],
[
[
@@ -572,11 +740,11 @@ class DatabaseSQLTest extends PHPUnit_Framework_TestCase {
'md_deps' => 'deps',
],
],
- "DELETE FROM module_deps " .
- "WHERE ( md_module='module' AND md_skin='skin' ); " .
+ "BEGIN; DELETE FROM module_deps " .
+ "WHERE (md_module = 'module' AND md_skin = 'skin'); " .
"INSERT INTO module_deps " .
"(md_module,md_skin,md_deps) " .
- "VALUES ('module','skin','deps')"
+ "VALUES ('module','skin','deps'); COMMIT"
],
[
[
@@ -594,16 +762,16 @@ class DatabaseSQLTest extends PHPUnit_Framework_TestCase {
],
],
],
- "DELETE FROM module_deps " .
- "WHERE ( md_module='module' AND md_skin='skin' ); " .
+ "BEGIN; DELETE FROM module_deps " .
+ "WHERE (md_module = 'module' AND md_skin = 'skin'); " .
"INSERT INTO module_deps " .
"(md_module,md_skin,md_deps) " .
"VALUES ('module','skin','deps'); " .
"DELETE FROM module_deps " .
- "WHERE ( md_module='module2' AND md_skin='skin2' ); " .
+ "WHERE (md_module = 'module2' AND md_skin = 'skin2'); " .
"INSERT INTO module_deps " .
"(md_module,md_skin,md_deps) " .
- "VALUES ('module2','skin2','deps2')"
+ "VALUES ('module2','skin2','deps2'); COMMIT"
],
[
[
@@ -621,16 +789,16 @@ class DatabaseSQLTest extends PHPUnit_Framework_TestCase {
],
],
],
- "DELETE FROM module_deps " .
- "WHERE ( md_module='module' ) OR ( md_skin='skin' ); " .
+ "BEGIN; DELETE FROM module_deps " .
+ "WHERE (md_module = 'module') OR (md_skin = 'skin'); " .
"INSERT INTO module_deps " .
"(md_module,md_skin,md_deps) " .
"VALUES ('module','skin','deps'); " .
"DELETE FROM module_deps " .
- "WHERE ( md_module='module2' ) OR ( md_skin='skin2' ); " .
+ "WHERE (md_module = 'module2') OR (md_skin = 'skin2'); " .
"INSERT INTO module_deps " .
"(md_module,md_skin,md_deps) " .
- "VALUES ('module2','skin2','deps2')"
+ "VALUES ('module2','skin2','deps2'); COMMIT"
],
[
[
@@ -642,9 +810,9 @@ class DatabaseSQLTest extends PHPUnit_Framework_TestCase {
'md_deps' => 'deps',
],
],
- "INSERT INTO module_deps " .
+ "BEGIN; INSERT INTO module_deps " .
"(md_module,md_skin,md_deps) " .
- "VALUES ('module','skin','deps')"
+ "VALUES ('module','skin','deps'); COMMIT"
],
];
}
@@ -843,8 +1011,8 @@ class DatabaseSQLTest extends PHPUnit_Framework_TestCase {
}
public static function provideUnionConditionPermutations() {
+ // phpcs:disable Generic.Files.LineLength
return [
- // @codingStandardsIgnoreStart Generic.Files.LineLength.TooLong
[
[
'table' => [ 'table1', 'table2' ],
@@ -986,8 +1154,8 @@ class DatabaseSQLTest extends PHPUnit_Framework_TestCase {
],
"SELECT foo_id FROM foo WHERE baz IS NULL ORDER BY foo_id LIMIT 150,25"
],
- // @codingStandardsIgnoreEnd
];
+ // phpcs:enable
}
/**
@@ -1148,4 +1316,752 @@ class DatabaseSQLTest extends PHPUnit_Framework_TestCase {
$this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
$this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
}
+
+ public function provideBuildSubstring() {
+ yield [ 'someField', 1, 2, 'SUBSTRING(someField FROM 1 FOR 2)' ];
+ yield [ 'someField', 1, null, 'SUBSTRING(someField FROM 1)' ];
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\Database::buildSubstring
+ * @dataProvider provideBuildSubstring
+ */
+ public function testBuildSubstring( $input, $start, $length, $expected ) {
+ $output = $this->database->buildSubstring( $input, $start, $length );
+ $this->assertSame( $expected, $output );
+ }
+
+ public function provideBuildSubstring_invalidParams() {
+ yield [ -1, 1 ];
+ yield [ 1, -1 ];
+ yield [ 1, 'foo' ];
+ yield [ 'foo', 1 ];
+ yield [ null, 1 ];
+ yield [ 0, 1 ];
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\Database::buildSubstring
+ * @covers Wikimedia\Rdbms\Database::assertBuildSubstringParams
+ * @dataProvider provideBuildSubstring_invalidParams
+ */
+ public function testBuildSubstring_invalidParams( $start, $length ) {
+ $this->setExpectedException( InvalidArgumentException::class );
+ $this->database->buildSubstring( 'foo', $start, $length );
+ }
+
+ /**
+ * @covers \Wikimedia\Rdbms\Database::buildIntegerCast
+ */
+ public function testBuildIntegerCast() {
+ $output = $this->database->buildIntegerCast( 'fieldName' );
+ $this->assertSame( 'CAST( fieldName AS INTEGER )', $output );
+ }
+
+ /**
+ * @covers \Wikimedia\Rdbms\Database::doSavepoint
+ * @covers \Wikimedia\Rdbms\Database::doReleaseSavepoint
+ * @covers \Wikimedia\Rdbms\Database::doRollbackToSavepoint
+ * @covers \Wikimedia\Rdbms\Database::startAtomic
+ * @covers \Wikimedia\Rdbms\Database::endAtomic
+ * @covers \Wikimedia\Rdbms\Database::cancelAtomic
+ * @covers \Wikimedia\Rdbms\Database::doAtomicSection
+ */
+ public function testAtomicSections() {
+ $this->database->startAtomic( __METHOD__ );
+ $this->database->endAtomic( __METHOD__ );
+ $this->assertLastSql( 'BEGIN; COMMIT' );
+
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->assertLastSql( 'BEGIN; ROLLBACK' );
+
+ $this->database->begin( __METHOD__ );
+ $this->database->startAtomic( __METHOD__ );
+ $this->database->endAtomic( __METHOD__ );
+ $this->database->commit( __METHOD__ );
+ $this->assertLastSql( 'BEGIN; COMMIT' );
+
+ $this->database->begin( __METHOD__ );
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->endAtomic( __METHOD__ );
+ $this->database->commit( __METHOD__ );
+ // phpcs:ignore Generic.Files.LineLength
+ $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; RELEASE SAVEPOINT wikimedia_rdbms_atomic1; COMMIT' );
+
+ $this->database->begin( __METHOD__ );
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->database->commit( __METHOD__ );
+ // phpcs:ignore Generic.Files.LineLength
+ $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT' );
+
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->database->endAtomic( __METHOD__ );
+ // phpcs:ignore Generic.Files.LineLength
+ $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT' );
+
+ $noOpCallack = function () {
+ };
+
+ $this->database->doAtomicSection( __METHOD__, $noOpCallack, IDatabase::ATOMIC_CANCELABLE );
+ $this->assertLastSql( 'BEGIN; COMMIT' );
+
+ $this->database->doAtomicSection( __METHOD__, $noOpCallack );
+ $this->assertLastSql( 'BEGIN; COMMIT' );
+
+ $this->database->begin( __METHOD__ );
+ $this->database->doAtomicSection( __METHOD__, $noOpCallack, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->rollback( __METHOD__ );
+ // phpcs:ignore Generic.Files.LineLength
+ $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; RELEASE SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK' );
+
+ $fname = __METHOD__;
+ $triggerMap = [
+ '-' => '-',
+ IDatabase::TRIGGER_COMMIT => 'tCommit',
+ IDatabase::TRIGGER_ROLLBACK => 'tRollback'
+ ];
+ $callback1 = function ( $trigger = '-' ) use ( $fname, $triggerMap ) {
+ $this->database->query( "SELECT 1, {$triggerMap[$trigger]} AS t", $fname );
+ };
+ $callback2 = function ( $trigger = '-' ) use ( $fname, $triggerMap ) {
+ $this->database->query( "SELECT 2, {$triggerMap[$trigger]} AS t", $fname );
+ };
+ $callback3 = function ( $trigger = '-' ) use ( $fname, $triggerMap ) {
+ $this->database->query( "SELECT 3, {$triggerMap[$trigger]} AS t", $fname );
+ };
+
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->onTransactionPreCommitOrIdle( $callback1, __METHOD__ );
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->assertLastSql( 'BEGIN; ROLLBACK' );
+
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->onTransactionIdle( $callback1, __METHOD__ );
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->assertLastSql( 'BEGIN; ROLLBACK' );
+
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->onTransactionResolution( $callback1, __METHOD__ );
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->assertLastSql( 'BEGIN; ROLLBACK; SELECT 1, tRollback AS t' );
+
+ $this->database->startAtomic( __METHOD__ . '_outer' );
+ $this->database->onTransactionPreCommitOrIdle( $callback1, __METHOD__ );
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->database->onTransactionPreCommitOrIdle( $callback3, __METHOD__ );
+ $this->database->endAtomic( __METHOD__ . '_outer' );
+ $this->assertLastSql( implode( "; ", [
+ 'BEGIN',
+ 'SAVEPOINT wikimedia_rdbms_atomic1',
+ 'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1',
+ 'SELECT 1, - AS t',
+ 'SELECT 3, - AS t',
+ 'COMMIT'
+ ] ) );
+
+ $this->database->startAtomic( __METHOD__ . '_outer' );
+ $this->database->onTransactionIdle( $callback1, __METHOD__ );
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->onTransactionIdle( $callback2, __METHOD__ );
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->database->onTransactionIdle( $callback3, __METHOD__ );
+ $this->database->endAtomic( __METHOD__ . '_outer' );
+ $this->assertLastSql( implode( "; ", [
+ 'BEGIN',
+ 'SAVEPOINT wikimedia_rdbms_atomic1',
+ 'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1',
+ 'COMMIT',
+ 'SELECT 1, tCommit AS t',
+ 'SELECT 3, tCommit AS t'
+ ] ) );
+
+ $this->database->startAtomic( __METHOD__ . '_outer' );
+ $this->database->onTransactionResolution( $callback1, __METHOD__ );
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->onTransactionResolution( $callback2, __METHOD__ );
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->database->onTransactionResolution( $callback3, __METHOD__ );
+ $this->database->endAtomic( __METHOD__ . '_outer' );
+ $this->assertLastSql( implode( "; ", [
+ 'BEGIN',
+ 'SAVEPOINT wikimedia_rdbms_atomic1',
+ 'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1',
+ 'COMMIT',
+ 'SELECT 1, tCommit AS t',
+ 'SELECT 2, tRollback AS t',
+ 'SELECT 3, tCommit AS t'
+ ] ) );
+
+ $makeCallback = function ( $id ) use ( $fname, $triggerMap ) {
+ return function ( $trigger = '-' ) use ( $id, $fname, $triggerMap ) {
+ $this->database->query( "SELECT $id, {$triggerMap[$trigger]} AS t", $fname );
+ };
+ };
+
+ $this->database->startAtomic( __METHOD__ . '_outer' );
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->onTransactionResolution( $makeCallback( 1 ), __METHOD__ );
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->database->endAtomic( __METHOD__ . '_outer' );
+ $this->assertLastSql( implode( "; ", [
+ 'BEGIN',
+ 'SAVEPOINT wikimedia_rdbms_atomic1',
+ 'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1',
+ 'COMMIT',
+ 'SELECT 1, tRollback AS t'
+ ] ) );
+
+ $this->database->startAtomic( __METHOD__ . '_level1', IDatabase::ATOMIC_CANCELABLE );
+ $this->database->onTransactionResolution( $makeCallback( 1 ), __METHOD__ );
+ $this->database->startAtomic( __METHOD__ . '_level2' );
+ $this->database->startAtomic( __METHOD__ . '_level3', IDatabase::ATOMIC_CANCELABLE );
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->onTransactionResolution( $makeCallback( 2 ), __METHOD__ );
+ $this->database->endAtomic( __METHOD__ );
+ $this->database->onTransactionResolution( $makeCallback( 3 ), __METHOD__ );
+ $this->database->cancelAtomic( __METHOD__ . '_level3' );
+ $this->database->endAtomic( __METHOD__ . '_level2' );
+ $this->database->onTransactionResolution( $makeCallback( 4 ), __METHOD__ );
+ $this->database->endAtomic( __METHOD__ . '_level1' );
+ $this->assertLastSql( implode( "; ", [
+ 'BEGIN',
+ 'SAVEPOINT wikimedia_rdbms_atomic1',
+ 'SAVEPOINT wikimedia_rdbms_atomic2',
+ 'RELEASE SAVEPOINT wikimedia_rdbms_atomic2',
+ 'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1',
+ 'COMMIT; SELECT 1, tCommit AS t',
+ 'SELECT 2, tRollback AS t',
+ 'SELECT 3, tRollback AS t',
+ 'SELECT 4, tCommit AS t'
+ ] ) );
+ }
+
+ /**
+ * @covers \Wikimedia\Rdbms\Database::doSavepoint
+ * @covers \Wikimedia\Rdbms\Database::doReleaseSavepoint
+ * @covers \Wikimedia\Rdbms\Database::doRollbackToSavepoint
+ * @covers \Wikimedia\Rdbms\Database::startAtomic
+ * @covers \Wikimedia\Rdbms\Database::endAtomic
+ * @covers \Wikimedia\Rdbms\Database::cancelAtomic
+ * @covers \Wikimedia\Rdbms\Database::doAtomicSection
+ */
+ public function testAtomicSectionsRecovery() {
+ $this->database->begin( __METHOD__ );
+ try {
+ $this->database->doAtomicSection(
+ __METHOD__,
+ function () {
+ $this->database->startAtomic( 'inner_func1' );
+ $this->database->startAtomic( 'inner_func2' );
+
+ throw new RuntimeException( 'Test exception' );
+ },
+ IDatabase::ATOMIC_CANCELABLE
+ );
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( RuntimeException $ex ) {
+ $this->assertSame( 'Test exception', $ex->getMessage() );
+ }
+ $this->database->commit( __METHOD__ );
+ // phpcs:ignore Generic.Files.LineLength
+ $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT' );
+
+ $this->database->begin( __METHOD__ );
+ try {
+ $this->database->doAtomicSection(
+ __METHOD__,
+ function () {
+ throw new RuntimeException( 'Test exception' );
+ }
+ );
+ $this->fail( 'Test exception not thrown' );
+ } catch ( RuntimeException $ex ) {
+ $this->assertSame( 'Test exception', $ex->getMessage() );
+ }
+ try {
+ $this->database->commit( __METHOD__ );
+ $this->fail( 'Test exception not thrown' );
+ } catch ( DBTransactionError $ex ) {
+ $this->assertSame(
+ 'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR.',
+ $ex->getMessage()
+ );
+ }
+ $this->database->rollback( __METHOD__ );
+ $this->assertLastSql( 'BEGIN; ROLLBACK' );
+ }
+
+ /**
+ * @covers \Wikimedia\Rdbms\Database::doSavepoint
+ * @covers \Wikimedia\Rdbms\Database::doReleaseSavepoint
+ * @covers \Wikimedia\Rdbms\Database::doRollbackToSavepoint
+ * @covers \Wikimedia\Rdbms\Database::startAtomic
+ * @covers \Wikimedia\Rdbms\Database::endAtomic
+ * @covers \Wikimedia\Rdbms\Database::cancelAtomic
+ * @covers \Wikimedia\Rdbms\Database::doAtomicSection
+ */
+ public function testAtomicSectionsCallbackCancellation() {
+ $fname = __METHOD__;
+ $callback1Called = null;
+ $callback1 = function ( $trigger = '-' ) use ( $fname, &$callback1Called ) {
+ $callback1Called = $trigger;
+ $this->database->query( "SELECT 1", $fname );
+ };
+ $callback2Called = null;
+ $callback2 = function ( $trigger = '-' ) use ( $fname, &$callback2Called ) {
+ $callback2Called = $trigger;
+ $this->database->query( "SELECT 2", $fname );
+ };
+ $callback3Called = null;
+ $callback3 = function ( $trigger = '-' ) use ( $fname, &$callback3Called ) {
+ $callback3Called = $trigger;
+ $this->database->query( "SELECT 3", $fname );
+ };
+
+ $this->database->startAtomic( __METHOD__ . '_outer' );
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->startAtomic( __METHOD__ . '_inner' );
+ $this->database->onTransactionIdle( $callback1, __METHOD__ );
+ $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
+ $this->database->onTransactionResolution( $callback3, __METHOD__ );
+ $this->database->endAtomic( __METHOD__ . '_inner' );
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->database->endAtomic( __METHOD__ . '_outer' );
+ $this->assertNull( $callback1Called );
+ $this->assertNull( $callback2Called );
+ $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+ // phpcs:ignore Generic.Files.LineLength
+ $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT; SELECT 3' );
+
+ $callback1Called = null;
+ $callback2Called = null;
+ $callback3Called = null;
+ $this->database->startAtomic( __METHOD__ . '_outer' );
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->startAtomic( __METHOD__ . '_inner', IDatabase::ATOMIC_CANCELABLE );
+ $this->database->onTransactionIdle( $callback1, __METHOD__ );
+ $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
+ $this->database->onTransactionResolution( $callback3, __METHOD__ );
+ $this->database->endAtomic( __METHOD__ . '_inner' );
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->database->endAtomic( __METHOD__ . '_outer' );
+ $this->assertNull( $callback1Called );
+ $this->assertNull( $callback2Called );
+ $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+ // phpcs:ignore Generic.Files.LineLength
+ $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; SAVEPOINT wikimedia_rdbms_atomic2; RELEASE SAVEPOINT wikimedia_rdbms_atomic2; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT; SELECT 3' );
+
+ $callback1Called = null;
+ $callback2Called = null;
+ $callback3Called = null;
+ $this->database->startAtomic( __METHOD__ . '_outer' );
+ $atomicId = $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->startAtomic( __METHOD__ . '_inner' );
+ $this->database->onTransactionIdle( $callback1, __METHOD__ );
+ $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
+ $this->database->onTransactionResolution( $callback3, __METHOD__ );
+ $this->database->cancelAtomic( __METHOD__, $atomicId );
+ $this->database->endAtomic( __METHOD__ . '_outer' );
+ $this->assertNull( $callback1Called );
+ $this->assertNull( $callback2Called );
+ $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+
+ $callback1Called = null;
+ $callback2Called = null;
+ $callback3Called = null;
+ $this->database->startAtomic( __METHOD__ . '_outer' );
+ $atomicId = $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->startAtomic( __METHOD__ . '_inner' );
+ $this->database->onTransactionIdle( $callback1, __METHOD__ );
+ $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
+ $this->database->onTransactionResolution( $callback3, __METHOD__ );
+ try {
+ $this->database->cancelAtomic( __METHOD__ . '_X', $atomicId );
+ } catch ( DBUnexpectedError $e ) {
+ $m = __METHOD__;
+ $this->assertSame(
+ "Invalid atomic section ended (got {$m}_X but expected {$m}).",
+ $e->getMessage()
+ );
+ }
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->database->endAtomic( __METHOD__ . '_outer' );
+ $this->assertNull( $callback1Called );
+ $this->assertNull( $callback2Called );
+ $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+
+ $this->database->startAtomic( __METHOD__ . '_outer' );
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->startAtomic( __METHOD__ . '_inner' );
+ $this->database->onTransactionIdle( $callback1, __METHOD__ );
+ $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
+ $this->database->onTransactionResolution( $callback3, __METHOD__ );
+ $this->database->cancelAtomic( __METHOD__ . '_inner' );
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->database->endAtomic( __METHOD__ . '_outer' );
+ $this->assertNull( $callback1Called );
+ $this->assertNull( $callback2Called );
+ $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+
+ $wrapper = TestingAccessWrapper::newFromObject( $this->database );
+ $callback1Called = null;
+ $callback2Called = null;
+ $callback3Called = null;
+ $this->database->startAtomic( __METHOD__ . '_outer' );
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->startAtomic( __METHOD__ . '_inner' );
+ $this->database->onTransactionIdle( $callback1, __METHOD__ );
+ $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
+ $this->database->onTransactionResolution( $callback3, __METHOD__ );
+ $wrapper->trxStatus = Database::STATUS_TRX_ERROR;
+ $this->database->cancelAtomic( __METHOD__ . '_inner' );
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->database->endAtomic( __METHOD__ . '_outer' );
+ $this->assertNull( $callback1Called );
+ $this->assertNull( $callback2Called );
+ $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+ }
+
+ /**
+ * @covers \Wikimedia\Rdbms\Database::doSavepoint
+ * @covers \Wikimedia\Rdbms\Database::doReleaseSavepoint
+ * @covers \Wikimedia\Rdbms\Database::doRollbackToSavepoint
+ * @covers \Wikimedia\Rdbms\Database::startAtomic
+ * @covers \Wikimedia\Rdbms\Database::endAtomic
+ * @covers \Wikimedia\Rdbms\Database::cancelAtomic
+ * @covers \Wikimedia\Rdbms\Database::doAtomicSection
+ */
+ public function testAtomicSectionsTrxRound() {
+ $this->database->setFlag( IDatabase::DBO_TRX );
+ $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+ $this->database->query( 'SELECT 1', __METHOD__ );
+ $this->database->endAtomic( __METHOD__ );
+ $this->database->commit( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
+ // phpcs:ignore Generic.Files.LineLength
+ $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; SELECT 1; RELEASE SAVEPOINT wikimedia_rdbms_atomic1; COMMIT' );
+ }
+
+ public static function provideAtomicSectionMethodsForErrors() {
+ return [
+ [ 'endAtomic' ],
+ [ 'cancelAtomic' ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideAtomicSectionMethodsForErrors
+ * @covers \Wikimedia\Rdbms\Database::endAtomic
+ * @covers \Wikimedia\Rdbms\Database::cancelAtomic
+ */
+ public function testNoAtomicSection( $method ) {
+ try {
+ $this->database->$method( __METHOD__ );
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( DBUnexpectedError $ex ) {
+ $this->assertSame(
+ 'No atomic section is open (got ' . __METHOD__ . ').',
+ $ex->getMessage()
+ );
+ }
+ }
+
+ /**
+ * @dataProvider provideAtomicSectionMethodsForErrors
+ * @covers \Wikimedia\Rdbms\Database::endAtomic
+ * @covers \Wikimedia\Rdbms\Database::cancelAtomic
+ */
+ public function testInvalidAtomicSectionEnded( $method ) {
+ $this->database->startAtomic( __METHOD__ . 'X' );
+ try {
+ $this->database->$method( __METHOD__ );
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( DBUnexpectedError $ex ) {
+ $this->assertSame(
+ 'Invalid atomic section ended (got ' . __METHOD__ . ' but expected ' .
+ __METHOD__ . 'X' . ').',
+ $ex->getMessage()
+ );
+ }
+ }
+
+ /**
+ * @covers \Wikimedia\Rdbms\Database::cancelAtomic
+ */
+ public function testUncancellableAtomicSection() {
+ $this->database->startAtomic( __METHOD__ );
+ try {
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->database->select( 'test', '1', [], __METHOD__ );
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( DBTransactionError $ex ) {
+ $this->assertSame(
+ 'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR.',
+ $ex->getMessage()
+ );
+ }
+ }
+
+ /**
+ * @expectedException \Wikimedia\Rdbms\DBTransactionStateError
+ */
+ public function testTransactionErrorState1() {
+ $wrapper = TestingAccessWrapper::newFromObject( $this->database );
+
+ $this->database->begin( __METHOD__ );
+ $wrapper->trxStatus = Database::STATUS_TRX_ERROR;
+ $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ );
+ $this->database->commit( __METHOD__ );
+ }
+
+ /**
+ * @covers \Wikimedia\Rdbms\Database::query
+ */
+ public function testTransactionErrorState2() {
+ $wrapper = TestingAccessWrapper::newFromObject( $this->database );
+
+ $this->database->startAtomic( __METHOD__ );
+ $wrapper->trxStatus = Database::STATUS_TRX_ERROR;
+ $this->database->rollback( __METHOD__ );
+ $this->assertEquals( 0, $this->database->trxLevel() );
+ $this->assertEquals( Database::STATUS_TRX_NONE, $wrapper->trxStatus() );
+ $this->assertLastSql( 'BEGIN; ROLLBACK' );
+
+ $this->database->startAtomic( __METHOD__ );
+ $this->assertEquals( Database::STATUS_TRX_OK, $wrapper->trxStatus() );
+ $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
+ $this->database->endAtomic( __METHOD__ );
+ $this->assertEquals( Database::STATUS_TRX_NONE, $wrapper->trxStatus() );
+ $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'1\'; COMMIT' );
+ $this->assertEquals( 0, $this->database->trxLevel(), 'Use after rollback()' );
+
+ $this->database->begin( __METHOD__ );
+ $this->database->startAtomic( __METHOD__, Database::ATOMIC_CANCELABLE );
+ $this->database->update( 'y', [ 'a' => 1 ], [ 'field' => 1 ], __METHOD__ );
+ $wrapper->trxStatus = Database::STATUS_TRX_ERROR;
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->assertEquals( Database::STATUS_TRX_OK, $wrapper->trxStatus() );
+ $this->database->startAtomic( __METHOD__ );
+ $this->database->delete( 'y', [ 'field' => 1 ], __METHOD__ );
+ $this->database->endAtomic( __METHOD__ );
+ $this->database->commit( __METHOD__ );
+ // phpcs:ignore Generic.Files.LineLength
+ $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; UPDATE y SET a = \'1\' WHERE field = \'1\'; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; DELETE FROM y WHERE field = \'1\'; COMMIT' );
+ $this->assertEquals( 0, $this->database->trxLevel(), 'Use after rollback()' );
+
+ // Next transaction
+ $this->database->startAtomic( __METHOD__ );
+ $this->assertEquals( Database::STATUS_TRX_OK, $wrapper->trxStatus() );
+ $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ );
+ $this->database->endAtomic( __METHOD__ );
+ $this->assertEquals( Database::STATUS_TRX_NONE, $wrapper->trxStatus() );
+ $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; COMMIT' );
+ $this->assertEquals( 0, $this->database->trxLevel() );
+ }
+
+ /**
+ * @covers \Wikimedia\Rdbms\Database::query
+ */
+ public function testImplicitTransactionRollback() {
+ $doError = function () {
+ $this->database->forceNextQueryError( 666, 'Evilness' );
+ try {
+ $this->database->delete( 'error', '1', __CLASS__ . '::SomeCaller' );
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( DBError $e ) {
+ $this->assertSame( 666, $e->errno );
+ }
+ };
+
+ $this->database->setFlag( Database::DBO_TRX );
+
+ // Implicit transaction gets silently rolled back
+ $this->database->begin( __METHOD__, Database::TRANSACTION_INTERNAL );
+ call_user_func( $doError );
+ $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
+ $this->database->commit( __METHOD__, Database::FLUSHING_INTERNAL );
+ // phpcs:ignore
+ $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; ROLLBACK; BEGIN; DELETE FROM x WHERE field = \'1\'; COMMIT' );
+
+ // ... unless there were prior writes
+ $this->database->begin( __METHOD__, Database::TRANSACTION_INTERNAL );
+ $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
+ call_user_func( $doError );
+ try {
+ $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( DBTransactionStateError $e ) {
+ }
+ $this->database->rollback( __METHOD__, Database::FLUSHING_INTERNAL );
+ // phpcs:ignore
+ $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'1\'; DELETE FROM error WHERE 1; ROLLBACK' );
+ }
+
+ /**
+ * @covers \Wikimedia\Rdbms\Database::query
+ */
+ public function testTransactionStatementRollbackIgnoring() {
+ $wrapper = TestingAccessWrapper::newFromObject( $this->database );
+ $warning = [];
+ $wrapper->deprecationLogger = function ( $msg ) use ( &$warning ) {
+ $warning[] = $msg;
+ };
+
+ $doError = function () {
+ $this->database->forceNextQueryError( 666, 'Evilness', [
+ 'wasKnownStatementRollbackError' => true,
+ ] );
+ try {
+ $this->database->delete( 'error', '1', __CLASS__ . '::SomeCaller' );
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( DBError $e ) {
+ $this->assertSame( 666, $e->errno );
+ }
+ };
+ $expectWarning = 'Caller from ' . __METHOD__ .
+ ' ignored an error originally raised from ' . __CLASS__ . '::SomeCaller: [666] Evilness';
+
+ // Rollback doesn't raise a warning
+ $warning = [];
+ $this->database->startAtomic( __METHOD__ );
+ call_user_func( $doError );
+ $this->database->rollback( __METHOD__ );
+ $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
+ $this->assertSame( [], $warning );
+ // phpcs:ignore
+ $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; ROLLBACK; DELETE FROM x WHERE field = \'1\'' );
+
+ // cancelAtomic() doesn't raise a warning
+ $warning = [];
+ $this->database->begin( __METHOD__ );
+ $this->database->startAtomic( __METHOD__, Database::ATOMIC_CANCELABLE );
+ call_user_func( $doError );
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
+ $this->database->commit( __METHOD__ );
+ $this->assertSame( [], $warning );
+ // phpcs:ignore
+ $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; DELETE FROM error WHERE 1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; DELETE FROM x WHERE field = \'1\'; COMMIT' );
+
+ // Commit does raise a warning
+ $warning = [];
+ $this->database->begin( __METHOD__ );
+ call_user_func( $doError );
+ $this->database->commit( __METHOD__ );
+ $this->assertSame( [ $expectWarning ], $warning );
+ $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; COMMIT' );
+
+ // Deprecation only gets raised once
+ $warning = [];
+ $this->database->begin( __METHOD__ );
+ call_user_func( $doError );
+ $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
+ $this->database->commit( __METHOD__ );
+ $this->assertSame( [ $expectWarning ], $warning );
+ // phpcs:ignore
+ $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; DELETE FROM x WHERE field = \'1\'; COMMIT' );
+ }
+
+ /**
+ * @covers \Wikimedia\Rdbms\Database::close
+ */
+ public function testPrematureClose1() {
+ $fname = __METHOD__;
+ $this->database->begin( __METHOD__ );
+ $this->database->onTransactionIdle( function () use ( $fname ) {
+ $this->database->query( 'SELECT 1', $fname );
+ } );
+ $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ );
+ $this->database->close();
+
+ $this->assertFalse( $this->database->isOpen() );
+ $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; COMMIT; SELECT 1' );
+ $this->assertEquals( 0, $this->database->trxLevel() );
+ }
+
+ /**
+ * @covers \Wikimedia\Rdbms\Database::close
+ */
+ public function testPrematureClose2() {
+ try {
+ $fname = __METHOD__;
+ $this->database->startAtomic( __METHOD__ );
+ $this->database->onTransactionIdle( function () use ( $fname ) {
+ $this->database->query( 'SELECT 1', $fname );
+ } );
+ $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ );
+ $this->database->close();
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( DBUnexpectedError $ex ) {
+ $this->assertSame(
+ 'Wikimedia\Rdbms\Database::close: atomic sections ' .
+ 'DatabaseSQLTest::testPrematureClose2 are still open.',
+ $ex->getMessage()
+ );
+ }
+
+ $this->assertFalse( $this->database->isOpen() );
+ $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; ROLLBACK' );
+ $this->assertEquals( 0, $this->database->trxLevel() );
+ }
+
+ /**
+ * @covers \Wikimedia\Rdbms\Database::close
+ */
+ public function testPrematureClose3() {
+ try {
+ $this->database->setFlag( IDatabase::DBO_TRX );
+ $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ );
+ $this->assertEquals( 1, $this->database->trxLevel() );
+ $this->database->close();
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( DBUnexpectedError $ex ) {
+ $this->assertSame(
+ 'Wikimedia\Rdbms\Database::close: ' .
+ 'mass commit/rollback of peer transaction required (DBO_TRX set).',
+ $ex->getMessage()
+ );
+ }
+
+ $this->assertFalse( $this->database->isOpen() );
+ $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; ROLLBACK' );
+ $this->assertEquals( 0, $this->database->trxLevel() );
+ }
+
+ /**
+ * @covers \Wikimedia\Rdbms\Database::close
+ */
+ public function testPrematureClose4() {
+ $this->database->setFlag( IDatabase::DBO_TRX );
+ $this->database->query( 'SELECT 1', __METHOD__ );
+ $this->assertEquals( 1, $this->database->trxLevel() );
+ $this->database->close();
+ $this->database->clearFlag( IDatabase::DBO_TRX );
+
+ $this->assertFalse( $this->database->isOpen() );
+ $this->assertLastSql( 'BEGIN; SELECT 1; COMMIT' );
+ $this->assertEquals( 0, $this->database->trxLevel() );
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\Database::selectFieldValues()
+ */
+ public function testSelectFieldValues() {
+ $this->database->forceNextResult( [
+ (object)[ 'value' => 'row1' ],
+ (object)[ 'value' => 'row2' ],
+ (object)[ 'value' => 'row3' ],
+ ] );
+
+ $this->assertSame(
+ [ 'row1', 'row2', 'row3' ],
+ $this->database->selectFieldValues( 'table', 'table.field', 'conds', __METHOD__ )
+ );
+ $this->assertLastSql( 'SELECT table.field AS value FROM table WHERE conds' );
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseSqliteRdbmsTest.php b/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseSqliteRdbmsTest.php
new file mode 100644
index 00000000..a886d6bf
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseSqliteRdbmsTest.php
@@ -0,0 +1,60 @@
+<?php
+
+use Wikimedia\Rdbms\DatabaseSqlite;
+
+/**
+ * DatabaseSqliteTest is already defined in mediawiki core hence the 'Rdbms' included in this
+ * class name.
+ * The test in core should have mediawiki specific stuff removed and the tests moved to this
+ * rdbms libs test.
+ */
+class DatabaseSqliteRdbmsTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+ use PHPUnit4And6Compat;
+
+ /**
+ * @return PHPUnit_Framework_MockObject_MockObject|DatabaseSqlite
+ */
+ private function getMockDb() {
+ return $this->getMockBuilder( DatabaseSqlite::class )
+ ->disableOriginalConstructor()
+ ->setMethods( null )
+ ->getMock();
+ }
+
+ public function provideBuildSubstring() {
+ yield [ 'someField', 1, 2, 'SUBSTR(someField,1,2)' ];
+ yield [ 'someField', 1, null, 'SUBSTR(someField,1)' ];
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\DatabaseSqlite::buildSubstring
+ * @dataProvider provideBuildSubstring
+ */
+ public function testBuildSubstring( $input, $start, $length, $expected ) {
+ $dbMock = $this->getMockDb();
+ $output = $dbMock->buildSubstring( $input, $start, $length );
+ $this->assertSame( $expected, $output );
+ }
+
+ public function provideBuildSubstring_invalidParams() {
+ yield [ -1, 1 ];
+ yield [ 1, -1 ];
+ yield [ 1, 'foo' ];
+ yield [ 'foo', 1 ];
+ yield [ null, 1 ];
+ yield [ 0, 1 ];
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\DatabaseSqlite::buildSubstring
+ * @dataProvider provideBuildSubstring_invalidParams
+ */
+ public function testBuildSubstring_invalidParams( $start, $length ) {
+ $dbMock = $this->getMockDb();
+ $this->setExpectedException( InvalidArgumentException::class );
+ $dbMock->buildSubstring( 'foo', $start, $length );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php b/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php
index 70b6c360..444a946e 100644
--- a/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php
@@ -1,16 +1,46 @@
<?php
+use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\DatabaseMysqli;
use Wikimedia\Rdbms\LBFactorySingle;
use Wikimedia\Rdbms\TransactionProfiler;
use Wikimedia\TestingAccessWrapper;
+use Wikimedia\Rdbms\DatabaseSqlite;
+use Wikimedia\Rdbms\DatabasePostgres;
+use Wikimedia\Rdbms\DatabaseMssql;
-class DatabaseTest extends PHPUnit_Framework_TestCase {
+class DatabaseTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
protected function setUp() {
$this->db = new DatabaseTestHelper( __CLASS__ . '::' . $this->getName() );
}
+ /**
+ * @dataProvider provideAddQuotes
+ * @covers Wikimedia\Rdbms\Database::factory
+ */
+ public function testFactory() {
+ $m = Database::NEW_UNCONNECTED; // no-connect mode
+ $p = [ 'host' => 'localhost', 'user' => 'me', 'password' => 'myself', 'dbname' => 'i' ];
+
+ $this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'mysqli', $p, $m ) );
+ $this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'MySqli', $p, $m ) );
+ $this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'MySQLi', $p, $m ) );
+ $this->assertInstanceOf( DatabasePostgres::class, Database::factory( 'postgres', $p, $m ) );
+ $this->assertInstanceOf( DatabasePostgres::class, Database::factory( 'Postgres', $p, $m ) );
+
+ $x = $p + [ 'port' => 10000, 'UseWindowsAuth' => false ];
+ $this->assertInstanceOf( DatabaseMssql::class, Database::factory( 'mssql', $x, $m ) );
+
+ $x = $p + [ 'dbFilePath' => 'some/file.sqlite' ];
+ $this->assertInstanceOf( DatabaseSqlite::class, Database::factory( 'sqlite', $x, $m ) );
+ $x = $p + [ 'dbDirectory' => 'some/file' ];
+ $this->assertInstanceOf( DatabaseSqlite::class, Database::factory( 'sqlite', $x, $m ) );
+ }
+
public static function provideAddQuotes() {
return [
[ null, 'NULL' ],
@@ -94,6 +124,52 @@ class DatabaseTest extends PHPUnit_Framework_TestCase {
);
}
+ public function provideTableNamesWithIndexClauseOrJOIN() {
+ return [
+ 'one-element array' => [
+ [ 'table' ], [], 'table '
+ ],
+ 'comma join' => [
+ [ 'table1', 'table2' ], [], 'table1,table2 '
+ ],
+ 'real join' => [
+ [ 'table1', 'table2' ],
+ [ 'table2' => [ 'LEFT JOIN', 't1_id = t2_id' ] ],
+ 'table1 LEFT JOIN table2 ON ((t1_id = t2_id))'
+ ],
+ 'real join with multiple conditionals' => [
+ [ 'table1', 'table2' ],
+ [ 'table2' => [ 'LEFT JOIN', [ 't1_id = t2_id', 't2_x = \'X\'' ] ] ],
+ 'table1 LEFT JOIN table2 ON ((t1_id = t2_id) AND (t2_x = \'X\'))'
+ ],
+ 'join with parenthesized group' => [
+ [ 'table1', 'n' => [ 'table2', 'table3' ] ],
+ [
+ 'table3' => [ 'JOIN', 't2_id = t3_id' ],
+ 'n' => [ 'LEFT JOIN', 't1_id = t2_id' ],
+ ],
+ 'table1 LEFT JOIN (table2 JOIN table3 ON ((t2_id = t3_id))) ON ((t1_id = t2_id))'
+ ],
+ 'join with degenerate parenthesized group' => [
+ [ 'table1', 'n' => [ 't2' => 'table2' ] ],
+ [
+ 'n' => [ 'LEFT JOIN', 't1_id = t2_id' ],
+ ],
+ 'table1 LEFT JOIN table2 t2 ON ((t1_id = t2_id))'
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideTableNamesWithIndexClauseOrJOIN
+ * @covers Wikimedia\Rdbms\Database::tableNamesWithIndexClauseOrJOIN
+ */
+ public function testTableNamesWithIndexClauseOrJOIN( $tables, $join_conds, $expect ) {
+ $clause = TestingAccessWrapper::newFromObject( $this->db )
+ ->tableNamesWithIndexClauseOrJOIN( $tables, [], [], $join_conds );
+ $this->assertSame( $expect, $clause );
+ }
+
/**
* @covers Wikimedia\Rdbms\Database::onTransactionIdle
* @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
@@ -101,28 +177,27 @@ class DatabaseTest extends PHPUnit_Framework_TestCase {
public function testTransactionIdle() {
$db = $this->db;
- $db->setFlag( DBO_TRX );
+ $db->clearFlag( DBO_TRX );
$called = false;
$flagSet = null;
- $db->onTransactionIdle(
- function () use ( $db, &$flagSet, &$called ) {
- $called = true;
- $flagSet = $db->getFlag( DBO_TRX );
- },
- __METHOD__
- );
- $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
- $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
+ $callback = function () use ( $db, &$flagSet, &$called ) {
+ $called = true;
+ $flagSet = $db->getFlag( DBO_TRX );
+ };
+
+ $db->onTransactionIdle( $callback, __METHOD__ );
$this->assertTrue( $called, 'Callback reached' );
+ $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
+ $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX still default' );
- $db->clearFlag( DBO_TRX );
$flagSet = null;
- $db->onTransactionIdle(
- function () use ( $db, &$flagSet ) {
- $flagSet = $db->getFlag( DBO_TRX );
- },
- __METHOD__
- );
+ $called = false;
+ $db->startAtomic( __METHOD__ );
+ $db->onTransactionIdle( $callback, __METHOD__ );
+ $this->assertFalse( $called, 'Callback not reached during TRX' );
+ $db->endAtomic( __METHOD__ );
+
+ $this->assertTrue( $called, 'Callback reached after COMMIT' );
$this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
$this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
@@ -137,6 +212,56 @@ class DatabaseTest extends PHPUnit_Framework_TestCase {
}
/**
+ * @covers Wikimedia\Rdbms\Database::onTransactionIdle
+ * @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
+ */
+ public function testTransactionIdle_TRX() {
+ $db = $this->getMockDB( [ 'isOpen', 'ping' ] );
+ $db->method( 'isOpen' )->willReturn( true );
+ $db->method( 'ping' )->willReturn( true );
+ $db->setFlag( DBO_TRX );
+
+ $lbFactory = LBFactorySingle::newFromConnection( $db );
+ // Ask for the connection so that LB sets internal state
+ // about this connection being the master connection
+ $lb = $lbFactory->getMainLB();
+ $conn = $lb->openConnection( $lb->getWriterIndex() );
+ $this->assertSame( $db, $conn, 'Same DB instance' );
+ $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX is set' );
+
+ $called = false;
+ $flagSet = null;
+ $callback = function () use ( $db, &$flagSet, &$called ) {
+ $called = true;
+ $flagSet = $db->getFlag( DBO_TRX );
+ };
+
+ $db->onTransactionIdle( $callback, __METHOD__ );
+ $this->assertTrue( $called, 'Called when idle if DBO_TRX is set' );
+ $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
+ $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX still default' );
+
+ $called = false;
+ $lbFactory->beginMasterChanges( __METHOD__ );
+ $db->onTransactionIdle( $callback, __METHOD__ );
+ $this->assertFalse( $called, 'Not called when lb-transaction is active' );
+
+ $lbFactory->commitMasterChanges( __METHOD__ );
+ $this->assertTrue( $called, 'Called when lb-transaction is committed' );
+
+ $called = false;
+ $lbFactory->beginMasterChanges( __METHOD__ );
+ $db->onTransactionIdle( $callback, __METHOD__ );
+ $this->assertFalse( $called, 'Not called when lb-transaction is active' );
+
+ $lbFactory->rollbackMasterChanges( __METHOD__ );
+ $this->assertFalse( $called, 'Not called when lb-transaction is rolled back' );
+
+ $lbFactory->commitMasterChanges( __METHOD__ );
+ $this->assertFalse( $called, 'Not called in next round commit' );
+ }
+
+ /**
* @covers Wikimedia\Rdbms\Database::onTransactionPreCommitOrIdle
* @covers Wikimedia\Rdbms\Database::runOnTransactionPreCommitCallbacks
*/
@@ -174,31 +299,47 @@ class DatabaseTest extends PHPUnit_Framework_TestCase {
* @covers Wikimedia\Rdbms\Database::runOnTransactionPreCommitCallbacks
*/
public function testTransactionPreCommitOrIdle_TRX() {
- $db = $this->getMockDB( [ 'isOpen' ] );
+ $db = $this->getMockDB( [ 'isOpen', 'ping' ] );
$db->method( 'isOpen' )->willReturn( true );
+ $db->method( 'ping' )->willReturn( true );
$db->setFlag( DBO_TRX );
$lbFactory = LBFactorySingle::newFromConnection( $db );
- // Ask for the connectin so that LB sets internal state
+ // Ask for the connection so that LB sets internal state
// about this connection being the master connection
$lb = $lbFactory->getMainLB();
$conn = $lb->openConnection( $lb->getWriterIndex() );
$this->assertSame( $db, $conn, 'Same DB instance' );
+
+ $this->assertFalse( $lb->hasMasterChanges() );
$this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX is set' );
+ $called = false;
+ $callback = function () use ( &$called ) {
+ $called = true;
+ };
+ $db->onTransactionPreCommitOrIdle( $callback, __METHOD__ );
+ $this->assertTrue( $called, 'Called when idle if DBO_TRX is set' );
+ $called = false;
+ $lbFactory->commitMasterChanges();
+ $this->assertFalse( $called );
$called = false;
- $db->onTransactionPreCommitOrIdle(
- function () use ( &$called ) {
- $called = true;
- }
- );
- $this->assertFalse( $called, 'Not called when idle if DBO_TRX is set' );
+ $lbFactory->beginMasterChanges( __METHOD__ );
+ $db->onTransactionPreCommitOrIdle( $callback, __METHOD__ );
+ $this->assertFalse( $called, 'Not called when lb-transaction is active' );
+ $lbFactory->commitMasterChanges( __METHOD__ );
+ $this->assertTrue( $called, 'Called when lb-transaction is committed' );
+ $called = false;
$lbFactory->beginMasterChanges( __METHOD__ );
+ $db->onTransactionPreCommitOrIdle( $callback, __METHOD__ );
$this->assertFalse( $called, 'Not called when lb-transaction is active' );
+ $lbFactory->rollbackMasterChanges( __METHOD__ );
+ $this->assertFalse( $called, 'Not called when lb-transaction is rolled back' );
+
$lbFactory->commitMasterChanges( __METHOD__ );
- $this->assertTrue( $called, 'Called when lb-transaction is committed' );
+ $this->assertFalse( $called, 'Not called in next round commit' );
}
/**
@@ -274,7 +415,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase {
*/
private function getMockDB( $methods = [] ) {
static $abstractMethods = [
- 'affectedRows',
+ 'fetchAffectedRowCount',
'closeConnection',
'dataSeek',
'doQuery',
@@ -322,10 +463,34 @@ class DatabaseTest extends PHPUnit_Framework_TestCase {
$this->assertFalse( (bool)$db->trxLevel(), "Transaction cleared." );
}
+ /**
+ * @covers Wikimedia\Rdbms\Database::getScopedLockAndFlush
+ * @covers Wikimedia\Rdbms\Database::lock
+ * @covers Wikimedia\Rdbms\Database::unlock
+ * @covers Wikimedia\Rdbms\Database::lockIsFree
+ */
public function testGetScopedLock() {
$db = $this->getMockDB( [ 'isOpen' ] );
$db->method( 'isOpen' )->willReturn( true );
+ $this->assertEquals( 0, $db->trxLevel() );
+ $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
+ $this->assertEquals( true, $db->lock( 'x', __METHOD__ ) );
+ $this->assertEquals( false, $db->lockIsFree( 'x', __METHOD__ ) );
+ $this->assertEquals( true, $db->unlock( 'x', __METHOD__ ) );
+ $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
+ $this->assertEquals( 0, $db->trxLevel() );
+
+ $db->setFlag( DBO_TRX );
+ $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
+ $this->assertEquals( true, $db->lock( 'x', __METHOD__ ) );
+ $this->assertEquals( false, $db->lockIsFree( 'x', __METHOD__ ) );
+ $this->assertEquals( true, $db->unlock( 'x', __METHOD__ ) );
+ $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
+ $db->clearFlag( DBO_TRX );
+
+ $this->assertEquals( 0, $db->trxLevel() );
+
$db->setFlag( DBO_TRX );
try {
$this->badLockingMethodImplicit( $db );
@@ -398,6 +563,32 @@ class DatabaseTest extends PHPUnit_Framework_TestCase {
}
/**
+ * @expectedException UnexpectedValueException
+ * @covers Wikimedia\Rdbms\Database::setFlag
+ */
+ public function testDBOIgnoreSet() {
+ $db = $this->getMockBuilder( DatabaseMysqli::class )
+ ->disableOriginalConstructor()
+ ->setMethods( null )
+ ->getMock();
+
+ $db->setFlag( Database::DBO_IGNORE );
+ }
+
+ /**
+ * @expectedException UnexpectedValueException
+ * @covers Wikimedia\Rdbms\Database::clearFlag
+ */
+ public function testDBOIgnoreClear() {
+ $db = $this->getMockBuilder( DatabaseMysqli::class )
+ ->disableOriginalConstructor()
+ ->setMethods( null )
+ ->getMock();
+
+ $db->clearFlag( Database::DBO_IGNORE );
+ }
+
+ /**
* @covers Wikimedia\Rdbms\Database::tablePrefix
* @covers Wikimedia\Rdbms\Database::dbSchema
*/
@@ -418,4 +609,5 @@ class DatabaseTest extends PHPUnit_Framework_TestCase {
$this->db->dbSchema( $old );
$this->assertNotEquals( 'xxx', $this->db->dbSchema() );
}
+
}
diff --git a/www/wiki/tests/phpunit/includes/libs/xmp/XMPTest.php b/www/wiki/tests/phpunit/includes/libs/xmp/XMPTest.php
index 514e6cdd..73fd4716 100644
--- a/www/wiki/tests/phpunit/includes/libs/xmp/XMPTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/xmp/XMPTest.php
@@ -4,7 +4,9 @@
* @group Media
* @covers XMPReader
*/
-class XMPTest extends PHPUnit_Framework_TestCase {
+class XMPTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
protected function setUp() {
parent::setUp();
diff --git a/www/wiki/tests/phpunit/includes/libs/xmp/XMPValidateTest.php b/www/wiki/tests/phpunit/includes/libs/xmp/XMPValidateTest.php
index 7f7ea930..746f68ac 100644
--- a/www/wiki/tests/phpunit/includes/libs/xmp/XMPValidateTest.php
+++ b/www/wiki/tests/phpunit/includes/libs/xmp/XMPValidateTest.php
@@ -5,7 +5,9 @@ use Psr\Log\NullLogger;
/**
* @group Media
*/
-class XMPValidateTest extends PHPUnit_Framework_TestCase {
+class XMPValidateTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
/**
* @dataProvider provideDates
diff --git a/www/wiki/tests/phpunit/includes/linkeddata/PageDataRequestHandlerTest.php b/www/wiki/tests/phpunit/includes/linkeddata/PageDataRequestHandlerTest.php
index 2b18b08b..ad0c3d1e 100644
--- a/www/wiki/tests/phpunit/includes/linkeddata/PageDataRequestHandlerTest.php
+++ b/www/wiki/tests/phpunit/includes/linkeddata/PageDataRequestHandlerTest.php
@@ -2,10 +2,7 @@
/**
* @covers PageDataRequestHandler
- *
* @group PageData
- *
- * @license GPL-2.0+
*/
class PageDataRequestHandlerTest extends \MediaWikiTestCase {
diff --git a/www/wiki/tests/phpunit/includes/logging/BlockLogFormatterTest.php b/www/wiki/tests/phpunit/includes/logging/BlockLogFormatterTest.php
index 4158ea23..03671ac8 100644
--- a/www/wiki/tests/phpunit/includes/logging/BlockLogFormatterTest.php
+++ b/www/wiki/tests/phpunit/includes/logging/BlockLogFormatterTest.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @covers BlockLogFormatter
+ */
class BlockLogFormatterTest extends LogFormatterTestCase {
/**
diff --git a/www/wiki/tests/phpunit/includes/logging/ContentModelLogFormatterTest.php b/www/wiki/tests/phpunit/includes/logging/ContentModelLogFormatterTest.php
new file mode 100644
index 00000000..17e54115
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/logging/ContentModelLogFormatterTest.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @covers ContentModelLogFormatter
+ */
+class ContentModelLogFormatterTest extends LogFormatterTestCase {
+ public static function provideContentModelLogDatabaseRows() {
+ return [
+ [
+ [
+ 'type' => 'contentmodel',
+ 'action' => 'new',
+ 'comment' => 'new content model comment',
+ 'namespace' => NS_MAIN,
+ 'title' => 'ContentModelPage',
+ 'params' => [
+ '5::newModel' => 'testcontentmodel',
+ ],
+ ],
+ [
+ 'text' => 'User created the page ContentModelPage ' .
+ 'using a non-default content model ' .
+ '"testcontentmodel"',
+ 'api' => [
+ 'newModel' => 'testcontentmodel',
+ ],
+ ],
+ ],
+ [
+ [
+ 'type' => 'contentmodel',
+ 'action' => 'change',
+ 'comment' => 'change content model comment',
+ 'namespace' => NS_MAIN,
+ 'title' => 'ContentModelPage',
+ 'params' => [
+ '4::oldmodel' => 'wikitext',
+ '5::newModel' => 'testcontentmodel',
+ ],
+ ],
+ [
+ 'text' => 'User changed the content model of the page ' .
+ 'ContentModelPage from "wikitext" to ' .
+ '"testcontentmodel"',
+ 'api' => [
+ 'oldmodel' => 'wikitext',
+ 'newModel' => 'testcontentmodel',
+ ],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideContentModelLogDatabaseRows
+ */
+ public function testContentModelLogDatabaseRows( $row, $extra ) {
+ $this->doTestLogFormatter( $row, $extra );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/logging/DatabaseLogEntryTest.php b/www/wiki/tests/phpunit/includes/logging/DatabaseLogEntryTest.php
new file mode 100644
index 00000000..4af1742e
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/logging/DatabaseLogEntryTest.php
@@ -0,0 +1,162 @@
+<?php
+
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IDatabase;
+
+class DatabaseLogEntryTest extends MediaWikiTestCase {
+ public function setUp() {
+ parent::setUp();
+
+ // These services cache their joins
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'CommentStore' );
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'ActorMigration' );
+ }
+
+ public function tearDown() {
+ parent::tearDown();
+
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'CommentStore' );
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'ActorMigration' );
+ }
+
+ /**
+ * @covers DatabaseLogEntry::newFromId
+ * @covers DatabaseLogEntry::getSelectQueryData
+ *
+ * @dataProvider provideNewFromId
+ *
+ * @param int $id
+ * @param array $selectFields
+ * @param string[]|null $row
+ * @param string[]|null $expectedFields
+ * @param string $migration
+ */
+ public function testNewFromId( $id,
+ array $selectFields,
+ array $row = null,
+ array $expectedFields = null,
+ $migration
+ ) {
+ $this->setMwGlobals( [
+ 'wgCommentTableSchemaMigrationStage' => $migration,
+ 'wgActorTableSchemaMigrationStage' => $migration,
+ ] );
+
+ $row = $row ? (object)$row : null;
+ $db = $this->getMock( IDatabase::class );
+ $db->expects( self::once() )
+ ->method( 'selectRow' )
+ ->with( $selectFields['tables'],
+ $selectFields['fields'],
+ $selectFields['conds'],
+ 'DatabaseLogEntry::newFromId',
+ $selectFields['options'],
+ $selectFields['join_conds']
+ )
+ ->will( self::returnValue( $row ) );
+
+ /** @var IDatabase $db */
+ $logEntry = DatabaseLogEntry::newFromId( $id, $db );
+
+ if ( !$expectedFields ) {
+ self::assertNull( $logEntry, "Expected no log entry returned for id=$id" );
+ } else {
+ self::assertEquals( $id, $logEntry->getId() );
+ self::assertEquals( $expectedFields['type'], $logEntry->getType() );
+ self::assertEquals( $expectedFields['comment'], $logEntry->getComment() );
+ }
+ }
+
+ public function provideNewFromId() {
+ $oldTables = [
+ 'tables' => [ 'logging', 'user' ],
+ 'fields' => [
+ 'log_id',
+ 'log_type',
+ 'log_action',
+ 'log_timestamp',
+ 'log_namespace',
+ 'log_title',
+ 'log_params',
+ 'log_deleted',
+ 'user_id',
+ 'user_name',
+ 'user_editcount',
+ 'log_comment_text' => 'log_comment',
+ 'log_comment_data' => 'NULL',
+ 'log_comment_cid' => 'NULL',
+ 'log_user' => 'log_user',
+ 'log_user_text' => 'log_user_text',
+ 'log_actor' => 'NULL',
+ ],
+ 'options' => [],
+ 'join_conds' => [ 'user' => [ 'LEFT JOIN', 'user_id=log_user' ] ],
+ ];
+ $newTables = [
+ 'tables' => [
+ 'logging',
+ 'user',
+ 'comment_log_comment' => 'comment',
+ 'actor_log_user' => 'actor'
+ ],
+ 'fields' => [
+ 'log_id',
+ 'log_type',
+ 'log_action',
+ 'log_timestamp',
+ 'log_namespace',
+ 'log_title',
+ 'log_params',
+ 'log_deleted',
+ 'user_id',
+ 'user_name',
+ 'user_editcount',
+ 'log_comment_text' => 'comment_log_comment.comment_text',
+ 'log_comment_data' => 'comment_log_comment.comment_data',
+ 'log_comment_cid' => 'comment_log_comment.comment_id',
+ 'log_user' => 'actor_log_user.actor_user',
+ 'log_user_text' => 'actor_log_user.actor_name',
+ 'log_actor' => 'log_actor',
+ ],
+ 'options' => [],
+ 'join_conds' => [
+ 'user' => [ 'LEFT JOIN', 'user_id=actor_log_user.actor_user' ],
+ 'comment_log_comment' => [ 'JOIN', 'comment_log_comment.comment_id = log_comment_id' ],
+ 'actor_log_user' => [ 'JOIN', 'actor_log_user.actor_id = log_actor' ],
+ ],
+ ];
+ return [
+ [
+ 0,
+ $oldTables + [ 'conds' => [ 'log_id' => 0 ] ],
+ null,
+ null,
+ MIGRATION_OLD,
+ ],
+ [
+ 123,
+ $oldTables + [ 'conds' => [ 'log_id' => 123 ] ],
+ [
+ 'log_id' => 123,
+ 'log_type' => 'foobarize',
+ 'log_comment_text' => 'test!',
+ 'log_comment_data' => null,
+ ],
+ [ 'type' => 'foobarize', 'comment' => 'test!' ],
+ MIGRATION_OLD,
+ ],
+ [
+ 567,
+ $newTables + [ 'conds' => [ 'log_id' => 567 ] ],
+ [
+ 'log_id' => 567,
+ 'log_type' => 'foobarize',
+ 'log_comment_text' => 'test!',
+ 'log_comment_data' => null,
+ ],
+ [ 'type' => 'foobarize', 'comment' => 'test!' ],
+ MIGRATION_NEW,
+ ],
+ ];
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/logging/DeleteLogFormatterTest.php b/www/wiki/tests/phpunit/includes/logging/DeleteLogFormatterTest.php
index 23378999..0e6855d9 100644
--- a/www/wiki/tests/phpunit/includes/logging/DeleteLogFormatterTest.php
+++ b/www/wiki/tests/phpunit/includes/logging/DeleteLogFormatterTest.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @covers DeleteLogFormatter
+ */
class DeleteLogFormatterTest extends LogFormatterTestCase {
/**
diff --git a/www/wiki/tests/phpunit/includes/logging/ImportLogFormatterTest.php b/www/wiki/tests/phpunit/includes/logging/ImportLogFormatterTest.php
index ec120780..80e4c0bc 100644
--- a/www/wiki/tests/phpunit/includes/logging/ImportLogFormatterTest.php
+++ b/www/wiki/tests/phpunit/includes/logging/ImportLogFormatterTest.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @covers ImportLogFormatter
+ */
class ImportLogFormatterTest extends LogFormatterTestCase {
/**
diff --git a/www/wiki/tests/phpunit/includes/logging/LogFormatterTest.php b/www/wiki/tests/phpunit/includes/logging/LogFormatterTest.php
index 1ef3df6c..e523a31e 100644
--- a/www/wiki/tests/phpunit/includes/logging/LogFormatterTest.php
+++ b/www/wiki/tests/phpunit/includes/logging/LogFormatterTest.php
@@ -53,8 +53,8 @@ class LogFormatterTest extends MediaWikiLangTestCase {
$this->setMwGlobals( [
'wgLogTypes' => [ 'phpunit' ],
- 'wgLogActionsHandlers' => [ 'phpunit/test' => 'LogFormatter',
- 'phpunit/param' => 'LogFormatter' ],
+ 'wgLogActionsHandlers' => [ 'phpunit/test' => LogFormatter::class,
+ 'phpunit/param' => LogFormatter::class ],
'wgUser' => User::newFromName( 'Testuser' ),
] );
@@ -308,6 +308,10 @@ class LogFormatterTest extends MediaWikiLangTestCase {
'key_ns' => NS_PROJECT,
'key_title' => Title::newFromText( 'project:foo' )->getFullText(),
] ],
+ [ '4:title-link:key', '<invalid>', [
+ 'key_ns' => NS_SPECIAL,
+ 'key_title' => SpecialPage::getTitleFor( 'Badtitle', '<invalid>' )->getFullText(),
+ ] ],
[ '4:user:key', 'foo', [ 'key' => 'Foo' ] ],
[ '4:user-link:key', 'foo', [ 'key' => 'Foo' ] ],
];
diff --git a/www/wiki/tests/phpunit/includes/logging/LogFormatterTestCase.php b/www/wiki/tests/phpunit/includes/logging/LogFormatterTestCase.php
index 2dc9a2cf..786d7619 100644
--- a/www/wiki/tests/phpunit/includes/logging/LogFormatterTestCase.php
+++ b/www/wiki/tests/phpunit/includes/logging/LogFormatterTestCase.php
@@ -36,6 +36,7 @@ abstract class LogFormatterTestCase extends MediaWikiLangTestCase {
'log_timestamp' => isset( $data['timestamp'] ) ? $data['timestamp'] : wfTimestampNow(),
'log_user' => isset( $data['user'] ) ? $data['user'] : 0,
'log_user_text' => isset( $data['user_text'] ) ? $data['user_text'] : 'User',
+ 'log_actor' => isset( $data['actor'] ) ? $data['actor'] : 0,
'log_namespace' => isset( $data['namespace'] ) ? $data['namespace'] : NS_MAIN,
'log_title' => isset( $data['title'] ) ? $data['title'] : 'Main_Page',
'log_page' => isset( $data['page'] ) ? $data['page'] : 0,
diff --git a/www/wiki/tests/phpunit/includes/logging/MergeLogFormatterTest.php b/www/wiki/tests/phpunit/includes/logging/MergeLogFormatterTest.php
index 8b9abe42..1978f1b5 100644
--- a/www/wiki/tests/phpunit/includes/logging/MergeLogFormatterTest.php
+++ b/www/wiki/tests/phpunit/includes/logging/MergeLogFormatterTest.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @covers MergeLogFormatter
+ */
class MergeLogFormatterTest extends LogFormatterTestCase {
/**
diff --git a/www/wiki/tests/phpunit/includes/logging/MoveLogFormatterTest.php b/www/wiki/tests/phpunit/includes/logging/MoveLogFormatterTest.php
index 3433a6a4..ebda46b2 100644
--- a/www/wiki/tests/phpunit/includes/logging/MoveLogFormatterTest.php
+++ b/www/wiki/tests/phpunit/includes/logging/MoveLogFormatterTest.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @covers MoveLogFormatter
+ */
class MoveLogFormatterTest extends LogFormatterTestCase {
/**
diff --git a/www/wiki/tests/phpunit/includes/logging/NewUsersLogFormatterTest.php b/www/wiki/tests/phpunit/includes/logging/NewUsersLogFormatterTest.php
index 333fd88d..eee2981c 100644
--- a/www/wiki/tests/phpunit/includes/logging/NewUsersLogFormatterTest.php
+++ b/www/wiki/tests/phpunit/includes/logging/NewUsersLogFormatterTest.php
@@ -1,6 +1,7 @@
<?php
/**
+ * @covers NewUsersLogFormatter
* @group Database
*/
class NewUsersLogFormatterTest extends LogFormatterTestCase {
@@ -10,11 +11,11 @@ class NewUsersLogFormatterTest extends LogFormatterTestCase {
// Register LogHandler, see $wgNewUserLog in Setup.php
$this->mergeMwGlobalArrayValue( 'wgLogActionsHandlers', [
- 'newusers/newusers' => 'NewUsersLogFormatter',
- 'newusers/create' => 'NewUsersLogFormatter',
- 'newusers/create2' => 'NewUsersLogFormatter',
- 'newusers/byemail' => 'NewUsersLogFormatter',
- 'newusers/autocreate' => 'NewUsersLogFormatter',
+ 'newusers/newusers' => NewUsersLogFormatter::class,
+ 'newusers/create' => NewUsersLogFormatter::class,
+ 'newusers/create2' => NewUsersLogFormatter::class,
+ 'newusers/byemail' => NewUsersLogFormatter::class,
+ 'newusers/autocreate' => NewUsersLogFormatter::class,
] );
}
diff --git a/www/wiki/tests/phpunit/includes/logging/PageLangLogFormatterTest.php b/www/wiki/tests/phpunit/includes/logging/PageLangLogFormatterTest.php
index 2156bdb4..33fd68f6 100644
--- a/www/wiki/tests/phpunit/includes/logging/PageLangLogFormatterTest.php
+++ b/www/wiki/tests/phpunit/includes/logging/PageLangLogFormatterTest.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @covers PageLangLogFormatter
+ */
class PageLangLogFormatterTest extends LogFormatterTestCase {
protected function setUp() {
@@ -9,7 +12,7 @@ class PageLangLogFormatterTest extends LogFormatterTestCase {
$this->setMwGlobals( 'wgHooks', [] );
// Register LogHandler, see $wgPageLanguageUseDB in Setup.php
$this->mergeMwGlobalArrayValue( 'wgLogActionsHandlers', [
- 'pagelang/pagelang' => 'PageLangLogFormatter',
+ 'pagelang/pagelang' => PageLangLogFormatter::class,
] );
}
diff --git a/www/wiki/tests/phpunit/includes/logging/PatrolLogFormatterTest.php b/www/wiki/tests/phpunit/includes/logging/PatrolLogFormatterTest.php
index b6804543..0d78ed9c 100644
--- a/www/wiki/tests/phpunit/includes/logging/PatrolLogFormatterTest.php
+++ b/www/wiki/tests/phpunit/includes/logging/PatrolLogFormatterTest.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @covers PatrolLogFormatter
+ */
class PatrolLogFormatterTest extends LogFormatterTestCase {
/**
diff --git a/www/wiki/tests/phpunit/includes/logging/ProtectLogFormatterTest.php b/www/wiki/tests/phpunit/includes/logging/ProtectLogFormatterTest.php
index 1fa7fc24..1c076cab 100644
--- a/www/wiki/tests/phpunit/includes/logging/ProtectLogFormatterTest.php
+++ b/www/wiki/tests/phpunit/includes/logging/ProtectLogFormatterTest.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @covers ProtectLogFormatter
+ */
class ProtectLogFormatterTest extends LogFormatterTestCase {
/**
diff --git a/www/wiki/tests/phpunit/includes/logging/RightsLogFormatterTest.php b/www/wiki/tests/phpunit/includes/logging/RightsLogFormatterTest.php
index f48507d8..d081c61b 100644
--- a/www/wiki/tests/phpunit/includes/logging/RightsLogFormatterTest.php
+++ b/www/wiki/tests/phpunit/includes/logging/RightsLogFormatterTest.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @covers RightsLogFormatter
+ */
class RightsLogFormatterTest extends LogFormatterTestCase {
/**
diff --git a/www/wiki/tests/phpunit/includes/logging/UploadLogFormatterTest.php b/www/wiki/tests/phpunit/includes/logging/UploadLogFormatterTest.php
index 00d93d14..2b4067f1 100644
--- a/www/wiki/tests/phpunit/includes/logging/UploadLogFormatterTest.php
+++ b/www/wiki/tests/phpunit/includes/logging/UploadLogFormatterTest.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @covers UploadLogFormatter
+ */
class UploadLogFormatterTest extends LogFormatterTestCase {
/**
diff --git a/www/wiki/tests/phpunit/includes/mail/MailAddressTest.php b/www/wiki/tests/phpunit/includes/mail/MailAddressTest.php
index c837d26f..459f5cc4 100644
--- a/www/wiki/tests/phpunit/includes/mail/MailAddressTest.php
+++ b/www/wiki/tests/phpunit/includes/mail/MailAddressTest.php
@@ -7,7 +7,7 @@ class MailAddressTest extends MediaWikiTestCase {
*/
public function testConstructor() {
$ma = new MailAddress( 'foo@bar.baz', 'UserName', 'Real name' );
- $this->assertInstanceOf( 'MailAddress', $ma );
+ $this->assertInstanceOf( MailAddress::class, $ma );
}
/**
@@ -17,7 +17,7 @@ class MailAddressTest extends MediaWikiTestCase {
if ( wfIsWindows() ) {
$this->markTestSkipped( 'This test only works on non-Windows platforms' );
}
- $user = $this->createMock( 'User' );
+ $user = $this->createMock( User::class );
$user->expects( $this->any() )->method( 'getName' )->will(
$this->returnValue( 'UserName' )
);
@@ -29,11 +29,11 @@ class MailAddressTest extends MediaWikiTestCase {
);
$ma = MailAddress::newFromUser( $user );
- $this->assertInstanceOf( 'MailAddress', $ma );
+ $this->assertInstanceOf( MailAddress::class, $ma );
$this->setMwGlobals( 'wgEnotifUseRealName', true );
- $this->assertEquals( 'Real name <foo@bar.baz>', $ma->toString() );
+ $this->assertEquals( '"Real name" <foo@bar.baz>', $ma->toString() );
$this->setMwGlobals( 'wgEnotifUseRealName', false );
- $this->assertEquals( 'UserName <foo@bar.baz>', $ma->toString() );
+ $this->assertEquals( '"UserName" <foo@bar.baz>', $ma->toString() );
}
/**
@@ -51,11 +51,16 @@ class MailAddressTest extends MediaWikiTestCase {
public static function provideToString() {
return [
- [ true, 'foo@bar.baz', 'FooBar', 'Foo Bar', 'Foo Bar <foo@bar.baz>' ],
- [ true, 'foo@bar.baz', 'UserName', null, 'UserName <foo@bar.baz>' ],
- [ true, 'foo@bar.baz', 'AUser', 'My real name', 'My real name <foo@bar.baz>' ],
+ [ true, 'foo@bar.baz', 'FooBar', 'Foo Bar', '"Foo Bar" <foo@bar.baz>' ],
+ [ true, 'foo@bar.baz', 'UserName', null, '"UserName" <foo@bar.baz>' ],
+ [ true, 'foo@bar.baz', 'AUser', 'My real name', '"My real name" <foo@bar.baz>' ],
+ [ true, 'foo@bar.baz', 'AUser', 'My "real" name', '"My \"real\" name" <foo@bar.baz>' ],
+ [ true, 'foo@bar.baz', 'AUser', 'My "A/B" test', '"My \"A/B\" test" <foo@bar.baz>' ],
+ [ true, 'foo@bar.baz', 'AUser', 'E=MC2', '=?UTF-8?Q?E=3DMC2?= <foo@bar.baz>' ],
+ // A backslash (\) should be escaped (\\). In a string literal that is \\\\ (4x).
+ [ true, 'foo@bar.baz', 'AUser', 'My "B\C" test', '"My \"B\\\\C\" test" <foo@bar.baz>' ],
[ true, 'foo@bar.baz', 'A.user.name', 'my@real.name', '"my@real.name" <foo@bar.baz>' ],
- [ false, 'foo@bar.baz', 'AUserName', 'Some real name', 'AUserName <foo@bar.baz>' ],
+ [ false, 'foo@bar.baz', 'AUserName', 'Some real name', '"AUserName" <foo@bar.baz>' ],
[ false, 'foo@bar.baz', '', '', 'foo@bar.baz' ],
[ true, 'foo@bar.baz', '', '', 'foo@bar.baz' ],
[ true, '', '', '', '' ],
diff --git a/www/wiki/tests/phpunit/includes/media/BitmapScalingTest.php b/www/wiki/tests/phpunit/includes/media/BitmapScalingTest.php
index 92a927f8..fb96f7db 100644
--- a/www/wiki/tests/phpunit/includes/media/BitmapScalingTest.php
+++ b/www/wiki/tests/phpunit/includes/media/BitmapScalingTest.php
@@ -124,7 +124,7 @@ class BitmapScalingTest extends MediaWikiTestCase {
$file = new FakeDimensionFile( [ 4000, 4000 ] );
$handler = new BitmapHandler;
$params = [ 'width' => '3700' ]; // Still bigger than max size.
- $this->assertEquals( 'TransformTooBigImageAreaError',
+ $this->assertEquals( TransformTooBigImageAreaError::class,
get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) );
}
@@ -136,7 +136,7 @@ class BitmapScalingTest extends MediaWikiTestCase {
$file->mustRender = true;
$handler = new BitmapHandler;
$params = [ 'width' => '5000' ]; // Still bigger than max size.
- $this->assertEquals( 'TransformTooBigImageAreaError',
+ $this->assertEquals( TransformTooBigImageAreaError::class,
get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) );
}
diff --git a/www/wiki/tests/phpunit/includes/media/ExifBitmapTest.php b/www/wiki/tests/phpunit/includes/media/ExifBitmapTest.php
index 3dd7e4c6..eb02e7ed 100644
--- a/www/wiki/tests/phpunit/includes/media/ExifBitmapTest.php
+++ b/www/wiki/tests/phpunit/includes/media/ExifBitmapTest.php
@@ -47,9 +47,8 @@ class ExifBitmapTest extends MediaWikiMediaTestCase {
* @covers ExifBitmapHandler::isMetadataValid
*/
public function testGoodMetadata() {
- // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ // phpcs:ignore Generic.Files.LineLength
$meta = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}';
- // @codingStandardsIgnoreEnd
$res = $this->handler->isMetadataValid( null, $meta );
$this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res );
}
@@ -58,9 +57,8 @@ class ExifBitmapTest extends MediaWikiMediaTestCase {
* @covers ExifBitmapHandler::isMetadataValid
*/
public function testIsOldGood() {
- // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ // phpcs:ignore Generic.Files.LineLength
$meta = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:1;}';
- // @codingStandardsIgnoreEnd
$res = $this->handler->isMetadataValid( null, $meta );
$this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res );
}
@@ -70,9 +68,8 @@ class ExifBitmapTest extends MediaWikiMediaTestCase {
* @covers ExifBitmapHandler::isMetadataValid
*/
public function testPagedTiffHandledGracefully() {
- // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ // phpcs:ignore Generic.Files.LineLength
$meta = 'a:6:{s:9:"page_data";a:1:{i:1;a:5:{s:5:"width";i:643;s:6:"height";i:448;s:5:"alpha";s:4:"true";s:4:"page";i:1;s:6:"pixels";i:288064;}}s:10:"page_count";i:1;s:10:"first_page";i:1;s:9:"last_page";i:1;s:4:"exif";a:9:{s:10:"ImageWidth";i:643;s:11:"ImageLength";i:448;s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:4;s:12:"RowsPerStrip";i:50;s:19:"PlanarConfiguration";i:1;s:22:"MEDIAWIKI_EXIF_VERSION";i:1;}s:21:"TIFF_METADATA_VERSION";s:3:"1.4";}';
- // @codingStandardsIgnoreEnd
$res = $this->handler->isMetadataValid( null, $meta );
$this->assertEquals( ExifBitmapHandler::METADATA_BAD, $res );
}
diff --git a/www/wiki/tests/phpunit/includes/media/ExifRotationTest.php b/www/wiki/tests/phpunit/includes/media/ExifRotationTest.php
index 5ae17635..fff101f3 100644
--- a/www/wiki/tests/phpunit/includes/media/ExifRotationTest.php
+++ b/www/wiki/tests/phpunit/includes/media/ExifRotationTest.php
@@ -5,10 +5,13 @@
* @group Media
* @group medium
*
- * @todo covers tags
+ * @covers BitmapHandler
*/
class ExifRotationTest extends MediaWikiMediaTestCase {
+ /** @var BitmapHandler */
+ private $handler;
+
protected function setUp() {
parent::setUp();
$this->checkPHPExtension( 'exif' );
diff --git a/www/wiki/tests/phpunit/includes/media/FakeDimensionFile.php b/www/wiki/tests/phpunit/includes/media/FakeDimensionFile.php
index 81e820e0..5e3a3cba 100644
--- a/www/wiki/tests/phpunit/includes/media/FakeDimensionFile.php
+++ b/www/wiki/tests/phpunit/includes/media/FakeDimensionFile.php
@@ -1,8 +1,5 @@
<?php
-/**
- * @group Media
- */
class FakeDimensionFile extends File {
public $mustRender = false;
public $mime;
diff --git a/www/wiki/tests/phpunit/includes/media/FormatMetadataTest.php b/www/wiki/tests/phpunit/includes/media/FormatMetadataTest.php
index e9fc84e7..0987bd0a 100644
--- a/www/wiki/tests/phpunit/includes/media/FormatMetadataTest.php
+++ b/www/wiki/tests/phpunit/includes/media/FormatMetadataTest.php
@@ -44,7 +44,7 @@ class FormatMetadataTest extends MediaWikiMediaTestCase {
*/
public function testResolveMultivalueValue( $input, $output ) {
$formatMetadata = new FormatMetadata();
- $class = new ReflectionClass( 'FormatMetadata' );
+ $class = new ReflectionClass( FormatMetadata::class );
$method = $class->getMethod( 'resolveMultivalueValue' );
$method->setAccessible( true );
$actualInput = $method->invoke( $formatMetadata, $input );
@@ -95,4 +95,50 @@ class FormatMetadataTest extends MediaWikiMediaTestCase {
],
];
}
+
+ /**
+ * @param mixed $input
+ * @param mixed $output
+ * @dataProvider provideGetFormattedData
+ * @covers FormatMetadata::getFormattedData
+ */
+ public function testGetFormattedData( $input, $output ) {
+ $this->assertEquals( $output, FormatMetadata::getFormattedData( $input ) );
+ }
+
+ public function provideGetFormattedData() {
+ return [
+ [
+ [ 'Software' => 'Adobe Photoshop CS6 (Macintosh)' ],
+ [ 'Software' => 'Adobe Photoshop CS6 (Macintosh)' ],
+ ],
+ [
+ [ 'Software' => [ 'FotoWare FotoStation' ] ],
+ [ 'Software' => 'FotoWare FotoStation' ],
+ ],
+ [
+ [ 'Software' => [ [ 'Capture One PRO', '3.7.7' ] ] ],
+ [ 'Software' => 'Capture One PRO (Version 3.7.7)' ],
+ ],
+ [
+ [ 'Software' => [ [ 'FotoWare ColorFactory', '' ] ] ],
+ [ 'Software' => 'FotoWare ColorFactory (Version )' ],
+ ],
+ [
+ [ 'Software' => [ 'x-default' => 'paint.net 4.0.12', '_type' => 'lang' ] ],
+ [ 'Software' => '<ul class="metadata-langlist">'.
+ '<li class="mw-metadata-lang-default">'.
+ '<span class="mw-metadata-lang-value">paint.net 4.0.12</span>'.
+ "</li>\n".
+ '</ul>'
+ ],
+ ],
+ [
+ // https://phabricator.wikimedia.org/T178130
+ // WebMHandler.php turns both 'muxingapp' & 'writingapp' to 'Software'
+ [ 'Software' => [ [ 'Lavf57.25.100' ], [ 'Lavf57.25.100' ] ] ],
+ [ 'Software' => "<ul><li>Lavf57.25.100</li>\n<li>Lavf57.25.100</li></ul>" ],
+ ],
+ ];
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/media/GIFTest.php b/www/wiki/tests/phpunit/includes/media/GIFTest.php
index aaa3ac4d..4dd7443e 100644
--- a/www/wiki/tests/phpunit/includes/media/GIFTest.php
+++ b/www/wiki/tests/phpunit/includes/media/GIFTest.php
@@ -72,18 +72,18 @@ class GIFHandlerTest extends MediaWikiMediaTestCase {
}
public static function provideIsMetadataValid() {
+ // phpcs:disable Generic.Files.LineLength
return [
[ GIFHandler::BROKEN_FILE, GIFHandler::METADATA_GOOD ],
[ '', GIFHandler::METADATA_BAD ],
[ null, GIFHandler::METADATA_BAD ],
[ 'Something invalid!', GIFHandler::METADATA_BAD ],
- // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
[
'a:4:{s:10:"frameCount";i:1;s:6:"looped";b:0;s:8:"duration";d:0.1000000000000000055511151231257827021181583404541015625;s:8:"metadata";a:2:{s:14:"GIFFileComment";a:1:{i:0;s:35:"GIF test file ⁕ Created with GIMP";}s:15:"_MW_GIF_VERSION";i:1;}}',
GIFHandler::METADATA_GOOD
],
- // @codingStandardsIgnoreEnd
];
+ // phpcs:enable
}
/**
@@ -99,8 +99,8 @@ class GIFHandlerTest extends MediaWikiMediaTestCase {
}
public static function provideGetMetadata() {
+ // phpcs:disable Generic.Files.LineLength
return [
- // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
[
'nonanimated.gif',
'a:4:{s:10:"frameCount";i:1;s:6:"looped";b:0;s:8:"duration";d:0.1000000000000000055511151231257827021181583404541015625;s:8:"metadata";a:2:{s:14:"GIFFileComment";a:1:{i:0;s:35:"GIF test file ⁕ Created with GIMP";}s:15:"_MW_GIF_VERSION";i:1;}}'
@@ -109,8 +109,8 @@ class GIFHandlerTest extends MediaWikiMediaTestCase {
'animated-xmp.gif',
'a:4:{s:10:"frameCount";i:4;s:6:"looped";b:1;s:8:"duration";d:2.399999999999999911182158029987476766109466552734375;s:8:"metadata";a:5:{s:6:"Artist";s:7:"Bawolff";s:16:"ImageDescription";a:2:{s:9:"x-default";s:18:"A file to test GIF";s:5:"_type";s:4:"lang";}s:15:"SublocationDest";s:13:"The interwebs";s:14:"GIFFileComment";a:1:{i:0;s:16:"GIƒ·test·file";}s:15:"_MW_GIF_VERSION";i:1;}}'
],
- // @codingStandardsIgnoreEnd
];
+ // phpcs:enable
}
/**
@@ -153,6 +153,7 @@ class GIFHandlerTest extends MediaWikiMediaTestCase {
* @param string $filename
* @param float $expectedLength
* @dataProvider provideGetLength
+ * @covers GIFHandler::getLength
*/
public function testGetLength( $filename, $expectedLength ) {
$file = $this->dataFile( $filename, 'image/gif' );
diff --git a/www/wiki/tests/phpunit/includes/media/IPTCTest.php b/www/wiki/tests/phpunit/includes/media/IPTCTest.php
index 826957ec..4b3ba075 100644
--- a/www/wiki/tests/phpunit/includes/media/IPTCTest.php
+++ b/www/wiki/tests/phpunit/includes/media/IPTCTest.php
@@ -44,13 +44,6 @@ class IPTCTest extends MediaWikiTestCase {
* @covers IPTC::parse
*/
public function testIPTCParseForcedUTFButInvalid() {
- if ( version_compare( PHP_VERSION, '5.5.26', '<' )
- || ( version_compare( PHP_VERSION, '5.6.0', '>' )
- && version_compare( PHP_VERSION, '5.6.10', '<' )
- )
- ) {
- $this->markTestSkipped( 'Test fails on pre-PHP 5.5.25. See T124574/T39665 for details.' );
- }
$iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x11\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8"
. "\x1c\x01\x5A\x00\x03\x1B\x25\x47";
$res = IPTC::parse( $iptcData );
diff --git a/www/wiki/tests/phpunit/includes/media/JpegMetadataExtractorTest.php b/www/wiki/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
index 09912541..c943cef9 100644
--- a/www/wiki/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
+++ b/www/wiki/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
@@ -108,4 +108,21 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase {
$expected = 'BE';
$this->assertEquals( $expected, $res['byteOrder'] );
}
+
+ public function testInfiniteRead() {
+ // test file truncated right after a segment, which previously
+ // caused an infinite loop looking for the next segment byte.
+ // Should get past infinite loop and throw in wfUnpack()
+ $this->setExpectedException( 'MWException' );
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop1.jpg' );
+ }
+
+ public function testInfiniteRead2() {
+ // test file truncated after a segment's marker and size, which
+ // would cause a seek past end of file. Seek past end of file
+ // doesn't actually fail, but prevents further reading and was
+ // devolving into the previous case (testInfiniteRead).
+ $this->setExpectedException( 'MWException' );
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop2.jpg' );
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/media/JpegTest.php b/www/wiki/tests/phpunit/includes/media/JpegTest.php
index abe02808..13de7ff9 100644
--- a/www/wiki/tests/phpunit/includes/media/JpegTest.php
+++ b/www/wiki/tests/phpunit/includes/media/JpegTest.php
@@ -24,9 +24,8 @@ class JpegTest extends MediaWikiMediaTestCase {
public function testJpegMetadataExtraction() {
$file = $this->dataFile( 'test.jpg', 'image/jpeg' );
$res = $this->handler->getMetadata( $file, $this->filePath . 'test.jpg' );
- // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ // phpcs:ignore Generic.Files.LineLength
$expected = 'a:7:{s:16:"ImageDescription";s:9:"Test file";s:11:"XResolution";s:4:"72/1";s:11:"YResolution";s:4:"72/1";s:14:"ResolutionUnit";i:2;s:16:"YCbCrPositioning";i:1;s:15:"JPEGFileComment";a:1:{i:0;s:17:"Created with GIMP";}s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}';
- // @codingStandardsIgnoreEnd
// Unserialize in case serialization format ever changes.
$this->assertEquals( unserialize( $expected ), unserialize( $res ) );
diff --git a/www/wiki/tests/phpunit/includes/media/MediaWikiMediaTestCase.php b/www/wiki/tests/phpunit/includes/media/MediaWikiMediaTestCase.php
index 196f6882..a4e8056a 100644
--- a/www/wiki/tests/phpunit/includes/media/MediaWikiMediaTestCase.php
+++ b/www/wiki/tests/phpunit/includes/media/MediaWikiMediaTestCase.php
@@ -76,7 +76,7 @@ abstract class MediaWikiMediaTestCase extends MediaWikiTestCase {
protected function dataFile( $name, $type = null ) {
if ( !$type ) {
// Autodetect by file extension for the lazy.
- $magic = MimeMagic::singleton();
+ $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
$parts = explode( $name, '.' );
$type = $magic->guessTypesForExtension( $parts[count( $parts ) - 1] );
}
diff --git a/www/wiki/tests/phpunit/includes/media/PNGMetadataExtractorTest.php b/www/wiki/tests/phpunit/includes/media/PNGMetadataExtractorTest.php
index a9eaa9e7..22de9357 100644
--- a/www/wiki/tests/phpunit/includes/media/PNGMetadataExtractorTest.php
+++ b/www/wiki/tests/phpunit/includes/media/PNGMetadataExtractorTest.php
@@ -66,24 +66,6 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase {
}
/**
- * Test extraction of pHYs tags, which can tell what the
- * actual resolution of the image is (aka in dots per meter).
- */
- /*
- public function testPngPhysTag() {
- $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
- 'Png-native-test.png' );
-
- $this->assertArrayHasKey( 'text', $meta );
- $meta = $meta['text'];
-
- $this->assertEquals( '2835/100', $meta['XResolution'] );
- $this->assertEquals( '2835/100', $meta['YResolution'] );
- $this->assertEquals( 3, $meta['ResolutionUnit'] ); // 3 = cm
- }
- */
-
- /**
* Given a normal static PNG, check the animation metadata returned.
*/
public function testStaticPngAnimationMetadata() {
diff --git a/www/wiki/tests/phpunit/includes/media/PNGTest.php b/www/wiki/tests/phpunit/includes/media/PNGTest.php
index 32d54df7..5a66586e 100644
--- a/www/wiki/tests/phpunit/includes/media/PNGTest.php
+++ b/www/wiki/tests/phpunit/includes/media/PNGTest.php
@@ -73,18 +73,18 @@ class PNGHandlerTest extends MediaWikiMediaTestCase {
}
public static function provideIsMetadataValid() {
+ // phpcs:disable Generic.Files.LineLength
return [
[ PNGHandler::BROKEN_FILE, PNGHandler::METADATA_GOOD ],
[ '', PNGHandler::METADATA_BAD ],
[ null, PNGHandler::METADATA_BAD ],
[ 'Something invalid!', PNGHandler::METADATA_BAD ],
- // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
[
'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:8;s:9:"colorType";s:10:"truecolour";s:8:"metadata";a:1:{s:15:"_MW_PNG_VERSION";i:1;}}',
PNGHandler::METADATA_GOOD
],
- // @codingStandardsIgnoreEnd
];
+ // phpcs:enable
}
/**
@@ -101,8 +101,8 @@ class PNGHandlerTest extends MediaWikiMediaTestCase {
}
public static function provideGetMetadata() {
+ // phpcs:disable Generic.Files.LineLength
return [
- // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
[
'rgb-na-png.png',
'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:8;s:9:"colorType";s:10:"truecolour";s:8:"metadata";a:1:{s:15:"_MW_PNG_VERSION";i:1;}}'
@@ -111,8 +111,8 @@ class PNGHandlerTest extends MediaWikiMediaTestCase {
'xmp.png',
'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:1;s:9:"colorType";s:14:"index-coloured";s:8:"metadata";a:2:{s:12:"SerialNumber";s:9:"123456789";s:15:"_MW_PNG_VERSION";i:1;}}'
],
- // @codingStandardsIgnoreEnd
];
+ // phpcs:enable
}
/**
@@ -142,6 +142,7 @@ class PNGHandlerTest extends MediaWikiMediaTestCase {
* @param string $filename
* @param float $expectedLength
* @dataProvider provideGetLength
+ * @covers PNGHandler::getLength
*/
public function testGetLength( $filename, $expectedLength ) {
$file = $this->dataFile( $filename, 'image/png' );
diff --git a/www/wiki/tests/phpunit/includes/media/SVGMetadataExtractorTest.php b/www/wiki/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
index 9bfd5f61..6fbb4740 100644
--- a/www/wiki/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
+++ b/www/wiki/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
@@ -128,14 +128,14 @@ class SVGMetadataExtractorTest extends MediaWikiTestCase {
public static function provideSvgFilesWithXMLMetadata() {
$base = __DIR__ . '/../../data/media';
- // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ // phpcs:disable Generic.Files.LineLength
$metadata = '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<ns4:Work xmlns:ns4="http://creativecommons.org/ns#" rdf:about="">
<ns5:format xmlns:ns5="http://purl.org/dc/elements/1.1/">image/svg+xml</ns5:format>
<ns5:type xmlns:ns5="http://purl.org/dc/elements/1.1/" rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
</ns4:Work>
</rdf:RDF>';
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
$metadata = str_replace( "\r", '', $metadata ); // Windows compat
return [
diff --git a/www/wiki/tests/phpunit/includes/media/SVGTest.php b/www/wiki/tests/phpunit/includes/media/SVGTest.php
index 4a986b4c..b68dd0ee 100644
--- a/www/wiki/tests/phpunit/includes/media/SVGTest.php
+++ b/www/wiki/tests/phpunit/includes/media/SVGTest.php
@@ -3,7 +3,12 @@
/**
* @group Media
*/
-class SvgTest extends MediaWikiMediaTestCase {
+class SVGTest extends MediaWikiMediaTestCase {
+
+ /**
+ * @var SvgHandler
+ */
+ private $handler;
protected function setUp() {
parent::setUp();
@@ -38,4 +43,71 @@ class SvgTest extends MediaWikiMediaTestCase {
[ 'Wikimedia-logo.svg', [] ]
];
}
+
+ /**
+ * @param string $userPreferredLanguage
+ * @param array $svgLanguages
+ * @param string $expectedMatch
+ * @dataProvider providerGetMatchedLanguage
+ * @covers SvgHandler::getMatchedLanguage
+ */
+ public function testGetMatchedLanguage( $userPreferredLanguage, $svgLanguages, $expectedMatch ) {
+ $match = $this->handler->getMatchedLanguage( $userPreferredLanguage, $svgLanguages );
+ $this->assertEquals( $expectedMatch, $match );
+ }
+
+ public function providerGetMatchedLanguage() {
+ return [
+ 'no match' => [
+ 'userPreferredLanguage' => 'en',
+ 'svgLanguages' => [ 'de-DE', 'zh', 'ga', 'fr', 'sr-Latn-ME' ],
+ 'expectedMatch' => null,
+ ],
+ 'no subtags' => [
+ 'userPreferredLanguage' => 'en',
+ 'svgLanguages' => [ 'de', 'zh', 'en', 'fr' ],
+ 'expectedMatch' => 'en',
+ ],
+ 'user no subtags, svg 1 subtag' => [
+ 'userPreferredLanguage' => 'en',
+ 'svgLanguages' => [ 'de-DE', 'en-GB', 'en-US', 'fr' ],
+ 'expectedMatch' => 'en-GB',
+ ],
+ 'user no subtags, svg >1 subtag' => [
+ 'userPreferredLanguage' => 'sr',
+ 'svgLanguages' => [ 'de-DE', 'sr-Cyrl-BA', 'sr-Latn-ME', 'en-US', 'fr' ],
+ 'expectedMatch' => 'sr-Cyrl-BA',
+ ],
+ 'user 1 subtag, svg no subtags' => [
+ 'userPreferredLanguage' => 'en-US',
+ 'svgLanguages' => [ 'de', 'en', 'en', 'fr' ],
+ 'expectedMatch' => null,
+ ],
+ 'user 1 subtag, svg 1 subtag' => [
+ 'userPreferredLanguage' => 'en-US',
+ 'svgLanguages' => [ 'de-DE', 'en-GB', 'en-US', 'fr' ],
+ 'expectedMatch' => 'en-US',
+ ],
+ 'user 1 subtag, svg >1 subtag' => [
+ 'userPreferredLanguage' => 'sr-Latn',
+ 'svgLanguages' => [ 'de-DE', 'sr-Cyrl-BA', 'sr-Latn-ME', 'fr' ],
+ 'expectedMatch' => 'sr-Latn-ME',
+ ],
+ 'user >1 subtag, svg >1 subtag' => [
+ 'userPreferredLanguage' => 'sr-Latn-ME',
+ 'svgLanguages' => [ 'de-DE', 'sr-Cyrl-BA', 'sr-Latn-ME', 'en-US', 'fr' ],
+ 'expectedMatch' => 'sr-Latn-ME',
+ ],
+ 'user >1 subtag, svg <=1 subtag' => [
+ 'userPreferredLanguage' => 'sr-Latn-ME',
+ 'svgLanguages' => [ 'de-DE', 'sr-Cyrl', 'sr-Latn', 'en-US', 'fr' ],
+ 'expectedMatch' => null,
+ ],
+ 'ensure case-insensitive' => [
+ 'userPreferredLanguage' => 'sr-latn',
+ 'svgLanguages' => [ 'de-DE', 'sr-Cyrl', 'sr-Latn-ME', 'en-US', 'fr' ],
+ 'expectedMatch' => 'sr-Latn-ME',
+ ],
+ ];
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/media/TiffTest.php b/www/wiki/tests/phpunit/includes/media/TiffTest.php
index d1148202..8a69ec5b 100644
--- a/www/wiki/tests/phpunit/includes/media/TiffTest.php
+++ b/www/wiki/tests/phpunit/includes/media/TiffTest.php
@@ -34,9 +34,8 @@ class TiffTest extends MediaWikiTestCase {
public function testTiffMetadataExtraction() {
$res = $this->handler->getMetadata( null, $this->filePath . 'test.tiff' );
- // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ // phpcs:ignore Generic.Files.LineLength
$expected = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}';
- // @codingStandardsIgnoreEnd
// Re-unserialize in case there are subtle differences between how versions
// of php serialize stuff.
diff --git a/www/wiki/tests/phpunit/includes/media/WebPTest.php b/www/wiki/tests/phpunit/includes/media/WebPTest.php
index f51693d4..a0a99cc2 100644
--- a/www/wiki/tests/phpunit/includes/media/WebPTest.php
+++ b/www/wiki/tests/phpunit/includes/media/WebPTest.php
@@ -1,4 +1,8 @@
<?php
+
+/**
+ * @covers WebPHandler
+ */
class WebPHandlerTest extends MediaWikiTestCase {
public function setUp() {
parent::setUp();
@@ -19,7 +23,7 @@ class WebPHandlerTest extends MediaWikiTestCase {
$this->assertEquals( $expectedResult, WebPHandler::extractMetadata( $this->tempFileName ) );
}
public function provideTestExtractMetaData() {
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return [
// Files from https://developers.google.com/speed/webp/gallery2
[ "\x52\x49\x46\x46\x90\x68\x01\x00\x57\x45\x42\x50\x56\x50\x38\x4C\x83\x68\x01\x00\x2F\x8F\x01\x4B\x10\x8D\x38\x6C\xDB\x46\x92\xE0\xE0\x82\x7B\x6C",
@@ -67,7 +71,7 @@ class WebPHandlerTest extends MediaWikiTestCase {
[ 'RIFF1234WEBPVP8 ', false ],
[ 'RIFF1234WEBPVP8L ', false ],
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
/**
@@ -123,7 +127,7 @@ class WebPHandlerTest extends MediaWikiTestCase {
* @dataProvider provideTestGetMimeType
*/
public function testGuessMimeType( $path ) {
- $mime = MimeMagic::singleton();
+ $mime = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
$this->assertEquals( 'image/webp', $mime->guessMimeType( $path, false ) );
}
public function provideTestGetMimeType() {
diff --git a/www/wiki/tests/phpunit/includes/media/XMPTest.php b/www/wiki/tests/phpunit/includes/media/XMPTest.php
deleted file mode 100644
index bffe415c..00000000
--- a/www/wiki/tests/phpunit/includes/media/XMPTest.php
+++ /dev/null
@@ -1,223 +0,0 @@
-<?php
-
-/**
- * @group Media
- * @covers XMPReader
- */
-class XMPTest extends MediaWikiTestCase {
-
- protected function setUp() {
- parent::setUp();
- $this->checkPHPExtension( 'exif' ); # Requires libxml to do XMP parsing
- }
-
- /**
- * Put XMP in, compare what comes out...
- *
- * @param string $xmp The actual xml data.
- * @param array $expected Expected result of parsing the xmp.
- * @param string $info Short sentence on what's being tested.
- *
- * @throws Exception
- * @dataProvider provideXMPParse
- *
- * @covers XMPReader::parse
- */
- public function testXMPParse( $xmp, $expected, $info ) {
- if ( !is_string( $xmp ) || !is_array( $expected ) ) {
- throw new Exception( "Invalid data provided to " . __METHOD__ );
- }
- $reader = new XMPReader;
- $reader->parse( $xmp );
- $this->assertEquals( $expected, $reader->getResults(), $info, 0.0000000001 );
- }
-
- public static function provideXMPParse() {
- $xmpPath = __DIR__ . '/../../data/xmp/';
- $data = [];
-
- // $xmpFiles format: array of arrays with first arg file base name,
- // with the actual file having .xmp on the end for the xmp
- // and .result.php on the end for a php file containing the result
- // array. Second argument is some info on what's being tested.
- $xmpFiles = [
- [ '1', 'parseType=Resource test' ],
- [ '2', 'Structure with mixed attribute and element props' ],
- [ '3', 'Extra qualifiers (that should be ignored)' ],
- [ '3-invalid', 'Test ignoring qualifiers that look like normal props' ],
- [ '4', 'Flash as qualifier' ],
- [ '5', 'Flash as qualifier 2' ],
- [ '6', 'Multiple rdf:Description' ],
- [ '7', 'Generic test of several property types' ],
- [ 'flash', 'Test of Flash property' ],
- [ 'invalid-child-not-struct', 'Test child props not in struct or ignored' ],
- [ 'no-recognized-props', 'Test namespace and no recognized props' ],
- [ 'no-namespace', 'Test non-namespaced attributes are ignored' ],
- [ 'bag-for-seq', "Allow bag's instead of seq's. (bug 27105)" ],
- [ 'utf16BE', 'UTF-16BE encoding' ],
- [ 'utf16LE', 'UTF-16LE encoding' ],
- [ 'utf32BE', 'UTF-32BE encoding' ],
- [ 'utf32LE', 'UTF-32LE encoding' ],
- [ 'xmpExt', 'Extended XMP missing second part' ],
- [ 'gps', 'Handling of exif GPS parameters in XMP' ],
- ];
-
- $xmpFiles[] = [ 'doctype-included', 'XMP includes doctype' ];
-
- foreach ( $xmpFiles as $file ) {
- $xmp = file_get_contents( $xmpPath . $file[0] . '.xmp' );
- // I'm not sure if this is the best way to handle getting the
- // result array, but it seems kind of big to put directly in the test
- // file.
- $result = null;
- include $xmpPath . $file[0] . '.result.php';
- $data[] = [ $xmp, $result, '[' . $file[0] . '.xmp] ' . $file[1] ];
- }
-
- return $data;
- }
-
- /** Test ExtendedXMP block support. (Used when the XMP has to be split
- * over multiple jpeg segments, due to 64k size limit on jpeg segments.
- *
- * @todo This is based on what the standard says. Need to find a real
- * world example file to double check the support for this is right.
- *
- * @covers XMPReader::parseExtended
- */
- public function testExtendedXMP() {
- $xmpPath = __DIR__ . '/../../data/xmp/';
- $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
- $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
-
- $md5sum = '28C74E0AC2D796886759006FBE2E57B7'; // of xmpExt2.xmp
- $length = pack( 'N', strlen( $extendedXMP ) );
- $offset = pack( 'N', 0 );
- $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
-
- $reader = new XMPReader();
- $reader->parse( $standardXMP );
- $reader->parseExtended( $extendedPacket );
- $actual = $reader->getResults();
-
- $expected = [
- 'xmp-exif' => [
- 'DigitalZoomRatio' => '0/10',
- 'Flash' => 9,
- 'FNumber' => '2/10',
- ]
- ];
-
- $this->assertEquals( $expected, $actual );
- }
-
- /**
- * This test has an extended XMP block with a wrong guid (md5sum)
- * and thus should only return the StandardXMP, not the ExtendedXMP.
- *
- * @covers XMPReader::parseExtended
- */
- public function testExtendedXMPWithWrongGUID() {
- $xmpPath = __DIR__ . '/../../data/xmp/';
- $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
- $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
-
- $md5sum = '28C74E0AC2D796886759006FBE2E57B9'; // Note last digit.
- $length = pack( 'N', strlen( $extendedXMP ) );
- $offset = pack( 'N', 0 );
- $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
-
- $reader = new XMPReader();
- $reader->parse( $standardXMP );
- $reader->parseExtended( $extendedPacket );
- $actual = $reader->getResults();
-
- $expected = [
- 'xmp-exif' => [
- 'DigitalZoomRatio' => '0/10',
- 'Flash' => 9,
- ]
- ];
-
- $this->assertEquals( $expected, $actual );
- }
-
- /**
- * Have a high offset to simulate a missing packet,
- * which should cause it to ignore the ExtendedXMP packet.
- *
- * @covers XMPReader::parseExtended
- */
- public function testExtendedXMPMissingPacket() {
- $xmpPath = __DIR__ . '/../../data/xmp/';
- $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
- $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
-
- $md5sum = '28C74E0AC2D796886759006FBE2E57B7'; // of xmpExt2.xmp
- $length = pack( 'N', strlen( $extendedXMP ) );
- $offset = pack( 'N', 2048 );
- $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
-
- $reader = new XMPReader();
- $reader->parse( $standardXMP );
- $reader->parseExtended( $extendedPacket );
- $actual = $reader->getResults();
-
- $expected = [
- 'xmp-exif' => [
- 'DigitalZoomRatio' => '0/10',
- 'Flash' => 9,
- ]
- ];
-
- $this->assertEquals( $expected, $actual );
- }
-
- /**
- * Test for multi-section, hostile XML
- * @covers XMPReader::checkParseSafety
- */
- public function testCheckParseSafety() {
-
- // Test for detection
- $xmpPath = __DIR__ . '/../../data/xmp/';
- $file = fopen( $xmpPath . 'doctype-included.xmp', 'rb' );
- $valid = false;
- $reader = new XMPReader();
- do {
- $chunk = fread( $file, 10 );
- $valid = $reader->parse( $chunk, feof( $file ) );
- } while ( !feof( $file ) );
- $this->assertFalse( $valid, 'Check that doctype is detected in fragmented XML' );
- $this->assertEquals(
- [],
- $reader->getResults(),
- 'Check that doctype is detected in fragmented XML'
- );
- fclose( $file );
- unset( $reader );
-
- // Test for false positives
- $file = fopen( $xmpPath . 'doctype-not-included.xmp', 'rb' );
- $valid = false;
- $reader = new XMPReader();
- do {
- $chunk = fread( $file, 10 );
- $valid = $reader->parse( $chunk, feof( $file ) );
- } while ( !feof( $file ) );
- $this->assertTrue(
- $valid,
- 'Check for false-positive detecting doctype in fragmented XML'
- );
- $this->assertEquals(
- [
- 'xmp-exif' => [
- 'DigitalZoomRatio' => '0/10',
- 'Flash' => '9'
- ]
- ],
- $reader->getResults(),
- 'Check that doctype is detected in fragmented XML'
- );
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/media/XMPValidateTest.php b/www/wiki/tests/phpunit/includes/media/XMPValidateTest.php
deleted file mode 100644
index 6a006295..00000000
--- a/www/wiki/tests/phpunit/includes/media/XMPValidateTest.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-
-use Psr\Log\NullLogger;
-
-/**
- * @group Media
- */
-class XMPValidateTest extends MediaWikiTestCase {
-
- /**
- * @dataProvider provideDates
- * @covers XMPValidate::validateDate
- */
- public function testValidateDate( $value, $expected ) {
- // The method should modify $value.
- $validate = new XMPValidate( new NullLogger() );
- $validate->validateDate( [], $value, true );
- $this->assertEquals( $expected, $value );
- }
-
- public static function provideDates() {
- /* For reference valid date formats are:
- * YYYY
- * YYYY-MM
- * YYYY-MM-DD
- * YYYY-MM-DDThh:mmTZD
- * YYYY-MM-DDThh:mm:ssTZD
- * YYYY-MM-DDThh:mm:ss.sTZD
- * (Time zone is optional)
- */
- return [
- [ '1992', '1992' ],
- [ '1992-04', '1992:04' ],
- [ '1992-02-01', '1992:02:01' ],
- [ '2011-09-29', '2011:09:29' ],
- [ '1982-12-15T20:12', '1982:12:15 20:12' ],
- [ '1982-12-15T20:12Z', '1982:12:15 20:12' ],
- [ '1982-12-15T20:12+02:30', '1982:12:15 22:42' ],
- [ '1982-12-15T01:12-02:30', '1982:12:14 22:42' ],
- [ '1982-12-15T20:12:11', '1982:12:15 20:12:11' ],
- [ '1982-12-15T20:12:11Z', '1982:12:15 20:12:11' ],
- [ '1982-12-15T20:12:11+01:10', '1982:12:15 21:22:11' ],
- [ '2045-12-15T20:12:11', '2045:12:15 20:12:11' ],
- [ '1867-06-01T15:00:00', '1867:06:01 15:00:00' ],
- /* some invalid ones */
- [ '2001--12', null ],
- [ '2001-5-12', null ],
- [ '2001-5-12TZ', null ],
- [ '2001-05-12T15', null ],
- [ '2001-12T15:13', null ],
- ];
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php b/www/wiki/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php
index 7814b830..432754b6 100644
--- a/www/wiki/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php
+++ b/www/wiki/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php
@@ -42,7 +42,7 @@ class MemcachedBagOStuffTest extends MediaWikiTestCase {
);
$this->assertEquals(
- 'test:##dc89dcb43b28614da27660240af478b5',
+ 'test:BagOStuff-long-key:##dc89dcb43b28614da27660240af478b5',
$this->cache->makeKey( '𝕖𝕧𝕖𝕟', '𝕚𝕗', '𝕨𝕖', '𝕄𝔻𝟝', '𝕖𝕒𝕔𝕙',
'𝕒𝕣𝕘𝕦𝕞𝕖𝕟𝕥', '𝕥𝕙𝕚𝕤', '𝕜𝕖𝕪', '𝕨𝕠𝕦𝕝𝕕', '𝕤𝕥𝕚𝕝𝕝', '𝕓𝕖', '𝕥𝕠𝕠', '𝕝𝕠𝕟𝕘' )
);
@@ -70,6 +70,7 @@ class MemcachedBagOStuffTest extends MediaWikiTestCase {
/**
* @dataProvider validKeyProvider
+ * @covers MemcachedBagOStuff::validateKeyEncoding
*/
public function testValidateKeyEncoding( $key ) {
$this->assertSame( $key, $this->cache->validateKeyEncoding( $key ) );
@@ -86,9 +87,10 @@ class MemcachedBagOStuffTest extends MediaWikiTestCase {
/**
* @dataProvider invalidKeyProvider
+ * @covers MemcachedBagOStuff::validateKeyEncoding
*/
public function testValidateKeyEncodingThrowsException( $key ) {
- $this->setExpectedException( 'Exception' );
+ $this->setExpectedException( Exception::class );
$this->cache->validateKeyEncoding( $key );
}
diff --git a/www/wiki/tests/phpunit/includes/objectcache/ObjectCacheTest.php b/www/wiki/tests/phpunit/includes/objectcache/ObjectCacheTest.php
index f8ce24ec..43188528 100644
--- a/www/wiki/tests/phpunit/includes/objectcache/ObjectCacheTest.php
+++ b/www/wiki/tests/phpunit/includes/objectcache/ObjectCacheTest.php
@@ -16,13 +16,13 @@ class ObjectCacheTest extends MediaWikiTestCase {
private function setCacheConfig( $arr = [] ) {
$defaults = [
- CACHE_NONE => [ 'class' => 'EmptyBagOStuff' ],
- CACHE_DB => [ 'class' => 'SqlBagOStuff' ],
+ CACHE_NONE => [ 'class' => EmptyBagOStuff::class ],
+ CACHE_DB => [ 'class' => SqlBagOStuff::class ],
CACHE_ANYTHING => [ 'factory' => 'ObjectCache::newAnything' ],
// Mock ACCEL with 'hash' as being installed.
// This makes tests deterministic regardless of APC.
- CACHE_ACCEL => [ 'class' => 'HashBagOStuff' ],
- 'hash' => [ 'class' => 'HashBagOStuff' ],
+ CACHE_ACCEL => [ 'class' => HashBagOStuff::class ],
+ 'hash' => [ 'class' => HashBagOStuff::class ],
];
$this->setMwGlobals( 'wgObjectCaches', $arr + $defaults );
}
@@ -70,7 +70,7 @@ class ObjectCacheTest extends MediaWikiTestCase {
$this->setCacheConfig( [
// Mock APC not being installed (T160519, T147161)
- CACHE_ACCEL => [ 'class' => 'EmptyBagOStuff' ]
+ CACHE_ACCEL => [ 'class' => EmptyBagOStuff::class ]
] );
$this->assertInstanceOf(
@@ -91,7 +91,7 @@ class ObjectCacheTest extends MediaWikiTestCase {
$this->setCacheConfig( [
// Mock APC not being installed (T160519, T147161)
- CACHE_ACCEL => [ 'class' => 'EmptyBagOStuff' ]
+ CACHE_ACCEL => [ 'class' => EmptyBagOStuff::class ]
] );
$this->assertInstanceOf(
diff --git a/www/wiki/tests/phpunit/includes/objectcache/RESTBagOStuffTest.php b/www/wiki/tests/phpunit/includes/objectcache/RESTBagOStuffTest.php
index f722fe13..66754fc9 100644
--- a/www/wiki/tests/phpunit/includes/objectcache/RESTBagOStuffTest.php
+++ b/www/wiki/tests/phpunit/includes/objectcache/RESTBagOStuffTest.php
@@ -1,6 +1,8 @@
<?php
/**
* @group BagOStuff
+ *
+ * @covers RESTBagOStuff
*/
class RESTBagOStuffTest extends MediaWikiTestCase {
@@ -16,7 +18,7 @@ class RESTBagOStuffTest extends MediaWikiTestCase {
public function setUp() {
parent::setUp();
$this->client =
- $this->getMockBuilder( 'MultiHttpClient' )
+ $this->getMockBuilder( MultiHttpClient::class )
->setConstructorArgs( [ [] ] )
->setMethods( [ 'run' ] )
->getMock();
diff --git a/www/wiki/tests/phpunit/includes/objectcache/RedisBagOStuffTest.php b/www/wiki/tests/phpunit/includes/objectcache/RedisBagOStuffTest.php
index 34a72cec..df5614d8 100644
--- a/www/wiki/tests/phpunit/includes/objectcache/RedisBagOStuffTest.php
+++ b/www/wiki/tests/phpunit/includes/objectcache/RedisBagOStuffTest.php
@@ -5,13 +5,16 @@ use Wikimedia\TestingAccessWrapper;
/**
* @group BagOStuff
*/
-class RedisBagOStuffTest extends PHPUnit_Framework_TestCase {
+class RedisBagOStuffTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
/** @var RedisBagOStuff */
private $cache;
protected function setUp() {
parent::setUp();
- $cache = $this->getMockBuilder( 'RedisBagOStuff' )
+ $cache = $this->getMockBuilder( RedisBagOStuff::class )
->disableOriginalConstructor()
->getMock();
$this->cache = TestingAccessWrapper::newFromObject( $cache );
diff --git a/www/wiki/tests/phpunit/includes/page/ArticleTest.php b/www/wiki/tests/phpunit/includes/page/ArticleTest.php
index 7d0813d1..df4a2817 100644
--- a/www/wiki/tests/phpunit/includes/page/ArticleTest.php
+++ b/www/wiki/tests/phpunit/includes/page/ArticleTest.php
@@ -54,26 +54,4 @@ class ArticleTest extends MediaWikiTestCase {
$this->assertEquals( -8, $this->article->ext_someNewProperty,
"Article get/set magic on update to new field" );
}
-
- /**
- * Checks for the existence of the backwards compatibility static functions
- * (forwarders to WikiPage class)
- *
- * @covers Article::selectFields
- * @covers Article::onArticleCreate
- * @covers Article::onArticleDelete
- * @covers Article::onArticleEdit
- */
- public function testStaticFunctions() {
- $this->hideDeprecated( 'Article::selectFields' );
-
- $this->assertEquals( WikiPage::selectFields(), Article::selectFields(),
- "Article static functions" );
- $this->assertEquals( true, is_callable( "Article::onArticleCreate" ),
- "Article static functions" );
- $this->assertEquals( true, is_callable( "Article::onArticleDelete" ),
- "Article static functions" );
- $this->assertEquals( true, is_callable( "ImagePage::onArticleEdit" ),
- "Article static functions" );
- }
}
diff --git a/www/wiki/tests/phpunit/includes/page/ImagePage404Test.php b/www/wiki/tests/phpunit/includes/page/ImagePage404Test.php
index 48c4392d..4faace21 100644
--- a/www/wiki/tests/phpunit/includes/page/ImagePage404Test.php
+++ b/www/wiki/tests/phpunit/includes/page/ImagePage404Test.php
@@ -28,6 +28,7 @@ class ImagePage404Test extends MediaWikiMediaTestCase {
}
/**
+ * @covers ImagePage::getThumbSizes
* @dataProvider providerGetThumbSizes
* @param string $filename
* @param int $expectedNumberThumbs How many thumbnails to show
diff --git a/www/wiki/tests/phpunit/includes/page/ImagePageTest.php b/www/wiki/tests/phpunit/includes/page/ImagePageTest.php
index 2b30cfa5..8e49bf98 100644
--- a/www/wiki/tests/phpunit/includes/page/ImagePageTest.php
+++ b/www/wiki/tests/phpunit/includes/page/ImagePageTest.php
@@ -21,6 +21,7 @@ class ImagePageTest extends MediaWikiMediaTestCase {
}
/**
+ * @covers ImagePage::getDisplayWidthHeight
* @dataProvider providerGetDisplayWidthHeight
* @param array $dim Array [maxWidth, maxHeight, width, height]
* @param array $expected Array [width, height] The width and height we expect to display at
@@ -65,6 +66,7 @@ class ImagePageTest extends MediaWikiMediaTestCase {
}
/**
+ * @covers ImagePage::getThumbSizes
* @dataProvider providerGetThumbSizes
* @param string $filename
* @param int $expectedNumberThumbs How many thumbnails to show
diff --git a/www/wiki/tests/phpunit/includes/page/WikiPageContentHandlerDbTest.php b/www/wiki/tests/phpunit/includes/page/WikiPageContentHandlerDbTest.php
new file mode 100644
index 00000000..2d7d6cc3
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/page/WikiPageContentHandlerDbTest.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @group ContentHandler
+ * @group Database
+ * @group medium
+ */
+class WikiPageContentHandlerDbTest extends WikiPageDbTestBase {
+
+ protected function getContentHandlerUseDB() {
+ return true;
+ }
+
+ /**
+ * @covers WikiPage::getContentModel
+ */
+ public function testGetContentModel() {
+ $page = $this->createPage(
+ __METHOD__,
+ "some text",
+ CONTENT_MODEL_JAVASCRIPT
+ );
+
+ $page = new WikiPage( $page->getTitle() );
+ $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $page->getContentModel() );
+ }
+
+ /**
+ * @covers WikiPage::getContentHandler
+ */
+ public function testGetContentHandler() {
+ $page = $this->createPage(
+ __METHOD__,
+ "some text",
+ CONTENT_MODEL_JAVASCRIPT
+ );
+
+ $page = new WikiPage( $page->getTitle() );
+ $this->assertEquals( JavaScriptContentHandler::class, get_class( $page->getContentHandler() ) );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/page/WikiPageDbTestBase.php b/www/wiki/tests/phpunit/includes/page/WikiPageDbTestBase.php
new file mode 100644
index 00000000..53b659f2
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/page/WikiPageDbTestBase.php
@@ -0,0 +1,1903 @@
+<?php
+
+abstract class WikiPageDbTestBase extends MediaWikiLangTestCase {
+
+ private $pagesToDelete;
+
+ public function __construct( $name = null, array $data = [], $dataName = '' ) {
+ parent::__construct( $name, $data, $dataName );
+
+ $this->tablesUsed = array_merge(
+ $this->tablesUsed,
+ [ 'page',
+ 'revision',
+ 'redirect',
+ 'archive',
+ 'category',
+ 'ip_changes',
+ 'text',
+
+ 'recentchanges',
+ 'logging',
+
+ 'page_props',
+ 'pagelinks',
+ 'categorylinks',
+ 'langlinks',
+ 'externallinks',
+ 'imagelinks',
+ 'templatelinks',
+ 'iwlinks' ] );
+ }
+
+ protected function setUp() {
+ parent::setUp();
+ $this->setMwGlobals( 'wgContentHandlerUseDB', $this->getContentHandlerUseDB() );
+ $this->pagesToDelete = [];
+ }
+
+ protected function tearDown() {
+ foreach ( $this->pagesToDelete as $p ) {
+ /* @var $p WikiPage */
+
+ try {
+ if ( $p->exists() ) {
+ $p->doDeleteArticle( "testing done." );
+ }
+ } catch ( MWException $ex ) {
+ // fail silently
+ }
+ }
+ parent::tearDown();
+ }
+
+ abstract protected function getContentHandlerUseDB();
+
+ /**
+ * @param Title|string $title
+ * @param string|null $model
+ * @return WikiPage
+ */
+ private function newPage( $title, $model = null ) {
+ if ( is_string( $title ) ) {
+ $ns = $this->getDefaultWikitextNS();
+ $title = Title::newFromText( $title, $ns );
+ }
+
+ $p = new WikiPage( $title );
+
+ $this->pagesToDelete[] = $p;
+
+ return $p;
+ }
+
+ /**
+ * @param string|Title|WikiPage $page
+ * @param string $text
+ * @param int $model
+ *
+ * @return WikiPage
+ */
+ protected function createPage( $page, $text, $model = null ) {
+ if ( is_string( $page ) || $page instanceof Title ) {
+ $page = $this->newPage( $page, $model );
+ }
+
+ $content = ContentHandler::makeContent( $text, $page->getTitle(), $model );
+ $page->doEditContent( $content, "testing", EDIT_NEW );
+
+ return $page;
+ }
+
+ /**
+ * @covers WikiPage::doEditContent
+ * @covers WikiPage::doModify
+ * @covers WikiPage::doCreate
+ * @covers WikiPage::doEditUpdates
+ */
+ public function testDoEditContent() {
+ $page = $this->newPage( __METHOD__ );
+ $title = $page->getTitle();
+
+ $content = ContentHandler::makeContent(
+ "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam "
+ . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.",
+ $title,
+ CONTENT_MODEL_WIKITEXT
+ );
+
+ $page->doEditContent( $content, "[[testing]] 1" );
+
+ $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" );
+ $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" );
+ $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" );
+ $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" );
+
+ $id = $page->getId();
+
+ # ------------------------
+ $dbr = wfGetDB( DB_REPLICA );
+ $res = $dbr->select( 'pagelinks', '*', [ 'pl_from' => $id ] );
+ $n = $res->numRows();
+ $res->free();
+
+ $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' );
+
+ # ------------------------
+ $page = new WikiPage( $title );
+
+ $retrieved = $page->getContent();
+ $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' );
+
+ # ------------------------
+ $content = ContentHandler::makeContent(
+ "At vero eos et accusam et justo duo [[dolores]] et ea rebum. "
+ . "Stet clita kasd [[gubergren]], no sea takimata sanctus est.",
+ $title,
+ CONTENT_MODEL_WIKITEXT
+ );
+
+ $page->doEditContent( $content, "testing 2" );
+
+ # ------------------------
+ $page = new WikiPage( $title );
+
+ $retrieved = $page->getContent();
+ $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' );
+
+ # ------------------------
+ $dbr = wfGetDB( DB_REPLICA );
+ $res = $dbr->select( 'pagelinks', '*', [ 'pl_from' => $id ] );
+ $n = $res->numRows();
+ $res->free();
+
+ $this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' );
+ }
+
+ /**
+ * @covers WikiPage::doDeleteArticle
+ * @covers WikiPage::doDeleteArticleReal
+ */
+ public function testDoDeleteArticle() {
+ $page = $this->createPage(
+ __METHOD__,
+ "[[original text]] foo",
+ CONTENT_MODEL_WIKITEXT
+ );
+ $id = $page->getId();
+
+ $page->doDeleteArticle( "testing deletion" );
+
+ $this->assertFalse(
+ $page->getTitle()->getArticleID() > 0,
+ "Title object should now have page id 0"
+ );
+ $this->assertFalse( $page->getId() > 0, "WikiPage should now have page id 0" );
+ $this->assertFalse(
+ $page->exists(),
+ "WikiPage::exists should return false after page was deleted"
+ );
+ $this->assertNull(
+ $page->getContent(),
+ "WikiPage::getContent should return null after page was deleted"
+ );
+
+ $t = Title::newFromText( $page->getTitle()->getPrefixedText() );
+ $this->assertFalse(
+ $t->exists(),
+ "Title::exists should return false after page was deleted"
+ );
+
+ // Run the job queue
+ JobQueueGroup::destroySingletons();
+ $jobs = new RunJobs;
+ $jobs->loadParamsAndArgs( null, [ 'quiet' => true ], null );
+ $jobs->execute();
+
+ # ------------------------
+ $dbr = wfGetDB( DB_REPLICA );
+ $res = $dbr->select( 'pagelinks', '*', [ 'pl_from' => $id ] );
+ $n = $res->numRows();
+ $res->free();
+
+ $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' );
+ }
+
+ /**
+ * @covers WikiPage::doDeleteUpdates
+ */
+ public function testDoDeleteUpdates() {
+ $page = $this->createPage(
+ __METHOD__,
+ "[[original text]] foo",
+ CONTENT_MODEL_WIKITEXT
+ );
+ $id = $page->getId();
+
+ // Similar to MovePage logic
+ wfGetDB( DB_MASTER )->delete( 'page', [ 'page_id' => $id ], __METHOD__ );
+ $page->doDeleteUpdates( $id );
+
+ // Run the job queue
+ JobQueueGroup::destroySingletons();
+ $jobs = new RunJobs;
+ $jobs->loadParamsAndArgs( null, [ 'quiet' => true ], null );
+ $jobs->execute();
+
+ # ------------------------
+ $dbr = wfGetDB( DB_REPLICA );
+ $res = $dbr->select( 'pagelinks', '*', [ 'pl_from' => $id ] );
+ $n = $res->numRows();
+ $res->free();
+
+ $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' );
+ }
+
+ /**
+ * @covers WikiPage::getRevision
+ */
+ public function testGetRevision() {
+ $page = $this->newPage( __METHOD__ );
+
+ $rev = $page->getRevision();
+ $this->assertNull( $rev );
+
+ # -----------------
+ $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
+
+ $rev = $page->getRevision();
+
+ $this->assertEquals( $page->getLatest(), $rev->getId() );
+ $this->assertEquals( "some text", $rev->getContent()->getNativeData() );
+ }
+
+ /**
+ * @covers WikiPage::getContent
+ */
+ public function testGetContent() {
+ $page = $this->newPage( __METHOD__ );
+
+ $content = $page->getContent();
+ $this->assertNull( $content );
+
+ # -----------------
+ $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
+
+ $content = $page->getContent();
+ $this->assertEquals( "some text", $content->getNativeData() );
+ }
+
+ /**
+ * @covers WikiPage::exists
+ */
+ public function testExists() {
+ $page = $this->newPage( __METHOD__ );
+ $this->assertFalse( $page->exists() );
+
+ # -----------------
+ $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
+ $this->assertTrue( $page->exists() );
+
+ $page = new WikiPage( $page->getTitle() );
+ $this->assertTrue( $page->exists() );
+
+ # -----------------
+ $page->doDeleteArticle( "done testing" );
+ $this->assertFalse( $page->exists() );
+
+ $page = new WikiPage( $page->getTitle() );
+ $this->assertFalse( $page->exists() );
+ }
+
+ public function provideHasViewableContent() {
+ return [
+ [ 'WikiPageTest_testHasViewableContent', false, true ],
+ [ 'Special:WikiPageTest_testHasViewableContent', false ],
+ [ 'MediaWiki:WikiPageTest_testHasViewableContent', false ],
+ [ 'Special:Userlogin', true ],
+ [ 'MediaWiki:help', true ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideHasViewableContent
+ * @covers WikiPage::hasViewableContent
+ */
+ public function testHasViewableContent( $title, $viewable, $create = false ) {
+ $page = $this->newPage( $title );
+ $this->assertEquals( $viewable, $page->hasViewableContent() );
+
+ if ( $create ) {
+ $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
+ $this->assertTrue( $page->hasViewableContent() );
+
+ $page = new WikiPage( $page->getTitle() );
+ $this->assertTrue( $page->hasViewableContent() );
+ }
+ }
+
+ public function provideGetRedirectTarget() {
+ return [
+ [ 'WikiPageTest_testGetRedirectTarget_1', CONTENT_MODEL_WIKITEXT, "hello world", null ],
+ [
+ 'WikiPageTest_testGetRedirectTarget_2',
+ CONTENT_MODEL_WIKITEXT,
+ "#REDIRECT [[hello world]]",
+ "Hello world"
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetRedirectTarget
+ * @covers WikiPage::getRedirectTarget
+ */
+ public function testGetRedirectTarget( $title, $model, $text, $target ) {
+ $this->setMwGlobals( [
+ 'wgCapitalLinks' => true,
+ ] );
+
+ $page = $this->createPage( $title, $text, $model );
+
+ # sanity check, because this test seems to fail for no reason for some people.
+ $c = $page->getContent();
+ $this->assertEquals( WikitextContent::class, get_class( $c ) );
+
+ # now, test the actual redirect
+ $t = $page->getRedirectTarget();
+ $this->assertEquals( $target, is_null( $t ) ? null : $t->getPrefixedText() );
+ }
+
+ /**
+ * @dataProvider provideGetRedirectTarget
+ * @covers WikiPage::isRedirect
+ */
+ public function testIsRedirect( $title, $model, $text, $target ) {
+ $page = $this->createPage( $title, $text, $model );
+ $this->assertEquals( !is_null( $target ), $page->isRedirect() );
+ }
+
+ public function provideIsCountable() {
+ return [
+
+ // any
+ [ 'WikiPageTest_testIsCountable',
+ CONTENT_MODEL_WIKITEXT,
+ '',
+ 'any',
+ true
+ ],
+ [ 'WikiPageTest_testIsCountable',
+ CONTENT_MODEL_WIKITEXT,
+ 'Foo',
+ 'any',
+ true
+ ],
+
+ // link
+ [ 'WikiPageTest_testIsCountable',
+ CONTENT_MODEL_WIKITEXT,
+ 'Foo',
+ 'link',
+ false
+ ],
+ [ 'WikiPageTest_testIsCountable',
+ CONTENT_MODEL_WIKITEXT,
+ 'Foo [[bar]]',
+ 'link',
+ true
+ ],
+
+ // redirects
+ [ 'WikiPageTest_testIsCountable',
+ CONTENT_MODEL_WIKITEXT,
+ '#REDIRECT [[bar]]',
+ 'any',
+ false
+ ],
+ [ 'WikiPageTest_testIsCountable',
+ CONTENT_MODEL_WIKITEXT,
+ '#REDIRECT [[bar]]',
+ 'link',
+ false
+ ],
+
+ // not a content namespace
+ [ 'Talk:WikiPageTest_testIsCountable',
+ CONTENT_MODEL_WIKITEXT,
+ 'Foo',
+ 'any',
+ false
+ ],
+ [ 'Talk:WikiPageTest_testIsCountable',
+ CONTENT_MODEL_WIKITEXT,
+ 'Foo [[bar]]',
+ 'link',
+ false
+ ],
+
+ // not a content namespace, different model
+ [ 'MediaWiki:WikiPageTest_testIsCountable.js',
+ null,
+ 'Foo',
+ 'any',
+ false
+ ],
+ [ 'MediaWiki:WikiPageTest_testIsCountable.js',
+ null,
+ 'Foo [[bar]]',
+ 'link',
+ false
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideIsCountable
+ * @covers WikiPage::isCountable
+ */
+ public function testIsCountable( $title, $model, $text, $mode, $expected ) {
+ global $wgContentHandlerUseDB;
+
+ $this->setMwGlobals( 'wgArticleCountMethod', $mode );
+
+ $title = Title::newFromText( $title );
+
+ if ( !$wgContentHandlerUseDB
+ && $model
+ && ContentHandler::getDefaultModelFor( $title ) != $model
+ ) {
+ $this->markTestSkipped( "Can not use non-default content model $model for "
+ . $title->getPrefixedDBkey() . " with \$wgContentHandlerUseDB disabled." );
+ }
+
+ $page = $this->createPage( $title, $text, $model );
+
+ $editInfo = $page->prepareContentForEdit( $page->getContent() );
+
+ $v = $page->isCountable();
+ $w = $page->isCountable( $editInfo );
+
+ $this->assertEquals(
+ $expected,
+ $v,
+ "isCountable( null ) returned unexpected value " . var_export( $v, true )
+ . " instead of " . var_export( $expected, true )
+ . " in mode `$mode` for text \"$text\""
+ );
+
+ $this->assertEquals(
+ $expected,
+ $w,
+ "isCountable( \$editInfo ) returned unexpected value " . var_export( $v, true )
+ . " instead of " . var_export( $expected, true )
+ . " in mode `$mode` for text \"$text\""
+ );
+ }
+
+ public function provideGetParserOutput() {
+ return [
+ [
+ CONTENT_MODEL_WIKITEXT,
+ "hello ''world''\n",
+ "<div class=\"mw-parser-output\"><p>hello <i>world</i></p></div>"
+ ],
+ // @todo more...?
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetParserOutput
+ * @covers WikiPage::getParserOutput
+ */
+ public function testGetParserOutput( $model, $text, $expectedHtml ) {
+ $page = $this->createPage( __METHOD__, $text, $model );
+
+ $opt = $page->makeParserOptions( 'canonical' );
+ $po = $page->getParserOutput( $opt );
+ $text = $po->getText();
+
+ $text = trim( preg_replace( '/<!--.*?-->/sm', '', $text ) ); # strip injected comments
+ $text = preg_replace( '!\s*(</p>|</div>)!sm', '\1', $text ); # don't let tidy confuse us
+
+ $this->assertEquals( $expectedHtml, $text );
+
+ return $po;
+ }
+
+ /**
+ * @covers WikiPage::getParserOutput
+ */
+ public function testGetParserOutput_nonexisting() {
+ $page = new WikiPage( Title::newFromText( __METHOD__ ) );
+
+ $opt = new ParserOptions();
+ $po = $page->getParserOutput( $opt );
+
+ $this->assertFalse( $po, "getParserOutput() shall return false for non-existing pages." );
+ }
+
+ /**
+ * @covers WikiPage::getParserOutput
+ */
+ public function testGetParserOutput_badrev() {
+ $page = $this->createPage( __METHOD__, 'dummy', CONTENT_MODEL_WIKITEXT );
+
+ $opt = new ParserOptions();
+ $po = $page->getParserOutput( $opt, $page->getLatest() + 1234 );
+
+ // @todo would be neat to also test deleted revision
+
+ $this->assertFalse( $po, "getParserOutput() shall return false for non-existing revisions." );
+ }
+
+ public static $sections =
+
+ "Intro
+
+== stuff ==
+hello world
+
+== test ==
+just a test
+
+== foo ==
+more stuff
+";
+
+ public function dataReplaceSection() {
+ // NOTE: assume the Help namespace to contain wikitext
+ return [
+ [ 'Help:WikiPageTest_testReplaceSection',
+ CONTENT_MODEL_WIKITEXT,
+ self::$sections,
+ "0",
+ "No more",
+ null,
+ trim( preg_replace( '/^Intro/sm', 'No more', self::$sections ) )
+ ],
+ [ 'Help:WikiPageTest_testReplaceSection',
+ CONTENT_MODEL_WIKITEXT,
+ self::$sections,
+ "",
+ "No more",
+ null,
+ "No more"
+ ],
+ [ 'Help:WikiPageTest_testReplaceSection',
+ CONTENT_MODEL_WIKITEXT,
+ self::$sections,
+ "2",
+ "== TEST ==\nmore fun",
+ null,
+ trim( preg_replace( '/^== test ==.*== foo ==/sm',
+ "== TEST ==\nmore fun\n\n== foo ==",
+ self::$sections ) )
+ ],
+ [ 'Help:WikiPageTest_testReplaceSection',
+ CONTENT_MODEL_WIKITEXT,
+ self::$sections,
+ "8",
+ "No more",
+ null,
+ trim( self::$sections )
+ ],
+ [ 'Help:WikiPageTest_testReplaceSection',
+ CONTENT_MODEL_WIKITEXT,
+ self::$sections,
+ "new",
+ "No more",
+ "New",
+ trim( self::$sections ) . "\n\n== New ==\n\nNo more"
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataReplaceSection
+ * @covers WikiPage::replaceSectionContent
+ */
+ public function testReplaceSectionContent( $title, $model, $text, $section,
+ $with, $sectionTitle, $expected
+ ) {
+ $page = $this->createPage( $title, $text, $model );
+
+ $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() );
+ $c = $page->replaceSectionContent( $section, $content, $sectionTitle );
+
+ $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) );
+ }
+
+ /**
+ * @dataProvider dataReplaceSection
+ * @covers WikiPage::replaceSectionAtRev
+ */
+ public function testReplaceSectionAtRev( $title, $model, $text, $section,
+ $with, $sectionTitle, $expected
+ ) {
+ $page = $this->createPage( $title, $text, $model );
+ $baseRevId = $page->getLatest();
+
+ $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() );
+ $c = $page->replaceSectionAtRev( $section, $content, $sectionTitle, $baseRevId );
+
+ $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) );
+ }
+
+ /**
+ * @covers WikiPage::getOldestRevision
+ */
+ public function testGetOldestRevision() {
+ $page = $this->newPage( __METHOD__ );
+ $page->doEditContent(
+ new WikitextContent( 'one' ),
+ "first edit",
+ EDIT_NEW
+ );
+ $rev1 = $page->getRevision();
+
+ $page = new WikiPage( $page->getTitle() );
+ $page->doEditContent(
+ new WikitextContent( 'two' ),
+ "second edit",
+ EDIT_UPDATE
+ );
+
+ $page = new WikiPage( $page->getTitle() );
+ $page->doEditContent(
+ new WikitextContent( 'three' ),
+ "third edit",
+ EDIT_UPDATE
+ );
+
+ // sanity check
+ $this->assertNotEquals(
+ $rev1->getId(),
+ $page->getRevision()->getId(),
+ '$page->getRevision()->getId()'
+ );
+
+ // actual test
+ $this->assertEquals(
+ $rev1->getId(),
+ $page->getOldestRevision()->getId(),
+ '$page->getOldestRevision()->getId()'
+ );
+ }
+
+ /**
+ * @covers WikiPage::doRollback
+ * @covers WikiPage::commitRollback
+ */
+ public function testDoRollback() {
+ $admin = $this->getTestSysop()->getUser();
+ $user1 = $this->getTestUser()->getUser();
+ // Use the confirmed group for user2 to make sure the user is different
+ $user2 = $this->getTestUser( [ 'confirmed' ] )->getUser();
+
+ $page = $this->newPage( __METHOD__ );
+
+ // Make some edits
+ $text = "one";
+ $status1 = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
+ "section one", EDIT_NEW, false, $admin );
+
+ $text .= "\n\ntwo";
+ $status2 = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
+ "adding section two", 0, false, $user1 );
+
+ $text .= "\n\nthree";
+ $status3 = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
+ "adding section three", 0, false, $user2 );
+
+ /** @var Revision $rev1 */
+ /** @var Revision $rev2 */
+ /** @var Revision $rev3 */
+ $rev1 = $status1->getValue()['revision'];
+ $rev2 = $status2->getValue()['revision'];
+ $rev3 = $status3->getValue()['revision'];
+
+ /**
+ * We are having issues with doRollback spuriously failing. Apparently
+ * the last revision somehow goes missing or not committed under some
+ * circumstances. So, make sure the revisions have the correct usernames.
+ */
+ $this->assertEquals( 3, Revision::countByPageId( wfGetDB( DB_REPLICA ), $page->getId() ) );
+ $this->assertEquals( $admin->getName(), $rev1->getUserText() );
+ $this->assertEquals( $user1->getName(), $rev2->getUserText() );
+ $this->assertEquals( $user2->getName(), $rev3->getUserText() );
+
+ // Now, try the actual rollback
+ $token = $admin->getEditToken( 'rollback' );
+ $rollbackErrors = $page->doRollback(
+ $user2->getName(),
+ "testing rollback",
+ $token,
+ false,
+ $resultDetails,
+ $admin
+ );
+
+ if ( $rollbackErrors ) {
+ $this->fail(
+ "Rollback failed:\n" .
+ print_r( $rollbackErrors, true ) . ";\n" .
+ print_r( $resultDetails, true )
+ );
+ }
+
+ $page = new WikiPage( $page->getTitle() );
+ $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(),
+ "rollback did not revert to the correct revision" );
+ $this->assertEquals( "one\n\ntwo", $page->getContent()->getNativeData() );
+ }
+
+ /**
+ * @covers WikiPage::doRollback
+ * @covers WikiPage::commitRollback
+ */
+ public function testDoRollback_simple() {
+ $admin = $this->getTestSysop()->getUser();
+
+ $text = "one";
+ $page = $this->newPage( __METHOD__ );
+ $page->doEditContent(
+ ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
+ "section one",
+ EDIT_NEW,
+ false,
+ $admin
+ );
+ $rev1 = $page->getRevision();
+
+ $user1 = $this->getTestUser()->getUser();
+ $text .= "\n\ntwo";
+ $page = new WikiPage( $page->getTitle() );
+ $page->doEditContent(
+ ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
+ "adding section two",
+ 0,
+ false,
+ $user1
+ );
+
+ # now, try the rollback
+ $token = $admin->getEditToken( 'rollback' );
+ $errors = $page->doRollback(
+ $user1->getName(),
+ "testing revert",
+ $token,
+ false,
+ $details,
+ $admin
+ );
+
+ if ( $errors ) {
+ $this->fail( "Rollback failed:\n" . print_r( $errors, true )
+ . ";\n" . print_r( $details, true ) );
+ }
+
+ $page = new WikiPage( $page->getTitle() );
+ $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(),
+ "rollback did not revert to the correct revision" );
+ $this->assertEquals( "one", $page->getContent()->getNativeData() );
+ }
+
+ /**
+ * @covers WikiPage::doRollback
+ * @covers WikiPage::commitRollback
+ */
+ public function testDoRollbackFailureSameContent() {
+ $admin = $this->getTestSysop()->getUser();
+
+ $text = "one";
+ $page = $this->newPage( __METHOD__ );
+ $page->doEditContent(
+ ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
+ "section one",
+ EDIT_NEW,
+ false,
+ $admin
+ );
+ $rev1 = $page->getRevision();
+
+ $user1 = $this->getTestUser( [ 'sysop' ] )->getUser();
+ $text .= "\n\ntwo";
+ $page = new WikiPage( $page->getTitle() );
+ $page->doEditContent(
+ ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
+ "adding section two",
+ 0,
+ false,
+ $user1
+ );
+
+ # now, do a the rollback from the same user was doing the edit before
+ $resultDetails = [];
+ $token = $user1->getEditToken( 'rollback' );
+ $errors = $page->doRollback(
+ $user1->getName(),
+ "testing revert same user",
+ $token,
+ false,
+ $resultDetails,
+ $admin
+ );
+
+ $this->assertEquals( [], $errors, "Rollback failed same user" );
+
+ # now, try the rollback
+ $resultDetails = [];
+ $token = $admin->getEditToken( 'rollback' );
+ $errors = $page->doRollback(
+ $user1->getName(),
+ "testing revert",
+ $token,
+ false,
+ $resultDetails,
+ $admin
+ );
+
+ $this->assertEquals(
+ [
+ [
+ 'alreadyrolled',
+ __METHOD__,
+ $user1->getName(),
+ $admin->getName(),
+ ],
+ ],
+ $errors,
+ "Rollback not failed"
+ );
+
+ $page = new WikiPage( $page->getTitle() );
+ $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(),
+ "rollback did not revert to the correct revision" );
+ $this->assertEquals( "one", $page->getContent()->getNativeData() );
+ }
+
+ /**
+ * Tests tagging for edits that do rollback action
+ * @covers WikiPage::doRollback
+ */
+ public function testDoRollbackTagging() {
+ if ( !in_array( 'mw-rollback', ChangeTags::getSoftwareTags() ) ) {
+ $this->markTestSkipped( 'Rollback tag deactivated, skipped the test.' );
+ }
+
+ $admin = new User();
+ $admin->setName( 'Administrator' );
+ $admin->addToDatabase();
+
+ $text = 'First line';
+ $page = $this->newPage( 'WikiPageTest_testDoRollbackTagging' );
+ $page->doEditContent(
+ ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
+ 'Added first line',
+ EDIT_NEW,
+ false,
+ $admin
+ );
+
+ $secondUser = new User();
+ $secondUser->setName( '92.65.217.32' );
+ $text .= '\n\nSecond line';
+ $page = new WikiPage( $page->getTitle() );
+ $page->doEditContent(
+ ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
+ 'Adding second line',
+ 0,
+ false,
+ $secondUser
+ );
+
+ // Now, try the rollback
+ $admin->addGroup( 'sysop' ); // Make the test user a sysop
+ $token = $admin->getEditToken( 'rollback' );
+ $errors = $page->doRollback(
+ $secondUser->getName(),
+ 'testing rollback',
+ $token,
+ false,
+ $resultDetails,
+ $admin
+ );
+
+ // If doRollback completed without errors
+ if ( $errors === [] ) {
+ $tags = $resultDetails[ 'tags' ];
+ $this->assertContains( 'mw-rollback', $tags );
+ }
+ }
+
+ public function provideGetAutoDeleteReason() {
+ return [
+ [
+ [],
+ false,
+ false
+ ],
+
+ [
+ [
+ [ "first edit", null ],
+ ],
+ "/first edit.*only contributor/",
+ false
+ ],
+
+ [
+ [
+ [ "first edit", null ],
+ [ "second edit", null ],
+ ],
+ "/second edit.*only contributor/",
+ true
+ ],
+
+ [
+ [
+ [ "first edit", "127.0.2.22" ],
+ [ "second edit", "127.0.3.33" ],
+ ],
+ "/second edit/",
+ true
+ ],
+
+ [
+ [
+ [
+ "first edit: "
+ . "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam "
+ . " nonumy eirmod tempor invidunt ut labore et dolore magna "
+ . "aliquyam erat, sed diam voluptua. At vero eos et accusam "
+ . "et justo duo dolores et ea rebum. Stet clita kasd gubergren, "
+ . "no sea takimata sanctus est Lorem ipsum dolor sit amet.'",
+ null
+ ],
+ ],
+ '/first edit:.*\.\.\."/',
+ false
+ ],
+
+ [
+ [
+ [ "first edit", "127.0.2.22" ],
+ [ "", "127.0.3.33" ],
+ ],
+ "/before blanking.*first edit/",
+ true
+ ],
+
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetAutoDeleteReason
+ * @covers WikiPage::getAutoDeleteReason
+ */
+ public function testGetAutoDeleteReason( $edits, $expectedResult, $expectedHistory ) {
+ global $wgUser;
+
+ // NOTE: assume Help namespace to contain wikitext
+ $page = $this->newPage( "Help:WikiPageTest_testGetAutoDeleteReason" );
+
+ $c = 1;
+
+ foreach ( $edits as $edit ) {
+ $user = new User();
+
+ if ( !empty( $edit[1] ) ) {
+ $user->setName( $edit[1] );
+ } else {
+ $user = $wgUser;
+ }
+
+ $content = ContentHandler::makeContent( $edit[0], $page->getTitle(), $page->getContentModel() );
+
+ $page->doEditContent( $content, "test edit $c", $c < 2 ? EDIT_NEW : 0, false, $user );
+
+ $c += 1;
+ }
+
+ $reason = $page->getAutoDeleteReason( $hasHistory );
+
+ if ( is_bool( $expectedResult ) || is_null( $expectedResult ) ) {
+ $this->assertEquals( $expectedResult, $reason );
+ } else {
+ $this->assertTrue( (bool)preg_match( $expectedResult, $reason ),
+ "Autosummary didn't match expected pattern $expectedResult: $reason" );
+ }
+
+ $this->assertEquals( $expectedHistory, $hasHistory,
+ "expected \$hasHistory to be " . var_export( $expectedHistory, true ) );
+
+ $page->doDeleteArticle( "done" );
+ }
+
+ public function providePreSaveTransform() {
+ return [
+ [ 'hello this is ~~~',
+ "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
+ ],
+ [ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+ ],
+ ];
+ }
+
+ /**
+ * @covers WikiPage::factory
+ */
+ public function testWikiPageFactory() {
+ $title = Title::makeTitle( NS_FILE, 'Someimage.png' );
+ $page = WikiPage::factory( $title );
+ $this->assertEquals( WikiFilePage::class, get_class( $page ) );
+
+ $title = Title::makeTitle( NS_CATEGORY, 'SomeCategory' );
+ $page = WikiPage::factory( $title );
+ $this->assertEquals( WikiCategoryPage::class, get_class( $page ) );
+
+ $title = Title::makeTitle( NS_MAIN, 'SomePage' );
+ $page = WikiPage::factory( $title );
+ $this->assertEquals( WikiPage::class, get_class( $page ) );
+ }
+
+ /**
+ * @dataProvider provideCommentMigrationOnDeletion
+ *
+ * @param int $writeStage
+ * @param int $readStage
+ */
+ public function testCommentMigrationOnDeletion( $writeStage, $readStage ) {
+ $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', $writeStage );
+ $this->overrideMwServices();
+
+ $dbr = wfGetDB( DB_REPLICA );
+
+ $page = $this->createPage(
+ __METHOD__,
+ "foo",
+ CONTENT_MODEL_WIKITEXT
+ );
+ $revid = $page->getLatest();
+ if ( $writeStage > MIGRATION_OLD ) {
+ $comment_id = $dbr->selectField(
+ 'revision_comment_temp',
+ 'revcomment_comment_id',
+ [ 'revcomment_rev' => $revid ],
+ __METHOD__
+ );
+ }
+
+ $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', $readStage );
+ $this->overrideMwServices();
+
+ $page->doDeleteArticle( "testing deletion" );
+
+ if ( $readStage > MIGRATION_OLD ) {
+ // Didn't leave behind any 'revision_comment_temp' rows
+ $n = $dbr->selectField(
+ 'revision_comment_temp', 'COUNT(*)', [ 'revcomment_rev' => $revid ], __METHOD__
+ );
+ $this->assertEquals( 0, $n, 'no entry in revision_comment_temp after deletion' );
+
+ // Copied or upgraded the comment_id, as applicable
+ $ar_comment_id = $dbr->selectField(
+ 'archive',
+ 'ar_comment_id',
+ [ 'ar_rev_id' => $revid ],
+ __METHOD__
+ );
+ if ( $writeStage > MIGRATION_OLD ) {
+ $this->assertSame( $comment_id, $ar_comment_id );
+ } else {
+ $this->assertNotEquals( 0, $ar_comment_id );
+ }
+ }
+
+ // Copied rev_comment, if applicable
+ if ( $readStage <= MIGRATION_WRITE_BOTH && $writeStage <= MIGRATION_WRITE_BOTH ) {
+ $ar_comment = $dbr->selectField(
+ 'archive',
+ 'ar_comment',
+ [ 'ar_rev_id' => $revid ],
+ __METHOD__
+ );
+ $this->assertSame( 'testing', $ar_comment );
+ }
+ }
+
+ public function provideCommentMigrationOnDeletion() {
+ return [
+ [ MIGRATION_OLD, MIGRATION_OLD ],
+ [ MIGRATION_OLD, MIGRATION_WRITE_BOTH ],
+ [ MIGRATION_OLD, MIGRATION_WRITE_NEW ],
+ [ MIGRATION_WRITE_BOTH, MIGRATION_OLD ],
+ [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_BOTH ],
+ [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
+ [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
+ [ MIGRATION_WRITE_NEW, MIGRATION_WRITE_BOTH ],
+ [ MIGRATION_WRITE_NEW, MIGRATION_WRITE_NEW ],
+ [ MIGRATION_WRITE_NEW, MIGRATION_NEW ],
+ [ MIGRATION_NEW, MIGRATION_WRITE_BOTH ],
+ [ MIGRATION_NEW, MIGRATION_WRITE_NEW ],
+ [ MIGRATION_NEW, MIGRATION_NEW ],
+ ];
+ }
+
+ /**
+ * @covers WikiPage::updateCategoryCounts
+ */
+ public function testUpdateCategoryCounts() {
+ $page = new WikiPage( Title::newFromText( __METHOD__ ) );
+
+ // Add an initial category
+ $page->updateCategoryCounts( [ 'A' ], [], 0 );
+
+ $this->assertEquals( 1, Category::newFromName( 'A' )->getPageCount() );
+ $this->assertEquals( 0, Category::newFromName( 'B' )->getPageCount() );
+ $this->assertEquals( 0, Category::newFromName( 'C' )->getPageCount() );
+
+ // Add a new category
+ $page->updateCategoryCounts( [ 'B' ], [], 0 );
+
+ $this->assertEquals( 1, Category::newFromName( 'A' )->getPageCount() );
+ $this->assertEquals( 1, Category::newFromName( 'B' )->getPageCount() );
+ $this->assertEquals( 0, Category::newFromName( 'C' )->getPageCount() );
+
+ // Add and remove a category
+ $page->updateCategoryCounts( [ 'C' ], [ 'A' ], 0 );
+
+ $this->assertEquals( 0, Category::newFromName( 'A' )->getPageCount() );
+ $this->assertEquals( 1, Category::newFromName( 'B' )->getPageCount() );
+ $this->assertEquals( 1, Category::newFromName( 'C' )->getPageCount() );
+ }
+
+ public function provideUpdateRedirectOn() {
+ yield [ '#REDIRECT [[Foo]]', true, null, true, true, 0 ];
+ yield [ '#REDIRECT [[Foo]]', true, 'Foo', true, false, 1 ];
+ yield [ 'SomeText', false, null, false, true, 0 ];
+ yield [ 'SomeText', false, 'Foo', false, false, 1 ];
+ }
+
+ /**
+ * @dataProvider provideUpdateRedirectOn
+ * @covers WikiPage::updateRedirectOn
+ *
+ * @param string $initialText
+ * @param bool $initialRedirectState
+ * @param string|null $redirectTitle
+ * @param bool|null $lastRevIsRedirect
+ * @param bool $expectedSuccess
+ * @param int $expectedRowCount
+ */
+ public function testUpdateRedirectOn(
+ $initialText,
+ $initialRedirectState,
+ $redirectTitle,
+ $lastRevIsRedirect,
+ $expectedSuccess,
+ $expectedRowCount
+ ) {
+ static $pageCounter = 0;
+ $pageCounter++;
+
+ $page = $this->createPage( Title::newFromText( __METHOD__ . $pageCounter ), $initialText );
+ $this->assertSame( $initialRedirectState, $page->isRedirect() );
+
+ $redirectTitle = is_string( $redirectTitle )
+ ? Title::newFromText( $redirectTitle )
+ : $redirectTitle;
+
+ $success = $page->updateRedirectOn( $this->db, $redirectTitle, $lastRevIsRedirect );
+ $this->assertSame( $expectedSuccess, $success, 'Success assertion' );
+ /**
+ * updateRedirectOn explicitly updates the redirect table (and not the page table).
+ * Most of core checks the page table for redirect status, so we have to be ugly and
+ * assert a select from the table here.
+ */
+ $this->assertRedirectTableCountForPageId( $page->getId(), $expectedRowCount );
+ }
+
+ private function assertRedirectTableCountForPageId( $pageId, $expected ) {
+ $this->assertSelect(
+ 'redirect',
+ 'COUNT(*)',
+ [ 'rd_from' => $pageId ],
+ [ [ strval( $expected ) ] ]
+ );
+ }
+
+ /**
+ * @covers WikiPage::insertRedirectEntry
+ */
+ public function testInsertRedirectEntry_insertsRedirectEntry() {
+ $page = $this->createPage( Title::newFromText( __METHOD__ ), 'A' );
+ $this->assertRedirectTableCountForPageId( $page->getId(), 0 );
+
+ $targetTitle = Title::newFromText( 'SomeTarget#Frag' );
+ $targetTitle->mInterwiki = 'eninter';
+ $page->insertRedirectEntry( $targetTitle, null );
+
+ $this->assertSelect(
+ 'redirect',
+ [ 'rd_from', 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ],
+ [ 'rd_from' => $page->getId() ],
+ [ [
+ strval( $page->getId() ),
+ strval( $targetTitle->getNamespace() ),
+ strval( $targetTitle->getDBkey() ),
+ strval( $targetTitle->getFragment() ),
+ strval( $targetTitle->getInterwiki() ),
+ ] ]
+ );
+ }
+
+ /**
+ * @covers WikiPage::insertRedirectEntry
+ */
+ public function testInsertRedirectEntry_insertsRedirectEntryWithPageLatest() {
+ $page = $this->createPage( Title::newFromText( __METHOD__ ), 'A' );
+ $this->assertRedirectTableCountForPageId( $page->getId(), 0 );
+
+ $targetTitle = Title::newFromText( 'SomeTarget#Frag' );
+ $targetTitle->mInterwiki = 'eninter';
+ $page->insertRedirectEntry( $targetTitle, $page->getLatest() );
+
+ $this->assertSelect(
+ 'redirect',
+ [ 'rd_from', 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ],
+ [ 'rd_from' => $page->getId() ],
+ [ [
+ strval( $page->getId() ),
+ strval( $targetTitle->getNamespace() ),
+ strval( $targetTitle->getDBkey() ),
+ strval( $targetTitle->getFragment() ),
+ strval( $targetTitle->getInterwiki() ),
+ ] ]
+ );
+ }
+
+ /**
+ * @covers WikiPage::insertRedirectEntry
+ */
+ public function testInsertRedirectEntry_doesNotInsertIfPageLatestIncorrect() {
+ $page = $this->createPage( Title::newFromText( __METHOD__ ), 'A' );
+ $this->assertRedirectTableCountForPageId( $page->getId(), 0 );
+
+ $targetTitle = Title::newFromText( 'SomeTarget#Frag' );
+ $targetTitle->mInterwiki = 'eninter';
+ $page->insertRedirectEntry( $targetTitle, 215251 );
+
+ $this->assertRedirectTableCountForPageId( $page->getId(), 0 );
+ }
+
+ private function getRow( array $overrides = [] ) {
+ $row = [
+ 'page_id' => '44',
+ 'page_len' => '76',
+ 'page_is_redirect' => '1',
+ 'page_latest' => '99',
+ 'page_namespace' => '3',
+ 'page_title' => 'JaJaTitle',
+ 'page_restrictions' => 'edit=autoconfirmed,sysop:move=sysop',
+ 'page_touched' => '20120101020202',
+ 'page_links_updated' => '20140101020202',
+ ];
+ foreach ( $overrides as $key => $value ) {
+ $row[$key] = $value;
+ }
+ return (object)$row;
+ }
+
+ public function provideNewFromRowSuccess() {
+ yield 'basic row' => [
+ $this->getRow(),
+ function ( WikiPage $wikiPage, self $test ) {
+ $test->assertSame( 44, $wikiPage->getId() );
+ $test->assertSame( 76, $wikiPage->getTitle()->getLength() );
+ $test->assertTrue( $wikiPage->isRedirect() );
+ $test->assertSame( 99, $wikiPage->getLatest() );
+ $test->assertSame( 3, $wikiPage->getTitle()->getNamespace() );
+ $test->assertSame( 'JaJaTitle', $wikiPage->getTitle()->getDBkey() );
+ $test->assertSame(
+ [
+ 'edit' => [ 'autoconfirmed', 'sysop' ],
+ 'move' => [ 'sysop' ],
+ ],
+ $wikiPage->getTitle()->getAllRestrictions()
+ );
+ $test->assertSame( '20120101020202', $wikiPage->getTouched() );
+ $test->assertSame( '20140101020202', $wikiPage->getLinksTimestamp() );
+ }
+ ];
+ yield 'different timestamp formats' => [
+ $this->getRow( [
+ 'page_touched' => '2012-01-01 02:02:02',
+ 'page_links_updated' => '2014-01-01 02:02:02',
+ ] ),
+ function ( WikiPage $wikiPage, self $test ) {
+ $test->assertSame( '20120101020202', $wikiPage->getTouched() );
+ $test->assertSame( '20140101020202', $wikiPage->getLinksTimestamp() );
+ }
+ ];
+ yield 'no restrictions' => [
+ $this->getRow( [
+ 'page_restrictions' => '',
+ ] ),
+ function ( WikiPage $wikiPage, self $test ) {
+ $test->assertSame(
+ [
+ 'edit' => [],
+ 'move' => [],
+ ],
+ $wikiPage->getTitle()->getAllRestrictions()
+ );
+ }
+ ];
+ yield 'not redirect' => [
+ $this->getRow( [
+ 'page_is_redirect' => '0',
+ ] ),
+ function ( WikiPage $wikiPage, self $test ) {
+ $test->assertFalse( $wikiPage->isRedirect() );
+ }
+ ];
+ }
+
+ /**
+ * @covers WikiPage::newFromRow
+ * @covers WikiPage::loadFromRow
+ * @dataProvider provideNewFromRowSuccess
+ *
+ * @param object $row
+ * @param callable $assertions
+ */
+ public function testNewFromRow( $row, $assertions ) {
+ $page = WikiPage::newFromRow( $row, 'fromdb' );
+ $assertions( $page, $this );
+ }
+
+ public function provideTestNewFromId_returnsNullOnBadPageId() {
+ yield[ 0 ];
+ yield[ -11 ];
+ }
+
+ /**
+ * @covers WikiPage::newFromID
+ * @dataProvider provideTestNewFromId_returnsNullOnBadPageId
+ */
+ public function testNewFromId_returnsNullOnBadPageId( $pageId ) {
+ $this->assertNull( WikiPage::newFromID( $pageId ) );
+ }
+
+ /**
+ * @covers WikiPage::newFromID
+ */
+ public function testNewFromId_appearsToFetchCorrectRow() {
+ $createdPage = $this->createPage( __METHOD__, 'Xsfaij09' );
+ $fetchedPage = WikiPage::newFromID( $createdPage->getId() );
+ $this->assertSame( $createdPage->getId(), $fetchedPage->getId() );
+ $this->assertEquals(
+ $createdPage->getContent()->getNativeData(),
+ $fetchedPage->getContent()->getNativeData()
+ );
+ }
+
+ /**
+ * @covers WikiPage::newFromID
+ */
+ public function testNewFromId_returnsNullOnNonExistingId() {
+ $this->assertNull( WikiPage::newFromID( 2147483647 ) );
+ }
+
+ public function provideTestInsertProtectNullRevision() {
+ // phpcs:disable Generic.Files.LineLength
+ yield [
+ 'goat-message-key',
+ [ 'edit' => 'sysop' ],
+ [ 'edit' => '20200101040404' ],
+ false,
+ 'Goat Reason',
+ true,
+ '(goat-message-key: WikiPageDbTestBase::testInsertProtectNullRevision, UTSysop)(colon-separator)Goat Reason(word-separator)(parentheses: (protect-summary-desc: (restriction-edit), (protect-level-sysop), (protect-expiring: 04:04, 1 (january) 2020, 1 (january) 2020, 04:04)))'
+ ];
+ yield [
+ 'goat-key',
+ [ 'edit' => 'sysop', 'move' => 'something' ],
+ [ 'edit' => '20200101040404', 'move' => '20210101050505' ],
+ false,
+ 'Goat Goat',
+ true,
+ '(goat-key: WikiPageDbTestBase::testInsertProtectNullRevision, UTSysop)(colon-separator)Goat Goat(word-separator)(parentheses: (protect-summary-desc: (restriction-edit), (protect-level-sysop), (protect-expiring: 04:04, 1 (january) 2020, 1 (january) 2020, 04:04))(word-separator)(protect-summary-desc: (restriction-move), (protect-level-something), (protect-expiring: 05:05, 1 (january) 2021, 1 (january) 2021, 05:05)))'
+ ];
+ // phpcs:enable
+ }
+
+ /**
+ * @dataProvider provideTestInsertProtectNullRevision
+ * @covers WikiPage::insertProtectNullRevision
+ * @covers WikiPage::protectDescription
+ *
+ * @param string $revCommentMsg
+ * @param array $limit
+ * @param array $expiry
+ * @param bool $cascade
+ * @param string $reason
+ * @param bool|null $user true if the test sysop should be used, or null
+ * @param string $expectedComment
+ */
+ public function testInsertProtectNullRevision(
+ $revCommentMsg,
+ array $limit,
+ array $expiry,
+ $cascade,
+ $reason,
+ $user,
+ $expectedComment
+ ) {
+ $this->setContentLang( 'qqx' );
+
+ $page = $this->createPage( __METHOD__, 'Goat' );
+
+ $user = $user === null ? $user : $this->getTestSysop()->getUser();
+
+ $result = $page->insertProtectNullRevision(
+ $revCommentMsg,
+ $limit,
+ $expiry,
+ $cascade,
+ $reason,
+ $user
+ );
+
+ $this->assertTrue( $result instanceof Revision );
+ $this->assertSame( $expectedComment, $result->getComment( Revision::RAW ) );
+ }
+
+ /**
+ * @covers WikiPage::updateRevisionOn
+ */
+ public function testUpdateRevisionOn_existingPage() {
+ $user = $this->getTestSysop()->getUser();
+ $page = $this->createPage( __METHOD__, 'StartText' );
+
+ $revision = new Revision(
+ [
+ 'id' => 9989,
+ 'page' => $page->getId(),
+ 'title' => $page->getTitle(),
+ 'comment' => __METHOD__,
+ 'minor_edit' => true,
+ 'text' => __METHOD__ . '-text',
+ 'len' => strlen( __METHOD__ . '-text' ),
+ 'user' => $user->getId(),
+ 'user_text' => $user->getName(),
+ 'timestamp' => '20170707040404',
+ 'content_model' => CONTENT_MODEL_WIKITEXT,
+ 'content_format' => CONTENT_FORMAT_WIKITEXT,
+ ]
+ );
+
+ $result = $page->updateRevisionOn( $this->db, $revision );
+ $this->assertTrue( $result );
+ $this->assertSame( 9989, $page->getLatest() );
+ $this->assertEquals( $revision, $page->getRevision() );
+ }
+
+ /**
+ * @covers WikiPage::updateRevisionOn
+ */
+ public function testUpdateRevisionOn_NonExistingPage() {
+ $user = $this->getTestSysop()->getUser();
+ $page = $this->createPage( __METHOD__, 'StartText' );
+ $page->doDeleteArticle( 'reason' );
+
+ $revision = new Revision(
+ [
+ 'id' => 9989,
+ 'page' => $page->getId(),
+ 'title' => $page->getTitle(),
+ 'comment' => __METHOD__,
+ 'minor_edit' => true,
+ 'text' => __METHOD__ . '-text',
+ 'len' => strlen( __METHOD__ . '-text' ),
+ 'user' => $user->getId(),
+ 'user_text' => $user->getName(),
+ 'timestamp' => '20170707040404',
+ 'content_model' => CONTENT_MODEL_WIKITEXT,
+ 'content_format' => CONTENT_FORMAT_WIKITEXT,
+ ]
+ );
+
+ $result = $page->updateRevisionOn( $this->db, $revision );
+ $this->assertFalse( $result );
+ }
+
+ /**
+ * @covers WikiPage::updateIfNewerOn
+ */
+ public function testUpdateIfNewerOn_olderRevision() {
+ $user = $this->getTestSysop()->getUser();
+ $page = $this->createPage( __METHOD__, 'StartText' );
+ $initialRevision = $page->getRevision();
+
+ $olderTimeStamp = wfTimestamp(
+ TS_MW,
+ wfTimestamp( TS_UNIX, $initialRevision->getTimestamp() ) - 1
+ );
+
+ $olderRevison = new Revision(
+ [
+ 'id' => 9989,
+ 'page' => $page->getId(),
+ 'title' => $page->getTitle(),
+ 'comment' => __METHOD__,
+ 'minor_edit' => true,
+ 'text' => __METHOD__ . '-text',
+ 'len' => strlen( __METHOD__ . '-text' ),
+ 'user' => $user->getId(),
+ 'user_text' => $user->getName(),
+ 'timestamp' => $olderTimeStamp,
+ 'content_model' => CONTENT_MODEL_WIKITEXT,
+ 'content_format' => CONTENT_FORMAT_WIKITEXT,
+ ]
+ );
+
+ $result = $page->updateIfNewerOn( $this->db, $olderRevison );
+ $this->assertFalse( $result );
+ }
+
+ /**
+ * @covers WikiPage::updateIfNewerOn
+ */
+ public function testUpdateIfNewerOn_newerRevision() {
+ $user = $this->getTestSysop()->getUser();
+ $page = $this->createPage( __METHOD__, 'StartText' );
+ $initialRevision = $page->getRevision();
+
+ $newerTimeStamp = wfTimestamp(
+ TS_MW,
+ wfTimestamp( TS_UNIX, $initialRevision->getTimestamp() ) + 1
+ );
+
+ $newerRevision = new Revision(
+ [
+ 'id' => 9989,
+ 'page' => $page->getId(),
+ 'title' => $page->getTitle(),
+ 'comment' => __METHOD__,
+ 'minor_edit' => true,
+ 'text' => __METHOD__ . '-text',
+ 'len' => strlen( __METHOD__ . '-text' ),
+ 'user' => $user->getId(),
+ 'user_text' => $user->getName(),
+ 'timestamp' => $newerTimeStamp,
+ 'content_model' => CONTENT_MODEL_WIKITEXT,
+ 'content_format' => CONTENT_FORMAT_WIKITEXT,
+ ]
+ );
+ $result = $page->updateIfNewerOn( $this->db, $newerRevision );
+ $this->assertTrue( $result );
+ }
+
+ /**
+ * @covers WikiPage::insertOn
+ */
+ public function testInsertOn() {
+ $title = Title::newFromText( __METHOD__ );
+ $page = new WikiPage( $title );
+
+ $startTimeStamp = wfTimestampNow();
+ $result = $page->insertOn( $this->db );
+ $endTimeStamp = wfTimestampNow();
+
+ $this->assertInternalType( 'int', $result );
+ $this->assertTrue( $result > 0 );
+
+ $condition = [ 'page_id' => $result ];
+
+ // Check the default fields have been filled
+ $this->assertSelect(
+ 'page',
+ [
+ 'page_namespace',
+ 'page_title',
+ 'page_restrictions',
+ 'page_is_redirect',
+ 'page_is_new',
+ 'page_latest',
+ 'page_len',
+ ],
+ $condition,
+ [ [
+ '0',
+ __METHOD__,
+ '',
+ '0',
+ '1',
+ '0',
+ '0',
+ ] ]
+ );
+
+ // Check the page_random field has been filled
+ $pageRandom = $this->db->selectField( 'page', 'page_random', $condition );
+ $this->assertTrue( (float)$pageRandom < 1 && (float)$pageRandom > 0 );
+
+ // Assert the touched timestamp in the DB is roughly when we inserted the page
+ $pageTouched = $this->db->selectField( 'page', 'page_touched', $condition );
+ $this->assertTrue(
+ wfTimestamp( TS_UNIX, $startTimeStamp )
+ <= wfTimestamp( TS_UNIX, $pageTouched )
+ );
+ $this->assertTrue(
+ wfTimestamp( TS_UNIX, $endTimeStamp )
+ >= wfTimestamp( TS_UNIX, $pageTouched )
+ );
+
+ // Try inserting the same page again and checking the result is false (no change)
+ $result = $page->insertOn( $this->db );
+ $this->assertFalse( $result );
+ }
+
+ /**
+ * @covers WikiPage::insertOn
+ */
+ public function testInsertOn_idSpecified() {
+ $title = Title::newFromText( __METHOD__ );
+ $page = new WikiPage( $title );
+ $id = 1478952189;
+
+ $result = $page->insertOn( $this->db, $id );
+
+ $this->assertSame( $id, $result );
+
+ $condition = [ 'page_id' => $result ];
+
+ // Check there is actually a row in the db
+ $this->assertSelect(
+ 'page',
+ [ 'page_title' ],
+ $condition,
+ [ [ __METHOD__ ] ]
+ );
+ }
+
+ public function provideTestDoUpdateRestrictions_setBasicRestrictions() {
+ // Note: Once the current dates passes the date in these tests they will fail.
+ yield 'move something' => [
+ true,
+ [ 'move' => 'something' ],
+ [],
+ [ 'edit' => [], 'move' => [ 'something' ] ],
+ [],
+ ];
+ yield 'move something, edit blank' => [
+ true,
+ [ 'move' => 'something', 'edit' => '' ],
+ [],
+ [ 'edit' => [], 'move' => [ 'something' ] ],
+ [],
+ ];
+ yield 'edit sysop, with expiry' => [
+ true,
+ [ 'edit' => 'sysop' ],
+ [ 'edit' => '21330101020202' ],
+ [ 'edit' => [ 'sysop' ], 'move' => [] ],
+ [ 'edit' => '21330101020202' ],
+ ];
+ yield 'move and edit, move with expiry' => [
+ true,
+ [ 'move' => 'something', 'edit' => 'another' ],
+ [ 'move' => '22220202010101' ],
+ [ 'edit' => [ 'another' ], 'move' => [ 'something' ] ],
+ [ 'move' => '22220202010101' ],
+ ];
+ yield 'move and edit, edit with infinity expiry' => [
+ true,
+ [ 'move' => 'something', 'edit' => 'another' ],
+ [ 'edit' => 'infinity' ],
+ [ 'edit' => [ 'another' ], 'move' => [ 'something' ] ],
+ [ 'edit' => 'infinity' ],
+ ];
+ yield 'non existing, create something' => [
+ false,
+ [ 'create' => 'something' ],
+ [],
+ [ 'create' => [ 'something' ] ],
+ [],
+ ];
+ yield 'non existing, create something with expiry' => [
+ false,
+ [ 'create' => 'something' ],
+ [ 'create' => '23451212112233' ],
+ [ 'create' => [ 'something' ] ],
+ [ 'create' => '23451212112233' ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideTestDoUpdateRestrictions_setBasicRestrictions
+ * @covers WikiPage::doUpdateRestrictions
+ */
+ public function testDoUpdateRestrictions_setBasicRestrictions(
+ $pageExists,
+ array $limit,
+ array $expiry,
+ array $expectedRestrictions,
+ array $expectedRestrictionExpiries
+ ) {
+ if ( $pageExists ) {
+ $page = $this->createPage( __METHOD__, 'ABC' );
+ } else {
+ $page = new WikiPage( Title::newFromText( __METHOD__ . '-nonexist' ) );
+ }
+ $user = $this->getTestSysop()->getUser();
+ $cascade = false;
+
+ $status = $page->doUpdateRestrictions( $limit, $expiry, $cascade, 'aReason', $user, [] );
+
+ $logId = $status->getValue();
+ $allRestrictions = $page->getTitle()->getAllRestrictions();
+
+ $this->assertTrue( $status->isGood() );
+ $this->assertInternalType( 'int', $logId );
+ $this->assertSame( $expectedRestrictions, $allRestrictions );
+ foreach ( $expectedRestrictionExpiries as $key => $value ) {
+ $this->assertSame( $value, $page->getTitle()->getRestrictionExpiry( $key ) );
+ }
+
+ // Make sure the log entry looks good
+ // log_params is not checked here
+ $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
+ $this->assertSelect(
+ [ 'logging' ] + $actorQuery['tables'],
+ [
+ 'log_comment',
+ 'log_user' => $actorQuery['fields']['log_user'],
+ 'log_user_text' => $actorQuery['fields']['log_user_text'],
+ 'log_namespace',
+ 'log_title',
+ ],
+ [ 'log_id' => $logId ],
+ [ [
+ 'aReason',
+ (string)$user->getId(),
+ $user->getName(),
+ (string)$page->getTitle()->getNamespace(),
+ $page->getTitle()->getDBkey(),
+ ] ],
+ [],
+ $actorQuery['joins']
+ );
+ }
+
+ /**
+ * @covers WikiPage::doUpdateRestrictions
+ */
+ public function testDoUpdateRestrictions_failsOnReadOnly() {
+ $page = $this->createPage( __METHOD__, 'ABC' );
+ $user = $this->getTestSysop()->getUser();
+ $cascade = false;
+
+ // Set read only
+ $readOnly = $this->getMockBuilder( ReadOnlyMode::class )
+ ->disableOriginalConstructor()
+ ->setMethods( [ 'isReadOnly', 'getReason' ] )
+ ->getMock();
+ $readOnly->expects( $this->once() )
+ ->method( 'isReadOnly' )
+ ->will( $this->returnValue( true ) );
+ $readOnly->expects( $this->once() )
+ ->method( 'getReason' )
+ ->will( $this->returnValue( 'Some Read Only Reason' ) );
+ $this->setService( 'ReadOnlyMode', $readOnly );
+
+ $status = $page->doUpdateRestrictions( [], [], $cascade, 'aReason', $user, [] );
+ $this->assertFalse( $status->isOK() );
+ $this->assertSame( 'readonlytext', $status->getMessage()->getKey() );
+ }
+
+ /**
+ * @covers WikiPage::doUpdateRestrictions
+ */
+ public function testDoUpdateRestrictions_returnsGoodIfNothingChanged() {
+ $page = $this->createPage( __METHOD__, 'ABC' );
+ $user = $this->getTestSysop()->getUser();
+ $cascade = false;
+ $limit = [ 'edit' => 'sysop' ];
+
+ $status = $page->doUpdateRestrictions(
+ $limit,
+ [],
+ $cascade,
+ 'aReason',
+ $user,
+ []
+ );
+
+ // The first entry should have a logId as it did something
+ $this->assertTrue( $status->isGood() );
+ $this->assertInternalType( 'int', $status->getValue() );
+
+ $status = $page->doUpdateRestrictions(
+ $limit,
+ [],
+ $cascade,
+ 'aReason',
+ $user,
+ []
+ );
+
+ // The second entry should not have a logId as nothing changed
+ $this->assertTrue( $status->isGood() );
+ $this->assertNull( $status->getValue() );
+ }
+
+ /**
+ * @covers WikiPage::doUpdateRestrictions
+ */
+ public function testDoUpdateRestrictions_logEntryTypeAndAction() {
+ $page = $this->createPage( __METHOD__, 'ABC' );
+ $user = $this->getTestSysop()->getUser();
+ $cascade = false;
+
+ // Protect the page
+ $status = $page->doUpdateRestrictions(
+ [ 'edit' => 'sysop' ],
+ [],
+ $cascade,
+ 'aReason',
+ $user,
+ []
+ );
+ $this->assertTrue( $status->isGood() );
+ $this->assertInternalType( 'int', $status->getValue() );
+ $this->assertSelect(
+ 'logging',
+ [ 'log_type', 'log_action' ],
+ [ 'log_id' => $status->getValue() ],
+ [ [ 'protect', 'protect' ] ]
+ );
+
+ // Modify the protection
+ $status = $page->doUpdateRestrictions(
+ [ 'edit' => 'somethingElse' ],
+ [],
+ $cascade,
+ 'aReason',
+ $user,
+ []
+ );
+ $this->assertTrue( $status->isGood() );
+ $this->assertInternalType( 'int', $status->getValue() );
+ $this->assertSelect(
+ 'logging',
+ [ 'log_type', 'log_action' ],
+ [ 'log_id' => $status->getValue() ],
+ [ [ 'protect', 'modify' ] ]
+ );
+
+ // Remove the protection
+ $status = $page->doUpdateRestrictions(
+ [],
+ [],
+ $cascade,
+ 'aReason',
+ $user,
+ []
+ );
+ $this->assertTrue( $status->isGood() );
+ $this->assertInternalType( 'int', $status->getValue() );
+ $this->assertSelect(
+ 'logging',
+ [ 'log_type', 'log_action' ],
+ [ 'log_id' => $status->getValue() ],
+ [ [ 'protect', 'unprotect' ] ]
+ );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/page/WikiPageNoContentHandlerDbTest.php b/www/wiki/tests/phpunit/includes/page/WikiPageNoContentHandlerDbTest.php
new file mode 100644
index 00000000..a6ce185a
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/page/WikiPageNoContentHandlerDbTest.php
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * @group ContentHandler
+ * @group Database
+ * @group medium
+ */
+class WikiPageNoContentHandlerDbTest extends WikiPageDbTestBase {
+
+ protected function getContentHandlerUseDB() {
+ return false;
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/page/WikiPageTest.php b/www/wiki/tests/phpunit/includes/page/WikiPageTest.php
deleted file mode 100644
index 386f142d..00000000
--- a/www/wiki/tests/phpunit/includes/page/WikiPageTest.php
+++ /dev/null
@@ -1,1208 +0,0 @@
-<?php
-
-/**
- * @group ContentHandler
- * @group Database
- * ^--- important, causes temporary tables to be used instead of the real database
- * @group medium
- */
-class WikiPageTest extends MediaWikiLangTestCase {
-
- protected $pages_to_delete;
-
- function __construct( $name = null, array $data = [], $dataName = '' ) {
- parent::__construct( $name, $data, $dataName );
-
- $this->tablesUsed = array_merge(
- $this->tablesUsed,
- [ 'page',
- 'revision',
- 'archive',
- 'ip_changes',
- 'text',
-
- 'recentchanges',
- 'logging',
-
- 'page_props',
- 'pagelinks',
- 'categorylinks',
- 'langlinks',
- 'externallinks',
- 'imagelinks',
- 'templatelinks',
- 'iwlinks' ] );
- }
-
- protected function setUp() {
- parent::setUp();
- $this->pages_to_delete = [];
-
- LinkCache::singleton()->clear(); # avoid cached redirect status, etc
- }
-
- protected function tearDown() {
- foreach ( $this->pages_to_delete as $p ) {
- /* @var $p WikiPage */
-
- try {
- if ( $p->exists() ) {
- $p->doDeleteArticle( "testing done." );
- }
- } catch ( MWException $ex ) {
- // fail silently
- }
- }
- parent::tearDown();
- }
-
- /**
- * @param Title|string $title
- * @param string|null $model
- * @return WikiPage
- */
- protected function newPage( $title, $model = null ) {
- if ( is_string( $title ) ) {
- $ns = $this->getDefaultWikitextNS();
- $title = Title::newFromText( $title, $ns );
- }
-
- $p = new WikiPage( $title );
-
- $this->pages_to_delete[] = $p;
-
- return $p;
- }
-
- /**
- * @param string|Title|WikiPage $page
- * @param string $text
- * @param int $model
- *
- * @return WikiPage
- */
- protected function createPage( $page, $text, $model = null ) {
- if ( is_string( $page ) || $page instanceof Title ) {
- $page = $this->newPage( $page, $model );
- }
-
- $content = ContentHandler::makeContent( $text, $page->getTitle(), $model );
- $page->doEditContent( $content, "testing", EDIT_NEW );
-
- return $page;
- }
-
- /**
- * @covers WikiPage::doEditContent
- * @covers WikiPage::doModify
- * @covers WikiPage::doCreate
- * @covers WikiPage::doEditUpdates
- */
- public function testDoEditContent() {
- $page = $this->newPage( "WikiPageTest_testDoEditContent" );
- $title = $page->getTitle();
-
- $content = ContentHandler::makeContent(
- "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam "
- . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.",
- $title,
- CONTENT_MODEL_WIKITEXT
- );
-
- $page->doEditContent( $content, "[[testing]] 1" );
-
- $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" );
- $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" );
- $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" );
- $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" );
-
- $id = $page->getId();
-
- # ------------------------
- $dbr = wfGetDB( DB_REPLICA );
- $res = $dbr->select( 'pagelinks', '*', [ 'pl_from' => $id ] );
- $n = $res->numRows();
- $res->free();
-
- $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' );
-
- # ------------------------
- $page = new WikiPage( $title );
-
- $retrieved = $page->getContent();
- $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' );
-
- # ------------------------
- $content = ContentHandler::makeContent(
- "At vero eos et accusam et justo duo [[dolores]] et ea rebum. "
- . "Stet clita kasd [[gubergren]], no sea takimata sanctus est.",
- $title,
- CONTENT_MODEL_WIKITEXT
- );
-
- $page->doEditContent( $content, "testing 2" );
-
- # ------------------------
- $page = new WikiPage( $title );
-
- $retrieved = $page->getContent();
- $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' );
-
- # ------------------------
- $dbr = wfGetDB( DB_REPLICA );
- $res = $dbr->select( 'pagelinks', '*', [ 'pl_from' => $id ] );
- $n = $res->numRows();
- $res->free();
-
- $this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' );
- }
-
- /**
- * @covers WikiPage::doDeleteArticle
- */
- public function testDoDeleteArticle() {
- $page = $this->createPage(
- "WikiPageTest_testDoDeleteArticle",
- "[[original text]] foo",
- CONTENT_MODEL_WIKITEXT
- );
- $id = $page->getId();
-
- $page->doDeleteArticle( "testing deletion" );
-
- $this->assertFalse(
- $page->getTitle()->getArticleID() > 0,
- "Title object should now have page id 0"
- );
- $this->assertFalse( $page->getId() > 0, "WikiPage should now have page id 0" );
- $this->assertFalse(
- $page->exists(),
- "WikiPage::exists should return false after page was deleted"
- );
- $this->assertNull(
- $page->getContent(),
- "WikiPage::getContent should return null after page was deleted"
- );
-
- $t = Title::newFromText( $page->getTitle()->getPrefixedText() );
- $this->assertFalse(
- $t->exists(),
- "Title::exists should return false after page was deleted"
- );
-
- // Run the job queue
- JobQueueGroup::destroySingletons();
- $jobs = new RunJobs;
- $jobs->loadParamsAndArgs( null, [ 'quiet' => true ], null );
- $jobs->execute();
-
- # ------------------------
- $dbr = wfGetDB( DB_REPLICA );
- $res = $dbr->select( 'pagelinks', '*', [ 'pl_from' => $id ] );
- $n = $res->numRows();
- $res->free();
-
- $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' );
- }
-
- /**
- * @covers WikiPage::doDeleteUpdates
- */
- public function testDoDeleteUpdates() {
- $page = $this->createPage(
- "WikiPageTest_testDoDeleteArticle",
- "[[original text]] foo",
- CONTENT_MODEL_WIKITEXT
- );
- $id = $page->getId();
-
- // Similar to MovePage logic
- wfGetDB( DB_MASTER )->delete( 'page', [ 'page_id' => $id ], __METHOD__ );
- $page->doDeleteUpdates( $id );
-
- // Run the job queue
- JobQueueGroup::destroySingletons();
- $jobs = new RunJobs;
- $jobs->loadParamsAndArgs( null, [ 'quiet' => true ], null );
- $jobs->execute();
-
- # ------------------------
- $dbr = wfGetDB( DB_REPLICA );
- $res = $dbr->select( 'pagelinks', '*', [ 'pl_from' => $id ] );
- $n = $res->numRows();
- $res->free();
-
- $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' );
- }
-
- /**
- * @covers WikiPage::getRevision
- */
- public function testGetRevision() {
- $page = $this->newPage( "WikiPageTest_testGetRevision" );
-
- $rev = $page->getRevision();
- $this->assertNull( $rev );
-
- # -----------------
- $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
-
- $rev = $page->getRevision();
-
- $this->assertEquals( $page->getLatest(), $rev->getId() );
- $this->assertEquals( "some text", $rev->getContent()->getNativeData() );
- }
-
- /**
- * @covers WikiPage::getContent
- */
- public function testGetContent() {
- $page = $this->newPage( "WikiPageTest_testGetContent" );
-
- $content = $page->getContent();
- $this->assertNull( $content );
-
- # -----------------
- $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
-
- $content = $page->getContent();
- $this->assertEquals( "some text", $content->getNativeData() );
- }
-
- /**
- * @covers WikiPage::getContentModel
- */
- public function testGetContentModel() {
- global $wgContentHandlerUseDB;
-
- if ( !$wgContentHandlerUseDB ) {
- $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
- }
-
- $page = $this->createPage(
- "WikiPageTest_testGetContentModel",
- "some text",
- CONTENT_MODEL_JAVASCRIPT
- );
-
- $page = new WikiPage( $page->getTitle() );
- $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $page->getContentModel() );
- }
-
- /**
- * @covers WikiPage::getContentHandler
- */
- public function testGetContentHandler() {
- global $wgContentHandlerUseDB;
-
- if ( !$wgContentHandlerUseDB ) {
- $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
- }
-
- $page = $this->createPage(
- "WikiPageTest_testGetContentHandler",
- "some text",
- CONTENT_MODEL_JAVASCRIPT
- );
-
- $page = new WikiPage( $page->getTitle() );
- $this->assertEquals( 'JavaScriptContentHandler', get_class( $page->getContentHandler() ) );
- }
-
- /**
- * @covers WikiPage::exists
- */
- public function testExists() {
- $page = $this->newPage( "WikiPageTest_testExists" );
- $this->assertFalse( $page->exists() );
-
- # -----------------
- $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
- $this->assertTrue( $page->exists() );
-
- $page = new WikiPage( $page->getTitle() );
- $this->assertTrue( $page->exists() );
-
- # -----------------
- $page->doDeleteArticle( "done testing" );
- $this->assertFalse( $page->exists() );
-
- $page = new WikiPage( $page->getTitle() );
- $this->assertFalse( $page->exists() );
- }
-
- public static function provideHasViewableContent() {
- return [
- [ 'WikiPageTest_testHasViewableContent', false, true ],
- [ 'Special:WikiPageTest_testHasViewableContent', false ],
- [ 'MediaWiki:WikiPageTest_testHasViewableContent', false ],
- [ 'Special:Userlogin', true ],
- [ 'MediaWiki:help', true ],
- ];
- }
-
- /**
- * @dataProvider provideHasViewableContent
- * @covers WikiPage::hasViewableContent
- */
- public function testHasViewableContent( $title, $viewable, $create = false ) {
- $page = $this->newPage( $title );
- $this->assertEquals( $viewable, $page->hasViewableContent() );
-
- if ( $create ) {
- $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
- $this->assertTrue( $page->hasViewableContent() );
-
- $page = new WikiPage( $page->getTitle() );
- $this->assertTrue( $page->hasViewableContent() );
- }
- }
-
- public static function provideGetRedirectTarget() {
- return [
- [ 'WikiPageTest_testGetRedirectTarget_1', CONTENT_MODEL_WIKITEXT, "hello world", null ],
- [
- 'WikiPageTest_testGetRedirectTarget_2',
- CONTENT_MODEL_WIKITEXT,
- "#REDIRECT [[hello world]]",
- "Hello world"
- ],
- ];
- }
-
- /**
- * @dataProvider provideGetRedirectTarget
- * @covers WikiPage::getRedirectTarget
- */
- public function testGetRedirectTarget( $title, $model, $text, $target ) {
- $this->setMwGlobals( [
- 'wgCapitalLinks' => true,
- ] );
-
- $page = $this->createPage( $title, $text, $model );
-
- # sanity check, because this test seems to fail for no reason for some people.
- $c = $page->getContent();
- $this->assertEquals( 'WikitextContent', get_class( $c ) );
-
- # now, test the actual redirect
- $t = $page->getRedirectTarget();
- $this->assertEquals( $target, is_null( $t ) ? null : $t->getPrefixedText() );
- }
-
- /**
- * @dataProvider provideGetRedirectTarget
- * @covers WikiPage::isRedirect
- */
- public function testIsRedirect( $title, $model, $text, $target ) {
- $page = $this->createPage( $title, $text, $model );
- $this->assertEquals( !is_null( $target ), $page->isRedirect() );
- }
-
- public static function provideIsCountable() {
- return [
-
- // any
- [ 'WikiPageTest_testIsCountable',
- CONTENT_MODEL_WIKITEXT,
- '',
- 'any',
- true
- ],
- [ 'WikiPageTest_testIsCountable',
- CONTENT_MODEL_WIKITEXT,
- 'Foo',
- 'any',
- true
- ],
-
- // comma
- [ 'WikiPageTest_testIsCountable',
- CONTENT_MODEL_WIKITEXT,
- 'Foo',
- 'comma',
- false
- ],
- [ 'WikiPageTest_testIsCountable',
- CONTENT_MODEL_WIKITEXT,
- 'Foo, bar',
- 'comma',
- true
- ],
-
- // link
- [ 'WikiPageTest_testIsCountable',
- CONTENT_MODEL_WIKITEXT,
- 'Foo',
- 'link',
- false
- ],
- [ 'WikiPageTest_testIsCountable',
- CONTENT_MODEL_WIKITEXT,
- 'Foo [[bar]]',
- 'link',
- true
- ],
-
- // redirects
- [ 'WikiPageTest_testIsCountable',
- CONTENT_MODEL_WIKITEXT,
- '#REDIRECT [[bar]]',
- 'any',
- false
- ],
- [ 'WikiPageTest_testIsCountable',
- CONTENT_MODEL_WIKITEXT,
- '#REDIRECT [[bar]]',
- 'comma',
- false
- ],
- [ 'WikiPageTest_testIsCountable',
- CONTENT_MODEL_WIKITEXT,
- '#REDIRECT [[bar]]',
- 'link',
- false
- ],
-
- // not a content namespace
- [ 'Talk:WikiPageTest_testIsCountable',
- CONTENT_MODEL_WIKITEXT,
- 'Foo',
- 'any',
- false
- ],
- [ 'Talk:WikiPageTest_testIsCountable',
- CONTENT_MODEL_WIKITEXT,
- 'Foo, bar',
- 'comma',
- false
- ],
- [ 'Talk:WikiPageTest_testIsCountable',
- CONTENT_MODEL_WIKITEXT,
- 'Foo [[bar]]',
- 'link',
- false
- ],
-
- // not a content namespace, different model
- [ 'MediaWiki:WikiPageTest_testIsCountable.js',
- null,
- 'Foo',
- 'any',
- false
- ],
- [ 'MediaWiki:WikiPageTest_testIsCountable.js',
- null,
- 'Foo, bar',
- 'comma',
- false
- ],
- [ 'MediaWiki:WikiPageTest_testIsCountable.js',
- null,
- 'Foo [[bar]]',
- 'link',
- false
- ],
- ];
- }
-
- /**
- * @dataProvider provideIsCountable
- * @covers WikiPage::isCountable
- */
- public function testIsCountable( $title, $model, $text, $mode, $expected ) {
- global $wgContentHandlerUseDB;
-
- $this->setMwGlobals( 'wgArticleCountMethod', $mode );
-
- $title = Title::newFromText( $title );
-
- if ( !$wgContentHandlerUseDB
- && $model
- && ContentHandler::getDefaultModelFor( $title ) != $model
- ) {
- $this->markTestSkipped( "Can not use non-default content model $model for "
- . $title->getPrefixedDBkey() . " with \$wgContentHandlerUseDB disabled." );
- }
-
- $page = $this->createPage( $title, $text, $model );
-
- $editInfo = $page->prepareContentForEdit( $page->getContent() );
-
- $v = $page->isCountable();
- $w = $page->isCountable( $editInfo );
-
- $this->assertEquals(
- $expected,
- $v,
- "isCountable( null ) returned unexpected value " . var_export( $v, true )
- . " instead of " . var_export( $expected, true )
- . " in mode `$mode` for text \"$text\""
- );
-
- $this->assertEquals(
- $expected,
- $w,
- "isCountable( \$editInfo ) returned unexpected value " . var_export( $v, true )
- . " instead of " . var_export( $expected, true )
- . " in mode `$mode` for text \"$text\""
- );
- }
-
- public static function provideGetParserOutput() {
- return [
- [
- CONTENT_MODEL_WIKITEXT,
- "hello ''world''\n",
- "<div class=\"mw-parser-output\"><p>hello <i>world</i></p></div>"
- ],
- // @todo more...?
- ];
- }
-
- /**
- * @dataProvider provideGetParserOutput
- * @covers WikiPage::getParserOutput
- */
- public function testGetParserOutput( $model, $text, $expectedHtml ) {
- $page = $this->createPage( 'WikiPageTest_testGetParserOutput', $text, $model );
-
- $opt = $page->makeParserOptions( 'canonical' );
- $po = $page->getParserOutput( $opt );
- $text = $po->getText();
-
- $text = trim( preg_replace( '/<!--.*?-->/sm', '', $text ) ); # strip injected comments
- $text = preg_replace( '!\s*(</p>|</div>)!sm', '\1', $text ); # don't let tidy confuse us
-
- $this->assertEquals( $expectedHtml, $text );
-
- return $po;
- }
-
- /**
- * @covers WikiPage::getParserOutput
- */
- public function testGetParserOutput_nonexisting() {
- static $count = 0;
- $count++;
-
- $page = new WikiPage( new Title( "WikiPageTest_testGetParserOutput_nonexisting_$count" ) );
-
- $opt = new ParserOptions();
- $po = $page->getParserOutput( $opt );
-
- $this->assertFalse( $po, "getParserOutput() shall return false for non-existing pages." );
- }
-
- /**
- * @covers WikiPage::getParserOutput
- */
- public function testGetParserOutput_badrev() {
- $page = $this->createPage( 'WikiPageTest_testGetParserOutput', "dummy", CONTENT_MODEL_WIKITEXT );
-
- $opt = new ParserOptions();
- $po = $page->getParserOutput( $opt, $page->getLatest() + 1234 );
-
- // @todo would be neat to also test deleted revision
-
- $this->assertFalse( $po, "getParserOutput() shall return false for non-existing revisions." );
- }
-
- public static $sections =
-
- "Intro
-
-== stuff ==
-hello world
-
-== test ==
-just a test
-
-== foo ==
-more stuff
-";
-
- public function dataReplaceSection() {
- // NOTE: assume the Help namespace to contain wikitext
- return [
- [ 'Help:WikiPageTest_testReplaceSection',
- CONTENT_MODEL_WIKITEXT,
- self::$sections,
- "0",
- "No more",
- null,
- trim( preg_replace( '/^Intro/sm', 'No more', self::$sections ) )
- ],
- [ 'Help:WikiPageTest_testReplaceSection',
- CONTENT_MODEL_WIKITEXT,
- self::$sections,
- "",
- "No more",
- null,
- "No more"
- ],
- [ 'Help:WikiPageTest_testReplaceSection',
- CONTENT_MODEL_WIKITEXT,
- self::$sections,
- "2",
- "== TEST ==\nmore fun",
- null,
- trim( preg_replace( '/^== test ==.*== foo ==/sm',
- "== TEST ==\nmore fun\n\n== foo ==",
- self::$sections ) )
- ],
- [ 'Help:WikiPageTest_testReplaceSection',
- CONTENT_MODEL_WIKITEXT,
- self::$sections,
- "8",
- "No more",
- null,
- trim( self::$sections )
- ],
- [ 'Help:WikiPageTest_testReplaceSection',
- CONTENT_MODEL_WIKITEXT,
- self::$sections,
- "new",
- "No more",
- "New",
- trim( self::$sections ) . "\n\n== New ==\n\nNo more"
- ],
- ];
- }
-
- /**
- * @dataProvider dataReplaceSection
- * @covers WikiPage::replaceSectionContent
- */
- public function testReplaceSectionContent( $title, $model, $text, $section,
- $with, $sectionTitle, $expected
- ) {
- $page = $this->createPage( $title, $text, $model );
-
- $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() );
- $c = $page->replaceSectionContent( $section, $content, $sectionTitle );
-
- $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) );
- }
-
- /**
- * @dataProvider dataReplaceSection
- * @covers WikiPage::replaceSectionAtRev
- */
- public function testReplaceSectionAtRev( $title, $model, $text, $section,
- $with, $sectionTitle, $expected
- ) {
- $page = $this->createPage( $title, $text, $model );
- $baseRevId = $page->getLatest();
-
- $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() );
- $c = $page->replaceSectionAtRev( $section, $content, $sectionTitle, $baseRevId );
-
- $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) );
- }
-
- /* @todo FIXME: fix this!
- public function testGetUndoText() {
- $this->markTestSkippedIfNoDiff3();
-
- $text = "one";
- $page = $this->createPage( "WikiPageTest_testGetUndoText", $text );
- $rev1 = $page->getRevision();
-
- $text .= "\n\ntwo";
- $page->doEditContent(
- ContentHandler::makeContent( $text, $page->getTitle() ),
- "adding section two"
- );
- $rev2 = $page->getRevision();
-
- $text .= "\n\nthree";
- $page->doEditContent(
- ContentHandler::makeContent( $text, $page->getTitle() ),
- "adding section three"
- );
- $rev3 = $page->getRevision();
-
- $text .= "\n\nfour";
- $page->doEditContent(
- ContentHandler::makeContent( $text, $page->getTitle() ),
- "adding section four"
- );
- $rev4 = $page->getRevision();
-
- $text .= "\n\nfive";
- $page->doEditContent(
- ContentHandler::makeContent( $text, $page->getTitle() ),
- "adding section five"
- );
- $rev5 = $page->getRevision();
-
- $text .= "\n\nsix";
- $page->doEditContent(
- ContentHandler::makeContent( $text, $page->getTitle() ),
- "adding section six"
- );
- $rev6 = $page->getRevision();
-
- $undo6 = $page->getUndoText( $rev6 );
- if ( $undo6 === false ) $this->fail( "getUndoText failed for rev6" );
- $this->assertEquals( "one\n\ntwo\n\nthree\n\nfour\n\nfive", $undo6 );
-
- $undo3 = $page->getUndoText( $rev4, $rev2 );
- if ( $undo3 === false ) $this->fail( "getUndoText failed for rev4..rev2" );
- $this->assertEquals( "one\n\ntwo\n\nfive", $undo3 );
-
- $undo2 = $page->getUndoText( $rev2 );
- if ( $undo2 === false ) $this->fail( "getUndoText failed for rev2" );
- $this->assertEquals( "one\n\nfive", $undo2 );
- }
- */
-
- /**
- * @covers WikiPage::getOldestRevision
- */
- public function testGetOldestRevision() {
- $page = $this->newPage( "WikiPageTest_testGetOldestRevision" );
- $page->doEditContent(
- new WikitextContent( 'one' ),
- "first edit",
- EDIT_NEW
- );
- $rev1 = $page->getRevision();
-
- $page = new WikiPage( $page->getTitle() );
- $page->doEditContent(
- new WikitextContent( 'two' ),
- "second edit",
- EDIT_UPDATE
- );
-
- $page = new WikiPage( $page->getTitle() );
- $page->doEditContent(
- new WikitextContent( 'three' ),
- "third edit",
- EDIT_UPDATE
- );
-
- // sanity check
- $this->assertNotEquals(
- $rev1->getId(),
- $page->getRevision()->getId(),
- '$page->getRevision()->getId()'
- );
-
- // actual test
- $this->assertEquals(
- $rev1->getId(),
- $page->getOldestRevision()->getId(),
- '$page->getOldestRevision()->getId()'
- );
- }
-
- /**
- * @todo FIXME: this is a better rollback test than the one below, but it
- * keeps failing in jenkins for some reason.
- */
- public function broken_testDoRollback() {
- $admin = new User();
- $admin->setName( "Admin" );
-
- $text = "one";
- $page = $this->newPage( "WikiPageTest_testDoRollback" );
- $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
- "section one", EDIT_NEW, false, $admin );
-
- $user1 = new User();
- $user1->setName( "127.0.1.11" );
- $text .= "\n\ntwo";
- $page = new WikiPage( $page->getTitle() );
- $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
- "adding section two", 0, false, $user1 );
-
- $user2 = new User();
- $user2->setName( "127.0.2.13" );
- $text .= "\n\nthree";
- $page = new WikiPage( $page->getTitle() );
- $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
- "adding section three", 0, false, $user2 );
-
- # we are having issues with doRollback spuriously failing. Apparently
- # the last revision somehow goes missing or not committed under some
- # circumstances. So, make sure the last revision has the right user name.
- $dbr = wfGetDB( DB_REPLICA );
- $this->assertEquals( 3, Revision::countByPageId( $dbr, $page->getId() ) );
-
- $page = new WikiPage( $page->getTitle() );
- $rev3 = $page->getRevision();
- $this->assertEquals( '127.0.2.13', $rev3->getUserText() );
-
- $rev2 = $rev3->getPrevious();
- $this->assertEquals( '127.0.1.11', $rev2->getUserText() );
-
- $rev1 = $rev2->getPrevious();
- $this->assertEquals( 'Admin', $rev1->getUserText() );
-
- # now, try the actual rollback
- $admin->addToDatabase();
- $admin->addGroup( "sysop" ); # XXX: make the test user a sysop...
- $token = $admin->getEditToken(
- [ $page->getTitle()->getPrefixedText(), $user2->getName() ],
- null
- );
- $errors = $page->doRollback(
- $user2->getName(),
- "testing revert",
- $token,
- false,
- $details,
- $admin
- );
-
- if ( $errors ) {
- $this->fail( "Rollback failed:\n" . print_r( $errors, true )
- . ";\n" . print_r( $details, true ) );
- }
-
- $page = new WikiPage( $page->getTitle() );
- $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(),
- "rollback did not revert to the correct revision" );
- $this->assertEquals( "one\n\ntwo", $page->getContent()->getNativeData() );
- }
-
- /**
- * @todo FIXME: the above rollback test is better, but it keeps failing in jenkins for some reason.
- * @covers WikiPage::doRollback
- */
- public function testDoRollback() {
- $admin = new User();
- $admin->setName( "Admin" );
- $admin->addToDatabase();
-
- $text = "one";
- $page = $this->newPage( "WikiPageTest_testDoRollback" );
- $page->doEditContent(
- ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
- "section one",
- EDIT_NEW,
- false,
- $admin
- );
- $rev1 = $page->getRevision();
-
- $user1 = new User();
- $user1->setName( "127.0.1.11" );
- $text .= "\n\ntwo";
- $page = new WikiPage( $page->getTitle() );
- $page->doEditContent(
- ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
- "adding section two",
- 0,
- false,
- $user1
- );
-
- # now, try the rollback
- $admin->addGroup( "sysop" ); # XXX: make the test user a sysop...
- $token = $admin->getEditToken( 'rollback' );
- $errors = $page->doRollback(
- $user1->getName(),
- "testing revert",
- $token,
- false,
- $details,
- $admin
- );
-
- if ( $errors ) {
- $this->fail( "Rollback failed:\n" . print_r( $errors, true )
- . ";\n" . print_r( $details, true ) );
- }
-
- $page = new WikiPage( $page->getTitle() );
- $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(),
- "rollback did not revert to the correct revision" );
- $this->assertEquals( "one", $page->getContent()->getNativeData() );
- }
-
- /**
- * @covers WikiPage::doRollback
- */
- public function testDoRollbackFailureSameContent() {
- $admin = new User();
- $admin->setName( "Admin" );
- $admin->addToDatabase();
- $admin->addGroup( "sysop" ); # XXX: make the test user a sysop...
-
- $text = "one";
- $page = $this->newPage( "WikiPageTest_testDoRollback" );
- $page->doEditContent(
- ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
- "section one",
- EDIT_NEW,
- false,
- $admin
- );
- $rev1 = $page->getRevision();
-
- $user1 = new User();
- $user1->setName( "127.0.1.11" );
- $user1->addToDatabase();
- $user1->addGroup( "sysop" ); # XXX: make the test user a sysop...
- $text .= "\n\ntwo";
- $page = new WikiPage( $page->getTitle() );
- $page->doEditContent(
- ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
- "adding section two",
- 0,
- false,
- $user1
- );
-
- # now, do a the rollback from the same user was doing the edit before
- $resultDetails = [];
- $token = $user1->getEditToken( 'rollback' );
- $errors = $page->doRollback(
- $user1->getName(),
- "testing revert same user",
- $token,
- false,
- $resultDetails,
- $admin
- );
-
- $this->assertEquals( [], $errors, "Rollback failed same user" );
-
- # now, try the rollback
- $resultDetails = [];
- $token = $admin->getEditToken( 'rollback' );
- $errors = $page->doRollback(
- $user1->getName(),
- "testing revert",
- $token,
- false,
- $resultDetails,
- $admin
- );
-
- $this->assertEquals( [ [ 'alreadyrolled', 'WikiPageTest testDoRollback',
- '127.0.1.11', 'Admin' ] ], $errors, "Rollback not failed" );
-
- $page = new WikiPage( $page->getTitle() );
- $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(),
- "rollback did not revert to the correct revision" );
- $this->assertEquals( "one", $page->getContent()->getNativeData() );
- }
-
- public static function provideGetAutoDeleteReason() {
- return [
- [
- [],
- false,
- false
- ],
-
- [
- [
- [ "first edit", null ],
- ],
- "/first edit.*only contributor/",
- false
- ],
-
- [
- [
- [ "first edit", null ],
- [ "second edit", null ],
- ],
- "/second edit.*only contributor/",
- true
- ],
-
- [
- [
- [ "first edit", "127.0.2.22" ],
- [ "second edit", "127.0.3.33" ],
- ],
- "/second edit/",
- true
- ],
-
- [
- [
- [
- "first edit: "
- . "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam "
- . " nonumy eirmod tempor invidunt ut labore et dolore magna "
- . "aliquyam erat, sed diam voluptua. At vero eos et accusam "
- . "et justo duo dolores et ea rebum. Stet clita kasd gubergren, "
- . "no sea takimata sanctus est Lorem ipsum dolor sit amet.'",
- null
- ],
- ],
- '/first edit:.*\.\.\."/',
- false
- ],
-
- [
- [
- [ "first edit", "127.0.2.22" ],
- [ "", "127.0.3.33" ],
- ],
- "/before blanking.*first edit/",
- true
- ],
-
- ];
- }
-
- /**
- * @dataProvider provideGetAutoDeleteReason
- * @covers WikiPage::getAutoDeleteReason
- */
- public function testGetAutoDeleteReason( $edits, $expectedResult, $expectedHistory ) {
- global $wgUser;
-
- // NOTE: assume Help namespace to contain wikitext
- $page = $this->newPage( "Help:WikiPageTest_testGetAutoDeleteReason" );
-
- $c = 1;
-
- foreach ( $edits as $edit ) {
- $user = new User();
-
- if ( !empty( $edit[1] ) ) {
- $user->setName( $edit[1] );
- } else {
- $user = $wgUser;
- }
-
- $content = ContentHandler::makeContent( $edit[0], $page->getTitle(), $page->getContentModel() );
-
- $page->doEditContent( $content, "test edit $c", $c < 2 ? EDIT_NEW : 0, false, $user );
-
- $c += 1;
- }
-
- $reason = $page->getAutoDeleteReason( $hasHistory );
-
- if ( is_bool( $expectedResult ) || is_null( $expectedResult ) ) {
- $this->assertEquals( $expectedResult, $reason );
- } else {
- $this->assertTrue( (bool)preg_match( $expectedResult, $reason ),
- "Autosummary didn't match expected pattern $expectedResult: $reason" );
- }
-
- $this->assertEquals( $expectedHistory, $hasHistory,
- "expected \$hasHistory to be " . var_export( $expectedHistory, true ) );
-
- $page->doDeleteArticle( "done" );
- }
-
- public static function providePreSaveTransform() {
- return [
- [ 'hello this is ~~~',
- "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
- ],
- [ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
- 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
- ],
- ];
- }
-
- /**
- * @covers WikiPage::factory
- */
- public function testWikiPageFactory() {
- $title = Title::makeTitle( NS_FILE, 'Someimage.png' );
- $page = WikiPage::factory( $title );
- $this->assertEquals( 'WikiFilePage', get_class( $page ) );
-
- $title = Title::makeTitle( NS_CATEGORY, 'SomeCategory' );
- $page = WikiPage::factory( $title );
- $this->assertEquals( 'WikiCategoryPage', get_class( $page ) );
-
- $title = Title::makeTitle( NS_MAIN, 'SomePage' );
- $page = WikiPage::factory( $title );
- $this->assertEquals( 'WikiPage', get_class( $page ) );
- }
-
- /**
- * @dataProvider provideCommentMigrationOnDeletion
- * @param int $wstage
- * @param int $rstage
- */
- public function testCommentMigrationOnDeletion( $wstage, $rstage ) {
- $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', $wstage );
- $dbr = wfGetDB( DB_REPLICA );
-
- $page = $this->createPage(
- "WikiPageTest_testCommentMigrationOnDeletion",
- "foo",
- CONTENT_MODEL_WIKITEXT
- );
- $revid = $page->getLatest();
- if ( $wstage > MIGRATION_OLD ) {
- $comment_id = $dbr->selectField(
- 'revision_comment_temp',
- 'revcomment_comment_id',
- [ 'revcomment_rev' => $revid ],
- __METHOD__
- );
- }
-
- $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', $rstage );
-
- $page->doDeleteArticle( "testing deletion" );
-
- if ( $rstage > MIGRATION_OLD ) {
- // Didn't leave behind any 'revision_comment_temp' rows
- $n = $dbr->selectField(
- 'revision_comment_temp', 'COUNT(*)', [ 'revcomment_rev' => $revid ], __METHOD__
- );
- $this->assertEquals( 0, $n, 'no entry in revision_comment_temp after deletion' );
-
- // Copied or upgraded the comment_id, as applicable
- $ar_comment_id = $dbr->selectField(
- 'archive',
- 'ar_comment_id',
- [ 'ar_rev_id' => $revid ],
- __METHOD__
- );
- if ( $wstage > MIGRATION_OLD ) {
- $this->assertSame( $comment_id, $ar_comment_id );
- } else {
- $this->assertNotEquals( 0, $ar_comment_id );
- }
- }
-
- // Copied rev_comment, if applicable
- if ( $rstage <= MIGRATION_WRITE_BOTH && $wstage <= MIGRATION_WRITE_BOTH ) {
- $ar_comment = $dbr->selectField(
- 'archive',
- 'ar_comment',
- [ 'ar_rev_id' => $revid ],
- __METHOD__
- );
- $this->assertSame( 'testing', $ar_comment );
- }
- }
-
- public static function provideCommentMigrationOnDeletion() {
- return [
- [ MIGRATION_OLD, MIGRATION_OLD ],
- [ MIGRATION_OLD, MIGRATION_WRITE_BOTH ],
- [ MIGRATION_OLD, MIGRATION_WRITE_NEW ],
- [ MIGRATION_WRITE_BOTH, MIGRATION_OLD ],
- [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_BOTH ],
- [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
- [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
- [ MIGRATION_WRITE_NEW, MIGRATION_WRITE_BOTH ],
- [ MIGRATION_WRITE_NEW, MIGRATION_WRITE_NEW ],
- [ MIGRATION_WRITE_NEW, MIGRATION_NEW ],
- [ MIGRATION_NEW, MIGRATION_WRITE_BOTH ],
- [ MIGRATION_NEW, MIGRATION_WRITE_NEW ],
- [ MIGRATION_NEW, MIGRATION_NEW ],
- ];
- }
-
-}
diff --git a/www/wiki/tests/phpunit/includes/page/WikiPageTestContentHandlerUseDB.php b/www/wiki/tests/phpunit/includes/page/WikiPageTestContentHandlerUseDB.php
deleted file mode 100644
index 3db76280..00000000
--- a/www/wiki/tests/phpunit/includes/page/WikiPageTestContentHandlerUseDB.php
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-
-/**
- * @group ContentHandler
- * @group Database
- * ^--- important, causes temporary tables to be used instead of the real database
- */
-class WikiPageTestContentHandlerUseDB extends WikiPageTest {
-
- protected function setUp() {
- parent::setUp();
- $this->setMwGlobals( 'wgContentHandlerUseDB', false );
-
- $dbw = wfGetDB( DB_MASTER );
-
- $page_table = $dbw->tableName( 'page' );
- $revision_table = $dbw->tableName( 'revision' );
- $archive_table = $dbw->tableName( 'archive' );
-
- if ( $dbw->fieldExists( $page_table, 'page_content_model' ) ) {
- $dbw->query( "alter table $page_table drop column page_content_model" );
- $dbw->query( "alter table $revision_table drop column rev_content_model" );
- $dbw->query( "alter table $revision_table drop column rev_content_format" );
- $dbw->query( "alter table $archive_table drop column ar_content_model" );
- $dbw->query( "alter table $archive_table drop column ar_content_format" );
- }
- }
-
- /**
- * @covers WikiPage::getContentModel
- */
- public function testGetContentModel() {
- $page = $this->createPage(
- "WikiPageTest_testGetContentModel",
- "some text",
- CONTENT_MODEL_JAVASCRIPT
- );
-
- $page = new WikiPage( $page->getTitle() );
-
- // NOTE: since the content model is not recorded in the database,
- // we expect to get the default, namely CONTENT_MODEL_WIKITEXT
- $this->assertEquals( CONTENT_MODEL_WIKITEXT, $page->getContentModel() );
- }
-
- /**
- * @covers WikiPage::getContentHandler
- */
- public function testGetContentHandler() {
- $page = $this->createPage(
- "WikiPageTest_testGetContentHandler",
- "some text",
- CONTENT_MODEL_JAVASCRIPT
- );
-
- // NOTE: since the content model is not recorded in the database,
- // we expect to get the default, namely CONTENT_MODEL_WIKITEXT
- $page = new WikiPage( $page->getTitle() );
- $this->assertEquals( 'WikitextContentHandler', get_class( $page->getContentHandler() ) );
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/pager/RangeChronologicalPagerTest.php b/www/wiki/tests/phpunit/includes/pager/RangeChronologicalPagerTest.php
index 4721ce6f..72390ac8 100644
--- a/www/wiki/tests/phpunit/includes/pager/RangeChronologicalPagerTest.php
+++ b/www/wiki/tests/phpunit/includes/pager/RangeChronologicalPagerTest.php
@@ -14,7 +14,7 @@ class RangeChronologicalPagerTest extends MediaWikiLangTestCase {
* @dataProvider getDateCondProvider
*/
public function testGetDateCond( $inputYear, $inputMonth, $inputDay, $expected ) {
- $pager = $this->getMockForAbstractClass( 'RangeChronologicalPager' );
+ $pager = $this->getMockForAbstractClass( RangeChronologicalPager::class );
$this->assertEquals(
$expected,
wfTimestamp( TS_MW, $pager->getDateCond( $inputYear, $inputMonth, $inputDay ) )
@@ -42,7 +42,7 @@ class RangeChronologicalPagerTest extends MediaWikiLangTestCase {
* @dataProvider getDateRangeCondProvider
*/
public function testGetDateRangeCond( $start, $end, $expected ) {
- $pager = $this->getMockForAbstractClass( 'RangeChronologicalPager' );
+ $pager = $this->getMockForAbstractClass( RangeChronologicalPager::class );
$this->assertArrayEquals( $expected, $pager->getDateRangeCond( $start, $end ) );
}
@@ -84,7 +84,7 @@ class RangeChronologicalPagerTest extends MediaWikiLangTestCase {
* @dataProvider getDateRangeCondInvalidProvider
*/
public function testGetDateRangeCondInvalid( $start, $end ) {
- $pager = $this->getMockForAbstractClass( 'RangeChronologicalPager' );
+ $pager = $this->getMockForAbstractClass( RangeChronologicalPager::class );
$this->assertEquals( null, $pager->getDateRangeCond( $start, $end ) );
}
diff --git a/www/wiki/tests/phpunit/includes/pager/ReverseChronologicalPagerTest.php b/www/wiki/tests/phpunit/includes/pager/ReverseChronologicalPagerTest.php
index eb163d38..3910ab64 100644
--- a/www/wiki/tests/phpunit/includes/pager/ReverseChronologicalPagerTest.php
+++ b/www/wiki/tests/phpunit/includes/pager/ReverseChronologicalPagerTest.php
@@ -13,7 +13,7 @@ class ReverseChronologicalPagerTest extends MediaWikiLangTestCase {
* @covers ReverseChronologicalPager::getDateCond
*/
public function testGetDateCond() {
- $pager = $this->getMockForAbstractClass( 'ReverseChronologicalPager' );
+ $pager = $this->getMockForAbstractClass( ReverseChronologicalPager::class );
$timestamp = MWTimestamp::getInstance();
$db = wfGetDB( DB_MASTER );
diff --git a/www/wiki/tests/phpunit/includes/parser/CoreParserFunctionsTest.php b/www/wiki/tests/phpunit/includes/parser/CoreParserFunctionsTest.php
new file mode 100644
index 00000000..c6304477
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/parser/CoreParserFunctionsTest.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * @group Database
+ * @covers CoreParserFunctions
+ */
+class CoreParserFunctionsTest extends MediaWikiTestCase {
+
+ public function testGender() {
+ $user = User::createNew( '*Female' );
+ $user->setOption( 'gender', 'female' );
+ $user->saveSettings();
+
+ $msg = ( new RawMessage( '{{GENDER:*Female|m|f|o}}' ) )->parse();
+ $this->assertEquals( $msg, 'f', 'Works unescaped' );
+ $escapedName = wfEscapeWikiText( '*Female' );
+ $msg2 = ( new RawMessage( '{{GENDER:' . $escapedName . '|m|f|o}}' ) )
+ ->parse();
+ $this->assertEquals( $msg, 'f', 'Works escaped' );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/parser/MagicVariableTest.php b/www/wiki/tests/phpunit/includes/parser/MagicVariableTest.php
index 6a2afad6..86b496e2 100644
--- a/www/wiki/tests/phpunit/includes/parser/MagicVariableTest.php
+++ b/www/wiki/tests/phpunit/includes/parser/MagicVariableTest.php
@@ -9,11 +9,12 @@
* @author Antoine Musso
* @copyright Copyright © 2011, Antoine Musso
* @file
- * @todo covers tags
- *
- * @group Database
*/
+/**
+ * @group Database
+ * @covers Parser::getVariableValue
+ */
class MagicVariableTest extends MediaWikiTestCase {
/**
* @var Parser
diff --git a/www/wiki/tests/phpunit/includes/parser/MediaWikiParserTest.php b/www/wiki/tests/phpunit/includes/parser/MediaWikiParserTest.php
deleted file mode 100644
index 173447fc..00000000
--- a/www/wiki/tests/phpunit/includes/parser/MediaWikiParserTest.php
+++ /dev/null
@@ -1,138 +0,0 @@
-<?php
-require_once __DIR__ . '/NewParserTest.php';
-
-/**
- * The UnitTest must be either a class that inherits from MediaWikiTestCase
- * or a class that provides a public static suite() method which returns
- * an PHPUnit_Framework_Test object
- *
- * @group Parser
- * @group ParserTests
- * @group Database
- */
-class MediaWikiParserTest {
-
- /**
- * @defgroup filtering_constants Filtering constants
- *
- * Limit inclusion of parser tests files coming from MediaWiki core
- * @{
- */
-
- /** Include files shipped with MediaWiki core */
- const CORE_ONLY = 1;
- /** Include non core files as set in $wgParserTestFiles */
- const NO_CORE = 2;
- /** Include anything set via $wgParserTestFiles */
- const WITH_ALL = 3; # CORE_ONLY | NO_CORE
-
- /** @} */
-
- /**
- * Get a PHPUnit test suite of parser tests. Optionally filtered with
- * $flags.
- *
- * @par Examples:
- * Get a suite of parser tests shipped by MediaWiki core:
- * @code
- * MediaWikiParserTest::suite( MediaWikiParserTest::CORE_ONLY );
- * @endcode
- * Get a suite of various parser tests, like extensions:
- * @code
- * MediaWikiParserTest::suite( MediaWikiParserTest::NO_CORE );
- * @endcode
- * Get any test defined via $wgParserTestFiles:
- * @code
- * MediaWikiParserTest::suite( MediaWikiParserTest::WITH_ALL );
- * @endcode
- *
- * @param int $flags Bitwise flag to filter out the $wgParserTestFiles that
- * will be included. Default: MediaWikiParserTest::CORE_ONLY
- *
- * @return PHPUnit_Framework_TestSuite
- */
- public static function suite( $flags = self::CORE_ONLY ) {
- if ( is_string( $flags ) ) {
- $flags = self::CORE_ONLY;
- }
- global $wgParserTestFiles, $IP;
-
- $mwTestDir = $IP . '/tests/';
-
- # Human friendly helpers
- $wantsCore = ( $flags & self::CORE_ONLY );
- $wantsRest = ( $flags & self::NO_CORE );
-
- # Will hold the .txt parser test files we will include
- $filesToTest = [];
-
- # Filter out .txt files
- foreach ( $wgParserTestFiles as $parserTestFile ) {
- $isCore = ( 0 === strpos( $parserTestFile, $mwTestDir ) );
-
- if ( $isCore && $wantsCore ) {
- self::debug( "included core parser tests: $parserTestFile" );
- $filesToTest[] = $parserTestFile;
- } elseif ( !$isCore && $wantsRest ) {
- self::debug( "included non core parser tests: $parserTestFile" );
- $filesToTest[] = $parserTestFile;
- } else {
- self::debug( "skipped parser tests: $parserTestFile" );
- }
- }
- self::debug( 'parser tests files: '
- . implode( ' ', $filesToTest ) );
-
- $suite = new PHPUnit_Framework_TestSuite;
- $testList = [];
- $counter = 0;
- foreach ( $filesToTest as $fileName ) {
- // Call the highest level directory the extension name.
- // It may or may not actually be, but it should be close
- // enough to cause there to be separate names for different
- // things, which is good enough for our purposes.
- $extensionName = basename( dirname( $fileName ) );
- $testsName = $extensionName . '__' . basename( $fileName, '.txt' );
- $escapedFileName = strtr( $fileName, [ "'" => "\\'", '\\' => '\\\\' ] );
- $parserTestClassName = ucfirst( $testsName );
-
- // Official spec for class names: http://php.net/manual/en/language.oop5.basic.php
- // Prepend 'ParserTest_' to be paranoid about it not starting with a number
- $parserTestClassName = 'ParserTest_' .
- preg_replace( '/[^a-zA-Z0-9_\x7f-\xff]/', '_', $parserTestClassName );
-
- if ( isset( $testList[$parserTestClassName] ) ) {
- // If a conflict happens, gives a very unclear fatal.
- // So as a last ditch effort to prevent that eventuality, if there
- // is a conflict, append a number.
- $counter++;
- $parserTestClassName .= $counter;
- }
- $testList[$parserTestClassName] = true;
- $parserTestClassDefinition = <<<EOT
-/**
- * @group Database
- * @group Parser
- * @group ParserTests
- * @group ParserTests_$parserTestClassName
- */
-class $parserTestClassName extends NewParserTest {
- protected \$file = '$escapedFileName';
-}
-EOT;
-
- eval( $parserTestClassDefinition );
- self::debug( "Adding test class $parserTestClassName" );
- $suite->addTestSuite( $parserTestClassName );
- }
- return $suite;
- }
-
- /**
- * Write $msg under log group 'tests-parser'
- * @param string $msg Message to log
- */
- protected static function debug( $msg ) {
- return wfDebugLog( 'tests-parser', wfGetCaller() . ' ' . $msg );
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/parser/NewParserTest.php b/www/wiki/tests/phpunit/includes/parser/NewParserTest.php
deleted file mode 100644
index 8be35a5d..00000000
--- a/www/wiki/tests/phpunit/includes/parser/NewParserTest.php
+++ /dev/null
@@ -1,1123 +0,0 @@
-<?php
-
-/**
- * Although marked as a stub, can work independently.
- *
- * @group Database
- * @group Parser
- * @group Stub
- *
- * @todo covers tags
- */
-class NewParserTest extends MediaWikiTestCase {
- static protected $articles = []; // Array of test articles defined by the tests
- /* The data provider is run on a different instance than the test, so it must be static
- * When running tests from several files, all tests will see all articles.
- */
- static protected $backendToUse;
-
- public $keepUploads = false;
- public $runDisabled = false;
- public $runParsoid = false;
- public $regex = '';
- public $showProgress = true;
- public $savedWeirdGlobals = [];
- public $savedGlobals = [];
- public $hooks = [];
- public $functionHooks = [];
- public $transparentHooks = [];
-
- // Fuzz test
- public $maxFuzzTestLength = 300;
- public $fuzzSeed = 0;
- public $memoryLimit = 50;
-
- /**
- * @var DjVuSupport
- */
- private $djVuSupport;
- /**
- * @var TidySupport
- */
- private $tidySupport;
-
- protected $file = false;
-
- public static function setUpBeforeClass() {
- // Inject ParserTest well-known interwikis
- ParserTest::setupInterwikis();
- }
-
- protected function setUp() {
- global $wgNamespaceAliases, $wgContLang;
- global $wgHooks, $IP;
-
- parent::setUp();
-
- // Setup CLI arguments
- if ( $this->getCliArg( 'regex' ) ) {
- $this->regex = $this->getCliArg( 'regex' );
- } else {
- # Matches anything
- $this->regex = '';
- }
-
- $this->keepUploads = $this->getCliArg( 'keep-uploads' );
-
- $tmpGlobals = [];
-
- $tmpGlobals['wgLanguageCode'] = 'en';
- $tmpGlobals['wgContLang'] = Language::factory( 'en' );
- $tmpGlobals['wgSitename'] = 'MediaWiki';
- $tmpGlobals['wgServer'] = 'http://example.org';
- $tmpGlobals['wgServerName'] = 'example.org';
- $tmpGlobals['wgScriptPath'] = '';
- $tmpGlobals['wgScript'] = '/index.php';
- $tmpGlobals['wgResourceBasePath'] = '';
- $tmpGlobals['wgStylePath'] = '/skins';
- $tmpGlobals['wgExtensionAssetsPath'] = '/extensions';
- $tmpGlobals['wgArticlePath'] = '/wiki/$1';
- $tmpGlobals['wgActionPaths'] = [];
- $tmpGlobals['wgVariantArticlePath'] = false;
- $tmpGlobals['wgEnableUploads'] = true;
- $tmpGlobals['wgUploadNavigationUrl'] = false;
- $tmpGlobals['wgThumbnailScriptPath'] = false;
- $tmpGlobals['wgLocalFileRepo'] = [
- 'class' => 'LocalRepo',
- 'name' => 'local',
- 'url' => 'http://example.com/images',
- 'hashLevels' => 2,
- 'transformVia404' => false,
- 'backend' => 'local-backend'
- ];
- $tmpGlobals['wgForeignFileRepos'] = [];
- $tmpGlobals['wgDefaultExternalStore'] = [];
- $tmpGlobals['wgParserCacheType'] = CACHE_NONE;
- $tmpGlobals['wgCapitalLinks'] = true;
- $tmpGlobals['wgNoFollowLinks'] = true;
- $tmpGlobals['wgNoFollowDomainExceptions'] = [];
- $tmpGlobals['wgExternalLinkTarget'] = false;
- $tmpGlobals['wgThumbnailScriptPath'] = false;
- $tmpGlobals['wgUseImageResize'] = true;
- $tmpGlobals['wgAllowExternalImages'] = true;
- $tmpGlobals['wgRawHtml'] = false;
- $tmpGlobals['wgExperimentalHtmlIds'] = false;
- $tmpGlobals['wgAdaptiveMessageCache'] = true;
- $tmpGlobals['wgUseDatabaseMessages'] = true;
- $tmpGlobals['wgLocaltimezone'] = 'UTC';
- $tmpGlobals['wgGroupPermissions'] = [
- '*' => [
- 'createaccount' => true,
- 'read' => true,
- 'edit' => true,
- 'createpage' => true,
- 'createtalk' => true,
- ] ];
- $tmpGlobals['wgNamespaceProtection'] = [ NS_MEDIAWIKI => 'editinterface' ];
-
- $tmpGlobals['wgParser'] = new StubObject(
- 'wgParser', $GLOBALS['wgParserConf']['class'],
- [ $GLOBALS['wgParserConf'] ] );
-
- $tmpGlobals['wgFileExtensions'][] = 'svg';
- $tmpGlobals['wgSVGConverter'] = 'rsvg';
- $tmpGlobals['wgSVGConverters']['rsvg'] =
- '$path/rsvg-convert -w $width -h $height -o $output $input';
-
- if ( $GLOBALS['wgStyleDirectory'] === false ) {
- $tmpGlobals['wgStyleDirectory'] = "$IP/skins";
- }
-
- # Replace all media handlers with a mock. We do not need to generate
- # actual thumbnails to do parser testing, we only care about receiving
- # a ThumbnailImage properly initialized.
- global $wgMediaHandlers;
- foreach ( $wgMediaHandlers as $type => $handler ) {
- $tmpGlobals['wgMediaHandlers'][$type] = 'MockBitmapHandler';
- }
- // Vector images have to be handled slightly differently
- $tmpGlobals['wgMediaHandlers']['image/svg+xml'] = 'MockSvgHandler';
-
- // DjVu images have to be handled slightly differently
- $tmpGlobals['wgMediaHandlers']['image/vnd.djvu'] = 'MockDjVuHandler';
-
- // Ogg video/audio increasingly more differently
- $tmpGlobals['wgMediaHandlers']['application/ogg'] = 'MockOggHandler';
-
- $tmpHooks = $wgHooks;
- $tmpHooks['ParserTestParser'][] = 'ParserTestParserHook::setup';
- $tmpHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp';
- $tmpGlobals['wgHooks'] = $tmpHooks;
- # add a namespace shadowing a interwiki link, to test
- # proper precedence when resolving links. (bug 51680)
- $tmpGlobals['wgExtraNamespaces'] = [ 100 => 'MemoryAlpha' ];
-
- $tmpGlobals['wgLocalInterwikis'] = [ 'local', 'mi' ];
- # "extra language links"
- # see https://gerrit.wikimedia.org/r/111390
- $tmpGlobals['wgExtraInterlanguageLinkPrefixes'] = [ 'mul' ];
-
- // DjVu support
- $this->djVuSupport = new DjVuSupport();
- // Tidy support
- $this->tidySupport = new TidySupport();
- $tmpGlobals['wgTidyConfig'] = null;
- $tmpGlobals['wgUseTidy'] = false;
- $tmpGlobals['wgDebugTidy'] = false;
- $tmpGlobals['wgTidyConf'] = $IP . '/includes/tidy/tidy.conf';
- $tmpGlobals['wgTidyOpts'] = '';
- $tmpGlobals['wgTidyInternal'] = $this->tidySupport->isInternal();
-
- $this->setMwGlobals( $tmpGlobals );
-
- $this->savedWeirdGlobals['image_alias'] = $wgNamespaceAliases['Image'];
- $this->savedWeirdGlobals['image_talk_alias'] = $wgNamespaceAliases['Image_talk'];
-
- $wgNamespaceAliases['Image'] = NS_FILE;
- $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
-
- MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
- $wgContLang->resetNamespaces(); # reset namespace cache
- }
-
- protected function tearDown() {
- global $wgNamespaceAliases, $wgContLang;
-
- $wgNamespaceAliases['Image'] = $this->savedWeirdGlobals['image_alias'];
- $wgNamespaceAliases['Image_talk'] = $this->savedWeirdGlobals['image_talk_alias'];
-
- MWTidy::destroySingleton();
-
- // Restore backends
- RepoGroup::destroySingleton();
- FileBackendGroup::destroySingleton();
-
- // Remove temporary pages from the link cache
- LinkCache::singleton()->clear();
-
- // Restore message cache (temporary pages and $wgUseDatabaseMessages)
- MessageCache::destroyInstance();
-
- parent::tearDown();
-
- MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
- $wgContLang->resetNamespaces(); # reset namespace cache
- }
-
- public static function tearDownAfterClass() {
- ParserTest::tearDownInterwikis();
- parent::tearDownAfterClass();
- }
-
- function addDBDataOnce() {
- # disabled for performance
- # $this->tablesUsed[] = 'image';
-
- # Update certain things in site_stats
- $this->db->insert( 'site_stats',
- [ 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ],
- __METHOD__,
- [ 'IGNORE' ]
- );
-
- $user = User::newFromId( 0 );
- LinkCache::singleton()->clear(); # Avoids the odd failure at creating the nullRevision
-
- # Upload DB table entries for files.
- # We will upload the actual files later. Note that if anything causes LocalFile::load()
- # to be triggered before then, it will break via maybeUpgrade() setting the fileExists
- # member to false and storing it in cache.
- # note that the size/width/height/bits/etc of the file
- # are actually set by inspecting the file itself; the arguments
- # to recordUpload2 have no effect. That said, we try to make things
- # match up so it is less confusing to readers of the code & tests.
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) );
- if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
- $image->recordUpload2(
- '', // archive name
- 'Upload of some lame file',
- 'Some lame file',
- [
- 'size' => 7881,
- 'width' => 1941,
- 'height' => 220,
- 'bits' => 8,
- 'media_type' => MEDIATYPE_BITMAP,
- 'mime' => 'image/jpeg',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '1', 16, 36, 31 ),
- 'fileExists' => true ],
- $this->db->timestamp( '20010115123500' ), $user
- );
- }
-
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Thumb.png' ) );
- if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
- $image->recordUpload2(
- '', // archive name
- 'Upload of some lame thumbnail',
- 'Some lame thumbnail',
- [
- 'size' => 22589,
- 'width' => 135,
- 'height' => 135,
- 'bits' => 8,
- 'media_type' => MEDIATYPE_BITMAP,
- 'mime' => 'image/png',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '2', 16, 36, 31 ),
- 'fileExists' => true ],
- $this->db->timestamp( '20130225203040' ), $user
- );
- }
-
- # This image will be blacklisted in [[MediaWiki:Bad image list]]
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) );
- if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
- $image->recordUpload2(
- '', // archive name
- 'zomgnotcensored',
- 'Borderline image',
- [
- 'size' => 12345,
- 'width' => 320,
- 'height' => 240,
- 'bits' => 24,
- 'media_type' => MEDIATYPE_BITMAP,
- 'mime' => 'image/jpeg',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '3', 16, 36, 31 ),
- 'fileExists' => true ],
- $this->db->timestamp( '20010115123500' ), $user
- );
- }
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.svg' ) );
- if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
- $image->recordUpload2( '', 'Upload of some lame SVG', 'Some lame SVG', [
- 'size' => 12345,
- 'width' => 240,
- 'height' => 180,
- 'bits' => 0,
- 'media_type' => MEDIATYPE_DRAWING,
- 'mime' => 'image/svg+xml',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20010115123500' ), $user );
- }
-
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Video.ogv' ) );
- if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
- $image->recordUpload2( '', 'A pretty movie', 'Will it play', [
- 'size' => 12345,
- 'width' => 320,
- 'height' => 240,
- 'bits' => 0,
- 'media_type' => MEDIATYPE_VIDEO,
- 'mime' => 'application/ogg',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '', 16, 36, 32 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20010115123500' ), $user );
- }
-
- # A DjVu file
- # A DjVu file
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'LoremIpsum.djvu' ) );
- if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
- $image->recordUpload2( '', 'Upload a DjVu', 'A DjVu', [
- 'size' => 3249,
- 'width' => 2480,
- 'height' => 3508,
- 'bits' => 0,
- 'media_type' => MEDIATYPE_BITMAP,
- 'mime' => 'image/vnd.djvu',
- 'metadata' => '<?xml version="1.0" ?>
-<!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
-<DjVuXML>
-<HEAD></HEAD>
-<BODY><OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-</BODY>
-</DjVuXML>',
- 'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20140115123600' ), $user );
- }
- }
-
- // ParserTest setup/teardown functions
-
- /**
- * Set up the global variables for a consistent environment for each test.
- * Ideally this should replace the global configuration entirely.
- * @param array $opts
- * @param string $config
- * @return RequestContext
- */
- protected function setupGlobals( $opts = [], $config = '' ) {
- global $wgFileBackends;
- # Find out values for some special options.
- $lang =
- self::getOptionValue( 'language', $opts, 'en' );
- $variant =
- self::getOptionValue( 'variant', $opts, false );
- $maxtoclevel =
- self::getOptionValue( 'wgMaxTocLevel', $opts, 999 );
- $linkHolderBatchSize =
- self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 );
-
- $uploadDir = $this->getUploadDir();
- if ( $this->getCliArg( 'use-filebackend' ) ) {
- if ( self::$backendToUse ) {
- $backend = self::$backendToUse;
- } else {
- $name = $this->getCliArg( 'use-filebackend' );
- $useConfig = [];
- foreach ( $wgFileBackends as $conf ) {
- if ( $conf['name'] == $name ) {
- $useConfig = $conf;
- }
- }
- $useConfig['name'] = 'local-backend'; // swap name
- unset( $useConfig['lockManager'] );
- unset( $useConfig['fileJournal'] );
- $class = $useConfig['class'];
- self::$backendToUse = new $class( $useConfig );
- $backend = self::$backendToUse;
- }
- } else {
- # Replace with a mock. We do not care about generating real
- # files on the filesystem, just need to expose the file
- # informations.
- $backend = new MockFileBackend( [
- 'name' => 'local-backend',
- 'wikiId' => wfWikiID()
- ] );
- }
-
- $settings = [
- 'wgLocalFileRepo' => [
- 'class' => 'LocalRepo',
- 'name' => 'local',
- 'url' => 'http://example.com/images',
- 'hashLevels' => 2,
- 'transformVia404' => false,
- 'backend' => $backend
- ],
- 'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
- 'wgLanguageCode' => $lang,
- 'wgDBprefix' => $this->db->getType() != 'oracle' ? 'unittest_' : 'ut_',
- 'wgRawHtml' => self::getOptionValue( 'wgRawHtml', $opts, false ),
- 'wgNamespacesWithSubpages' => [ NS_MAIN => isset( $opts['subpage'] ) ],
- 'wgAllowExternalImages' => self::getOptionValue( 'wgAllowExternalImages', $opts, true ),
- 'wgThumbLimits' => [ self::getOptionValue( 'thumbsize', $opts, 180 ) ],
- 'wgMaxTocLevel' => $maxtoclevel,
- 'wgUseTeX' => isset( $opts['math'] ) || isset( $opts['texvc'] ),
- 'wgMathDirectory' => $uploadDir . '/math',
- 'wgDefaultLanguageVariant' => $variant,
- 'wgLinkHolderBatchSize' => $linkHolderBatchSize,
- 'wgUseTidy' => isset( $opts['tidy'] ),
- ];
-
- if ( $config ) {
- $configLines = explode( "\n", $config );
-
- foreach ( $configLines as $line ) {
- list( $var, $value ) = explode( '=', $line, 2 );
-
- $settings[$var] = eval( "return $value;" ); // ???
- }
- }
-
- $this->savedGlobals = [];
-
- /** @since 1.20 */
- Hooks::run( 'ParserTestGlobals', [ &$settings ] );
-
- $langObj = Language::factory( $lang );
- $settings['wgContLang'] = $langObj;
- $settings['wgLang'] = $langObj;
-
- $context = new RequestContext();
- $settings['wgOut'] = $context->getOutput();
- $settings['wgUser'] = $context->getUser();
- $settings['wgRequest'] = $context->getRequest();
-
- // We (re)set $wgThumbLimits to a single-element array above.
- $context->getUser()->setOption( 'thumbsize', 0 );
-
- foreach ( $settings as $var => $val ) {
- if ( array_key_exists( $var, $GLOBALS ) ) {
- $this->savedGlobals[$var] = $GLOBALS[$var];
- }
-
- $GLOBALS[$var] = $val;
- }
-
- MWTidy::destroySingleton();
- MagicWord::clearCache();
-
- # The entries saved into RepoGroup cache with previous globals will be wrong.
- RepoGroup::destroySingleton();
- FileBackendGroup::destroySingleton();
-
- # Create dummy files in storage
- $this->setupUploads();
-
- # Publish the articles after we have the final language set
- $this->publishTestArticles();
-
- MessageCache::destroyInstance();
-
- return $context;
- }
-
- /**
- * Get an FS upload directory (only applies to FSFileBackend)
- *
- * @return string The directory
- */
- protected function getUploadDir() {
- if ( $this->keepUploads ) {
- // Don't use getNewTempDirectory() as this is meant to persist
- $dir = wfTempDir() . '/mwParser-images';
-
- if ( is_dir( $dir ) ) {
- return $dir;
- }
- } else {
- $dir = $this->getNewTempDirectory();
- }
-
- if ( file_exists( $dir ) ) {
- wfDebug( "Already exists!\n" );
-
- return $dir;
- }
-
- return $dir;
- }
-
- /**
- * Create a dummy uploads directory which will contain a couple
- * of files in order to pass existence tests.
- *
- * @return string The directory
- */
- protected function setupUploads() {
- global $IP;
-
- $base = $this->getBaseDir();
- $backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
- $backend->prepare( [ 'dir' => "$base/local-public/3/3a" ] );
- $backend->store( [
- 'src' => "$IP/tests/phpunit/data/parser/headbg.jpg",
- 'dst' => "$base/local-public/3/3a/Foobar.jpg"
- ] );
- $backend->prepare( [ 'dir' => "$base/local-public/e/ea" ] );
- $backend->store( [
- 'src' => "$IP/tests/phpunit/data/parser/wiki.png",
- 'dst' => "$base/local-public/e/ea/Thumb.png"
- ] );
- $backend->prepare( [ 'dir' => "$base/local-public/0/09" ] );
- $backend->store( [
- 'src' => "$IP/tests/phpunit/data/parser/headbg.jpg",
- 'dst' => "$base/local-public/0/09/Bad.jpg"
- ] );
- $backend->prepare( [ 'dir' => "$base/local-public/5/5f" ] );
- $backend->store( [
- 'src' => "$IP/tests/phpunit/data/parser/LoremIpsum.djvu",
- 'dst' => "$base/local-public/5/5f/LoremIpsum.djvu"
- ] );
-
- // No helpful SVG file to copy, so make one ourselves
- $data = '<?xml version="1.0" encoding="utf-8"?>' .
- '<svg xmlns="http://www.w3.org/2000/svg"' .
- ' version="1.1" width="240" height="180"/>';
-
- $backend->prepare( [ 'dir' => "$base/local-public/f/ff" ] );
- $backend->quickCreate( [
- 'content' => $data, 'dst' => "$base/local-public/f/ff/Foobar.svg"
- ] );
- }
-
- /**
- * Restore default values and perform any necessary clean-up
- * after each test runs.
- */
- protected function teardownGlobals() {
- $this->teardownUploads();
-
- foreach ( $this->savedGlobals as $var => $val ) {
- $GLOBALS[$var] = $val;
- }
- }
-
- /**
- * Remove the dummy uploads directory
- */
- private function teardownUploads() {
- if ( $this->keepUploads ) {
- return;
- }
-
- $backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
- if ( $backend instanceof MockFileBackend ) {
- # In memory backend, so dont bother cleaning them up.
- return;
- }
-
- $base = $this->getBaseDir();
- // delete the files first, then the dirs.
- self::deleteFiles(
- [
- "$base/local-public/3/3a/Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/1000px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/100px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/120px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/137px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/1500px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/177px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/180px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/200px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/206px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/20px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/220px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/265px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/270px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/274px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/300px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/30px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/330px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/353px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/360px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/400px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/40px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/440px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/442px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/450px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/50px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/600px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/640px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/70px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/75px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/960px-Foobar.jpg",
-
- "$base/local-public/e/ea/Thumb.png",
-
- "$base/local-public/0/09/Bad.jpg",
-
- "$base/local-public/5/5f/LoremIpsum.djvu",
- "$base/local-thumb/5/5f/LoremIpsum.djvu/page2-2480px-LoremIpsum.djvu.jpg",
- "$base/local-thumb/5/5f/LoremIpsum.djvu/page2-3720px-LoremIpsum.djvu.jpg",
- "$base/local-thumb/5/5f/LoremIpsum.djvu/page2-4960px-LoremIpsum.djvu.jpg",
-
- "$base/local-public/f/ff/Foobar.svg",
- "$base/local-thumb/f/ff/Foobar.svg/180px-Foobar.svg.png",
- "$base/local-thumb/f/ff/Foobar.svg/2000px-Foobar.svg.png",
- "$base/local-thumb/f/ff/Foobar.svg/270px-Foobar.svg.png",
- "$base/local-thumb/f/ff/Foobar.svg/3000px-Foobar.svg.png",
- "$base/local-thumb/f/ff/Foobar.svg/360px-Foobar.svg.png",
- "$base/local-thumb/f/ff/Foobar.svg/4000px-Foobar.svg.png",
- "$base/local-thumb/f/ff/Foobar.svg/langde-180px-Foobar.svg.png",
- "$base/local-thumb/f/ff/Foobar.svg/langde-270px-Foobar.svg.png",
- "$base/local-thumb/f/ff/Foobar.svg/langde-360px-Foobar.svg.png",
-
- "$base/local-public/math/f/a/5/fa50b8b616463173474302ca3e63586b.png",
- ]
- );
- }
-
- /**
- * Delete the specified files, if they exist.
- * @param array $files Full paths to files to delete.
- */
- private static function deleteFiles( $files ) {
- $backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
- foreach ( $files as $file ) {
- $backend->delete( [ 'src' => $file ], [ 'force' => 1 ] );
- }
- foreach ( $files as $file ) {
- $tmp = FileBackend::parentStoragePath( $file );
- while ( $tmp ) {
- if ( !$backend->clean( [ 'dir' => $tmp ] )->isOK() ) {
- break;
- }
- $tmp = FileBackend::parentStoragePath( $tmp );
- }
- }
- }
-
- protected function getBaseDir() {
- return 'mwstore://local-backend';
- }
-
- public function parserTestProvider() {
- if ( $this->file === false ) {
- global $wgParserTestFiles;
- $this->file = $wgParserTestFiles[0];
- }
-
- return new TestFileDataProvider( $this->file, $this );
- }
-
- /**
- * Set the file from whose tests will be run by this instance
- * @param string $filename
- */
- public function setParserTestFile( $filename ) {
- $this->file = $filename;
- }
-
- /**
- * @group medium
- * @group ParserTests
- * @dataProvider parserTestProvider
- * @param string $desc
- * @param string $input
- * @param string $result
- * @param array $opts
- * @param array $config
- */
- public function testParserTest( $desc, $input, $result, $opts, $config ) {
- if ( $this->regex != '' && !preg_match( '/' . $this->regex . '/', $desc ) ) {
- $this->assertTrue( true ); // XXX: don't flood output with "test made no assertions"
- // $this->markTestSkipped( 'Filtered out by the user' );
- return;
- }
-
- if ( !$this->isWikitextNS( NS_MAIN ) ) {
- // parser tests frequently assume that the main namespace contains wikitext.
- // @todo When setting up pages, force the content model. Only skip if
- // $wgtContentModelUseDB is false.
- $this->markTestSkipped( "Main namespace does not support wikitext,"
- . "skipping parser test: $desc" );
- }
-
- wfDebug( "Running parser test: $desc\n" );
-
- $opts = $this->parseOptions( $opts );
- $context = $this->setupGlobals( $opts, $config );
-
- $user = $context->getUser();
- $options = ParserOptions::newFromContext( $context );
-
- if ( isset( $opts['title'] ) ) {
- $titleText = $opts['title'];
- } else {
- $titleText = 'Parser test';
- }
-
- $local = isset( $opts['local'] );
- $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null;
- $parser = $this->getParser( $preprocessor );
-
- $title = Title::newFromText( $titleText );
-
- # Parser test requiring math. Make sure texvc is executable
- # or just skip such tests.
- if ( isset( $opts['math'] ) || isset( $opts['texvc'] ) ) {
- global $wgTexvc;
-
- if ( !isset( $wgTexvc ) ) {
- $this->markTestSkipped( "SKIPPED: \$wgTexvc is not set" );
- } elseif ( !is_executable( $wgTexvc ) ) {
- $this->markTestSkipped( "SKIPPED: texvc binary does not exist"
- . " or is not executable.\n"
- . "Current configuration is:\n\$wgTexvc = '$wgTexvc'" );
- }
- }
-
- if ( isset( $opts['djvu'] ) ) {
- if ( !$this->djVuSupport->isEnabled() ) {
- $this->markTestSkipped( "SKIPPED: djvu binaries do not exist or are not executable.\n" );
- }
- }
-
- if ( isset( $opts['tidy'] ) ) {
- if ( !$this->tidySupport->isEnabled() ) {
- $this->markTestSkipped( "SKIPPED: tidy extension is not installed.\n" );
- } else {
- $options->setTidy( true );
- }
- }
-
- if ( isset( $opts['pst'] ) ) {
- $out = $parser->preSaveTransform( $input, $title, $user, $options );
- } elseif ( isset( $opts['msg'] ) ) {
- $out = $parser->transformMsg( $input, $options, $title );
- } elseif ( isset( $opts['section'] ) ) {
- $section = $opts['section'];
- $out = $parser->getSection( $input, $section );
- } elseif ( isset( $opts['replace'] ) ) {
- $section = $opts['replace'][0];
- $replace = $opts['replace'][1];
- $out = $parser->replaceSection( $input, $section, $replace );
- } elseif ( isset( $opts['comment'] ) ) {
- $out = Linker::formatComment( $input, $title, $local );
- } elseif ( isset( $opts['preload'] ) ) {
- $out = $parser->getPreloadText( $input, $title, $options );
- } else {
- $output = $parser->parse( $input, $title, $options, true, true, 1337 );
- $output->setTOCEnabled( !isset( $opts['notoc'] ) );
- $out = $output->getText();
- if ( isset( $opts['tidy'] ) ) {
- $out = preg_replace( '/\s+$/', '', $out );
- }
-
- if ( isset( $opts['showtitle'] ) ) {
- if ( $output->getTitleText() ) {
- $title = $output->getTitleText();
- }
-
- $out = "$title\n$out";
- }
-
- if ( isset( $opts['showindicators'] ) ) {
- $indicators = '';
- foreach ( $output->getIndicators() as $id => $content ) {
- $indicators .= "$id=$content\n";
- }
- $out = $indicators . $out;
- }
-
- if ( isset( $opts['ill'] ) ) {
- $out = implode( ' ', $output->getLanguageLinks() );
- } elseif ( isset( $opts['cat'] ) ) {
- $outputPage = $context->getOutput();
- $outputPage->addCategoryLinks( $output->getCategories() );
- $cats = $outputPage->getCategoryLinks();
-
- if ( isset( $cats['normal'] ) ) {
- $out = implode( ' ', $cats['normal'] );
- } else {
- $out = '';
- }
- }
- $parser->mPreprocessor = null;
- }
-
- $this->teardownGlobals();
-
- $this->assertEquals( $result, $out, $desc );
- }
-
- /**
- * Run a fuzz test series
- * Draw input from a set of test files
- *
- * @todo fixme Needs some work to not eat memory until the world explodes
- *
- * @group ParserFuzz
- */
- public function testFuzzTests() {
- global $wgParserTestFiles;
-
- $files = $wgParserTestFiles;
-
- if ( $this->getCliArg( 'file' ) ) {
- $files = [ $this->getCliArg( 'file' ) ];
- }
-
- $dict = $this->getFuzzInput( $files );
- $dictSize = strlen( $dict );
- $logMaxLength = log( $this->maxFuzzTestLength );
-
- ini_set( 'memory_limit', $this->memoryLimit * 1048576 );
-
- $user = new User;
- $opts = ParserOptions::newFromUser( $user );
- $title = Title::makeTitle( NS_MAIN, 'Parser_test' );
-
- $id = 1;
-
- while ( true ) {
-
- // Generate test input
- mt_srand( ++$this->fuzzSeed );
- $totalLength = mt_rand( 1, $this->maxFuzzTestLength );
- $input = '';
-
- while ( strlen( $input ) < $totalLength ) {
- $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength;
- $hairLength = min( intval( exp( $logHairLength ) ), $dictSize );
- $offset = mt_rand( 0, $dictSize - $hairLength );
- $input .= substr( $dict, $offset, $hairLength );
- }
-
- $this->setupGlobals();
- $parser = $this->getParser();
-
- // Run the test
- try {
- $parser->parse( $input, $title, $opts );
- $this->assertTrue( true, "Test $id, fuzz seed {$this->fuzzSeed}" );
- } catch ( Exception $exception ) {
- $input_dump = sprintf( "string(%d) \"%s\"\n", strlen( $input ), $input );
-
- $this->assertTrue( false, "Test $id, fuzz seed {$this->fuzzSeed}. \n\n" .
- "Input: $input_dump\n\nError: {$exception->getMessage()}\n\n" .
- "Backtrace: {$exception->getTraceAsString()}" );
- }
-
- $this->teardownGlobals();
- $parser->__destruct();
-
- if ( $id % 100 == 0 ) {
- $usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 );
- // echo "{$this->fuzzSeed}: $numSuccess/$numTotal (mem: $usage%)\n";
- if ( $usage > 90 ) {
- $ret = "Out of memory:\n";
- $memStats = $this->getMemoryBreakdown();
-
- foreach ( $memStats as $name => $usage ) {
- $ret .= "$name: $usage\n";
- }
-
- throw new MWException( $ret );
- }
- }
-
- $id++;
- }
- }
-
- // Various getter functions
-
- /**
- * Get an input dictionary from a set of parser test files
- * @param array $filenames
- * @return string
- */
- function getFuzzInput( $filenames ) {
- $dict = '';
-
- foreach ( $filenames as $filename ) {
- $contents = file_get_contents( $filename );
- preg_match_all( '/!!\s*input\n(.*?)\n!!\s*result/s', $contents, $matches );
-
- foreach ( $matches[1] as $match ) {
- $dict .= $match . "\n";
- }
- }
-
- return $dict;
- }
-
- /**
- * Get a memory usage breakdown
- * @return array
- */
- function getMemoryBreakdown() {
- $memStats = [];
-
- foreach ( $GLOBALS as $name => $value ) {
- $memStats['$' . $name] = strlen( serialize( $value ) );
- }
-
- $classes = get_declared_classes();
-
- foreach ( $classes as $class ) {
- $rc = new ReflectionClass( $class );
- $props = $rc->getStaticProperties();
- $memStats[$class] = strlen( serialize( $props ) );
- $methods = $rc->getMethods();
-
- foreach ( $methods as $method ) {
- $memStats[$class] += strlen( serialize( $method->getStaticVariables() ) );
- }
- }
-
- $functions = get_defined_functions();
-
- foreach ( $functions['user'] as $function ) {
- $rf = new ReflectionFunction( $function );
- $memStats["$function()"] = strlen( serialize( $rf->getStaticVariables() ) );
- }
-
- asort( $memStats );
-
- return $memStats;
- }
-
- /**
- * Get a Parser object
- * @param Preprocessor $preprocessor
- * @return Parser
- */
- function getParser( $preprocessor = null ) {
- global $wgParserConf;
-
- $class = $wgParserConf['class'];
- $parser = new $class( [ 'preprocessorClass' => $preprocessor ] + $wgParserConf );
-
- Hooks::run( 'ParserTestParser', [ &$parser ] );
-
- return $parser;
- }
-
- // Various action functions
-
- public function addArticle( $name, $text, $line ) {
- self::$articles[$name] = [ $text, $line ];
- }
-
- public function publishTestArticles() {
- if ( empty( self::$articles ) ) {
- return;
- }
-
- foreach ( self::$articles as $name => $info ) {
- list( $text, $line ) = $info;
- ParserTest::addArticle( $name, $text, $line, 'ignoreduplicate' );
- }
- }
-
- /**
- * Steal a callback function from the primary parser, save it for
- * application to our scary parser. If the hook is not installed,
- * abort processing of this file.
- *
- * @param string $name
- * @return bool True if tag hook is present
- */
- public function requireHook( $name ) {
- global $wgParser;
- $wgParser->firstCallInit(); // make sure hooks are loaded.
- return isset( $wgParser->mTagHooks[$name] );
- }
-
- public function requireFunctionHook( $name ) {
- global $wgParser;
- $wgParser->firstCallInit(); // make sure hooks are loaded.
- return isset( $wgParser->mFunctionHooks[$name] );
- }
-
- public function requireTransparentHook( $name ) {
- global $wgParser;
- $wgParser->firstCallInit(); // make sure hooks are loaded.
- return isset( $wgParser->mTransparentTagHooks[$name] );
- }
-
- // Various "cleanup" functions
-
- /**
- * Remove last character if it is a newline
- * @param string $s
- * @return string
- */
- public function removeEndingNewline( $s ) {
- if ( substr( $s, -1 ) === "\n" ) {
- return substr( $s, 0, -1 );
- } else {
- return $s;
- }
- }
-
- // Test options parser functions
-
- protected function parseOptions( $instring ) {
- $opts = [];
- // foo
- // foo=bar
- // foo="bar baz"
- // foo=[[bar baz]]
- // foo=bar,"baz quux"
- $regex = '/\b
- ([\w-]+) # Key
- \b
- (?:\s*
- = # First sub-value
- \s*
- (
- "
- [^"]* # Quoted val
- "
- |
- \[\[
- [^]]* # Link target
- \]\]
- |
- [\w-]+ # Plain word
- )
- (?:\s*
- , # Sub-vals 1..N
- \s*
- (
- "[^"]*" # Quoted val
- |
- \[\[[^]]*\]\] # Link target
- |
- [\w-]+ # Plain word
- )
- )*
- )?
- /x';
-
- if ( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) {
- foreach ( $matches as $bits ) {
- array_shift( $bits );
- $key = strtolower( array_shift( $bits ) );
- if ( count( $bits ) == 0 ) {
- $opts[$key] = true;
- } elseif ( count( $bits ) == 1 ) {
- $opts[$key] = $this->cleanupOption( array_shift( $bits ) );
- } else {
- // Array!
- $opts[$key] = array_map( [ $this, 'cleanupOption' ], $bits );
- }
- }
- }
-
- return $opts;
- }
-
- protected function cleanupOption( $opt ) {
- if ( substr( $opt, 0, 1 ) == '"' ) {
- return substr( $opt, 1, -1 );
- }
-
- if ( substr( $opt, 0, 2 ) == '[[' ) {
- return substr( $opt, 2, -2 );
- }
-
- return $opt;
- }
-
- /**
- * Use a regex to find out the value of an option
- * @param string $key Name of option val to retrieve
- * @param array $opts Options array to look in
- * @param mixed $default Default value returned if not found
- * @return mixed
- */
- protected static function getOptionValue( $key, $opts, $default ) {
- $key = strtolower( $key );
-
- if ( isset( $opts[$key] ) ) {
- return $opts[$key];
- } else {
- return $default;
- }
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/parser/ParserIntegrationTest.php b/www/wiki/tests/phpunit/includes/parser/ParserIntegrationTest.php
index c9209824..91653b5d 100644
--- a/www/wiki/tests/phpunit/includes/parser/ParserIntegrationTest.php
+++ b/www/wiki/tests/phpunit/includes/parser/ParserIntegrationTest.php
@@ -8,13 +8,29 @@ use Wikimedia\ScopedCallback;
* Note: the following groups are not used by PHPUnit.
* The list in ParserTestFileSuite::__construct() is used instead.
*
+ * @group large
* @group Database
* @group Parser
* @group ParserTests
*
- * @todo covers tags
+ * @covers Parser
+ * @covers BlockLevelPass
+ * @covers CoreParserFunctions
+ * @covers CoreTagHooks
+ * @covers Sanitizer
+ * @covers Preprocessor
+ * @covers Preprocessor_DOM
+ * @covers Preprocessor_Hash
+ * @covers DateFormatter
+ * @covers LinkHolderArray
+ * @covers StripState
+ * @covers ParserOptions
+ * @covers ParserOutput
*/
-class ParserIntegrationTest extends PHPUnit_Framework_TestCase {
+class ParserIntegrationTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
/** @var array */
private $ptTest;
diff --git a/www/wiki/tests/phpunit/includes/parser/ParserMethodsTest.php b/www/wiki/tests/phpunit/includes/parser/ParserMethodsTest.php
index ae58d1ce..d2ed4415 100644
--- a/www/wiki/tests/phpunit/includes/parser/ParserMethodsTest.php
+++ b/www/wiki/tests/phpunit/includes/parser/ParserMethodsTest.php
@@ -3,6 +3,7 @@
/**
* @group Database
* @covers Parser
+ * @covers BlockLevelPass
*/
class ParserMethodsTest extends MediaWikiLangTestCase {
diff --git a/www/wiki/tests/phpunit/includes/parser/ParserOptionsTest.php b/www/wiki/tests/phpunit/includes/parser/ParserOptionsTest.php
index ad899bd7..e2ed1d57 100644
--- a/www/wiki/tests/phpunit/includes/parser/ParserOptionsTest.php
+++ b/www/wiki/tests/phpunit/includes/parser/ParserOptionsTest.php
@@ -3,6 +3,9 @@
use Wikimedia\TestingAccessWrapper;
use Wikimedia\ScopedCallback;
+/**
+ * @covers ParserOptions
+ */
class ParserOptionsTest extends MediaWikiTestCase {
private static function clearCache() {
@@ -18,7 +21,6 @@ class ParserOptionsTest extends MediaWikiTestCase {
'stubthreshold' => true,
'printable' => true,
'userlang' => true,
- 'wrapclass' => true,
];
}
@@ -59,11 +61,14 @@ class ParserOptionsTest extends MediaWikiTestCase {
'No overrides' => [ true, [] ],
'In-key options are ok' => [ true, [
'thumbsize' => 1e100,
- 'wrapclass' => false,
+ 'printable' => false,
] ],
'Non-in-key options are not ok' => [ false, [
'removeComments' => false,
] ],
+ 'Non-in-key options are not ok (2)' => [ false, [
+ 'wrapclass' => 'foobar',
+ ] ],
'Canonical override, not default (1)' => [ true, [
'tidy' => true,
] ],
@@ -99,7 +104,7 @@ class ParserOptionsTest extends MediaWikiTestCase {
}
public static function provideOptionsHash() {
- $used = [ 'wrapclass', 'printable' ];
+ $used = [ 'thumbsize', 'printable' ];
$classWrapper = TestingAccessWrapper::newFromClass( ParserOptions::class );
$classWrapper->getDefaults();
@@ -113,9 +118,9 @@ class ParserOptionsTest extends MediaWikiTestCase {
'Canonical options, used some options' => [ $used, 'canonical', [] ],
'Used some options, non-default values' => [
$used,
- 'printable=1!wrapclass=foobar',
+ 'printable=1!thumbsize=200',
[
- 'wrapclass' => 'foobar',
+ 'thumbsize' => 200,
'printable' => true,
]
],
@@ -136,23 +141,6 @@ class ParserOptionsTest extends MediaWikiTestCase {
$confstr .= '!onPageRenderingHash';
}
- // Test weird historical behavior is still weird
- public function testOptionsHashEditSection() {
- $popt = ParserOptions::newCanonical();
- $popt->registerWatcher( function ( $name ) {
- $this->assertNotEquals( 'editsection', $name );
- } );
-
- $this->assertTrue( $popt->getEditSection() );
- $this->assertSame( 'canonical', $popt->optionsHash( [] ) );
- $this->assertSame( 'canonical', $popt->optionsHash( [ 'editsection' ] ) );
-
- $popt->setEditSection( false );
- $this->assertFalse( $popt->getEditSection() );
- $this->assertSame( 'canonical', $popt->optionsHash( [] ) );
- $this->assertSame( 'editsection=0', $popt->optionsHash( [ 'editsection' ] ) );
- }
-
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage Unknown parser option bogus
@@ -210,7 +198,7 @@ class ParserOptionsTest extends MediaWikiTestCase {
$wgHooks['ParserOptionsRegister'] = [];
$this->assertSame( [
'dateformat', 'numberheadings', 'printable', 'stubthreshold',
- 'thumbsize', 'userlang', 'wrapclass',
+ 'thumbsize', 'userlang'
], ParserOptions::allCacheVaryingOptions() );
self::clearCache();
@@ -228,7 +216,7 @@ class ParserOptionsTest extends MediaWikiTestCase {
};
$this->assertSame( [
'dateformat', 'foo', 'numberheadings', 'printable', 'stubthreshold',
- 'thumbsize', 'userlang', 'wrapclass',
+ 'thumbsize', 'userlang'
], ParserOptions::allCacheVaryingOptions() );
}
diff --git a/www/wiki/tests/phpunit/includes/parser/ParserOutputTest.php b/www/wiki/tests/phpunit/includes/parser/ParserOutputTest.php
index ec8f0d07..b08ba6c4 100644
--- a/www/wiki/tests/phpunit/includes/parser/ParserOutputTest.php
+++ b/www/wiki/tests/phpunit/includes/parser/ParserOutputTest.php
@@ -89,4 +89,206 @@ class ParserOutputTest extends MediaWikiTestCase {
$this->assertArrayNotHasKey( 'foo', $properties );
}
+ /**
+ * @covers ParserOutput::getText
+ * @dataProvider provideGetText
+ * @param array $options Options to getText()
+ * @param string $text Parser text
+ * @param string $expect Expected output
+ */
+ public function testGetText( $options, $text, $expect ) {
+ $this->setMwGlobals( [
+ 'wgArticlePath' => '/wiki/$1',
+ 'wgScriptPath' => '/w',
+ 'wgScript' => '/w/index.php',
+ ] );
+
+ $po = new ParserOutput( $text );
+ $actual = $po->getText( $options );
+ $this->assertSame( $expect, $actual );
+ }
+
+ public static function provideGetText() {
+ // phpcs:disable Generic.Files.LineLength
+ $text = <<<EOF
+<div class="mw-parser-output"><p>Test document.
+</p>
+<mw:toc><div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
+<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
+<ul>
+<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
+</ul>
+</div>
+</mw:toc>
+<h2><span class="mw-headline" id="Section_1">Section 1</span><mw:editsection page="Test Page" section="1">Section 1</mw:editsection></h2>
+<p>One
+</p>
+<h2><span class="mw-headline" id="Section_2">Section 2</span><mw:editsection page="Test Page" section="2">Section 2</mw:editsection></h2>
+<p>Two
+</p>
+<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><mw:editsection page="Test Page" section="3">Section 2.1</mw:editsection></h3>
+<p>Two point one
+</p>
+<h2><span class="mw-headline" id="Section_3">Section 3</span><mw:editsection page="Test Page" section="4">Section 3</mw:editsection></h2>
+<p>Three
+</p></div>
+EOF;
+
+ $dedupText = <<<EOF
+<p>This is a test document.</p>
+<style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
+<style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
+<style data-mw-deduplicate="duplicate2">.Duplicate2 {}</style>
+<style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
+<style data-mw-deduplicate="duplicate2">.Duplicate2 {}</style>
+<style data-mw-not-deduplicate="duplicate1">.Duplicate1 {}</style>
+<style data-mw-deduplicate="duplicate1">.Same-attribute-different-content {}</style>
+<style data-mw-deduplicate="duplicate3">.Duplicate1 {}</style>
+<style>.Duplicate1 {}</style>
+EOF;
+
+ return [
+ 'No options' => [
+ [], $text, <<<EOF
+<div class="mw-parser-output"><p>Test document.
+</p>
+<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
+<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
+<ul>
+<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
+</ul>
+</div>
+
+<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>One
+</p>
+<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Two
+</p>
+<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
+<p>Two point one
+</p>
+<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Three
+</p></div>
+EOF
+ ],
+ 'Disable section edit links' => [
+ [ 'enableSectionEditLinks' => false ], $text, <<<EOF
+<div class="mw-parser-output"><p>Test document.
+</p>
+<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
+<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
+<ul>
+<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
+</ul>
+</div>
+
+<h2><span class="mw-headline" id="Section_1">Section 1</span></h2>
+<p>One
+</p>
+<h2><span class="mw-headline" id="Section_2">Section 2</span></h2>
+<p>Two
+</p>
+<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span></h3>
+<p>Two point one
+</p>
+<h2><span class="mw-headline" id="Section_3">Section 3</span></h2>
+<p>Three
+</p></div>
+EOF
+ ],
+ 'Disable TOC' => [
+ [ 'allowTOC' => false ], $text, <<<EOF
+<div class="mw-parser-output"><p>Test document.
+</p>
+
+<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>One
+</p>
+<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Two
+</p>
+<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
+<p>Two point one
+</p>
+<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Three
+</p></div>
+EOF
+ ],
+ 'Unwrap text' => [
+ [ 'unwrap' => true ], $text, <<<EOF
+<p>Test document.
+</p>
+<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
+<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
+<ul>
+<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
+</ul>
+</div>
+
+<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>One
+</p>
+<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Two
+</p>
+<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
+<p>Two point one
+</p>
+<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Three
+</p>
+EOF
+ ],
+ 'Unwrap without a mw-parser-output wrapper' => [
+ [ 'unwrap' => true ], '<div class="foobar">Content</div>', '<div class="foobar">Content</div>'
+ ],
+ 'Unwrap with extra comment at end' => [
+ [ 'unwrap' => true ], '<div class="mw-parser-output"><p>Test document.</p></div>
+<!-- Saved in parser cache... -->', '<p>Test document.</p>
+<!-- Saved in parser cache... -->'
+ ],
+ 'Style deduplication' => [
+ [], $dedupText, <<<EOF
+<p>This is a test document.</p>
+<style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
+<link rel="mw-deduplicated-inline-style" href="mw-data:duplicate1"/>
+<style data-mw-deduplicate="duplicate2">.Duplicate2 {}</style>
+<link rel="mw-deduplicated-inline-style" href="mw-data:duplicate1"/>
+<link rel="mw-deduplicated-inline-style" href="mw-data:duplicate2"/>
+<style data-mw-not-deduplicate="duplicate1">.Duplicate1 {}</style>
+<link rel="mw-deduplicated-inline-style" href="mw-data:duplicate1"/>
+<style data-mw-deduplicate="duplicate3">.Duplicate1 {}</style>
+<style>.Duplicate1 {}</style>
+EOF
+ ],
+ 'Style deduplication disabled' => [
+ [ 'deduplicateStyles' => false ], $dedupText, $dedupText
+ ],
+ ];
+ // phpcs:enable
+ }
+
}
diff --git a/www/wiki/tests/phpunit/includes/parser/PreprocessorTest.php b/www/wiki/tests/phpunit/includes/parser/PreprocessorTest.php
index 11a21976..c415b586 100644
--- a/www/wiki/tests/phpunit/includes/parser/PreprocessorTest.php
+++ b/www/wiki/tests/phpunit/includes/parser/PreprocessorTest.php
@@ -37,8 +37,8 @@ class PreprocessorTest extends MediaWikiTestCase {
protected $mPreprocessors;
protected static $classNames = [
- 'Preprocessor_DOM',
- 'Preprocessor_Hash'
+ Preprocessor_DOM::class,
+ Preprocessor_Hash::class
];
protected function setUp() {
@@ -68,7 +68,7 @@ class PreprocessorTest extends MediaWikiTestCase {
}
public static function provideCases() {
- // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ // phpcs:disable Generic.Files.LineLength
return self::addClassArg( [
[ "Foo", "<root>Foo</root>" ],
[ "<!-- Foo -->", "<root><comment>&lt;!-- Foo --&gt;</comment></root>" ],
@@ -156,7 +156,7 @@ class PreprocessorTest extends MediaWikiTestCase {
[ "{{Foo|} Bar=}}", "<root><template><title>Foo</title><part><name>} Bar</name>=<value></value></part></template></root>" ],
/* [ file_get_contents( __DIR__ . '/QuoteQuran.txt' ], file_get_contents( __DIR__ . '/QuoteQuranExpanded.txt' ) ], */
] );
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
/**
@@ -208,7 +208,7 @@ class PreprocessorTest extends MediaWikiTestCase {
* These are more complex test cases taken out of wiki articles.
*/
public static function provideFiles() {
- // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ // phpcs:disable Generic.Files.LineLength
return self::addClassArg( [
[ "QuoteQuran" ], # https://en.wikipedia.org/w/index.php?title=Template:QuoteQuran/sandbox&oldid=237348988 GFDL + CC BY-SA by Striver
[ "Factorial" ], # https://en.wikipedia.org/w/index.php?title=Template:Factorial&oldid=98548758 GFDL + CC BY-SA by Polonium
@@ -216,7 +216,7 @@ class PreprocessorTest extends MediaWikiTestCase {
[ "Fundraising" ], # https://tl.wiktionary.org/w/index.php?title=MediaWiki:Sitenotice&oldid=5716 GFDL + CC BY-SA, copied there by Sky Harbor.
[ "NestedTemplates" ], # T29936
] );
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
/**
@@ -242,7 +242,7 @@ class PreprocessorTest extends MediaWikiTestCase {
* Tests from T30642 · https://phabricator.wikimedia.org/T30642
*/
public static function provideHeadings() {
- // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ // phpcs:disable Generic.Files.LineLength
return self::addClassArg( [
/* These should become headings: */
[ "== h ==<!--c1-->", "<root><h level=\"2\" i=\"1\">== h ==<comment>&lt;!--c1--&gt;</comment></h></root>" ],
@@ -281,7 +281,7 @@ class PreprocessorTest extends MediaWikiTestCase {
[ "== h ==<!--c1--> x <!--c2--><!--c3--> ", "<root>== h ==<comment>&lt;!--c1--&gt;</comment> x <comment>&lt;!--c2--&gt;</comment><comment>&lt;!--c3--&gt;</comment> </root>" ],
[ "== h ==<!--c1--><!--c2--><!--c3--> x ", "<root>== h ==<comment>&lt;!--c1--&gt;</comment><comment>&lt;!--c2--&gt;</comment><comment>&lt;!--c3--&gt;</comment> x </root>" ],
] );
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
/**
diff --git a/www/wiki/tests/phpunit/includes/SanitizerTest.php b/www/wiki/tests/phpunit/includes/parser/SanitizerTest.php
index 7256694f..35b81fb9 100644
--- a/www/wiki/tests/phpunit/includes/SanitizerTest.php
+++ b/www/wiki/tests/phpunit/includes/parser/SanitizerTest.php
@@ -322,6 +322,7 @@ class SanitizerTest extends MediaWikiTestCase {
],
[ '/* insecure input */', 'foo: attr( title, url );' ],
[ '/* insecure input */', 'foo: attr( title url );' ],
+ [ '/* insecure input */', 'foo: var(--evil-attribute)' ],
];
}
@@ -388,7 +389,7 @@ class SanitizerTest extends MediaWikiTestCase {
*/
public function testEscapeIdReferenceList( $referenceList, $id1, $id2 ) {
$this->assertEquals(
- Sanitizer::escapeIdReferenceList( $referenceList, 'noninitial' ),
+ Sanitizer::escapeIdReferenceList( $referenceList ),
Sanitizer::escapeIdForAttribute( $id1 )
. ' '
. Sanitizer::escapeIdForAttribute( $id2 )
@@ -406,6 +407,7 @@ class SanitizerTest extends MediaWikiTestCase {
/**
* @dataProvider provideIsReservedDataAttribute
+ * @covers Sanitizer::isReservedDataAttribute
*/
public function testIsReservedDataAttribute( $attr, $expected ) {
$this->assertSame( $expected, Sanitizer::isReservedDataAttribute( $attr ) );
@@ -513,6 +515,34 @@ class SanitizerTest extends MediaWikiTestCase {
}
/**
+ * @dataProvider provideStripAllTags
+ *
+ * @covers Sanitizer::stripAllTags()
+ * @covers RemexStripTagHandler
+ *
+ * @param string $input
+ * @param string $expected
+ */
+ public function testStripAllTags( $input, $expected ) {
+ $this->assertEquals( $expected, Sanitizer::stripAllTags( $input ) );
+ }
+
+ public function provideStripAllTags() {
+ return [
+ [ '<p>Foo</p>', 'Foo' ],
+ [ '<p id="one">Foo</p><p id="two">Bar</p>', 'FooBar' ],
+ [ "<p>Foo</p>\n<p>Bar</p>", 'Foo Bar' ],
+ [ '<p>Hello &lt;strong&gt; wor&#x6c;&#100; caf&eacute;</p>', 'Hello <strong> world café' ],
+ [
+ '<p><small data-foo=\'bar"&lt;baz>quux\'><a href="./Foo">Bar</a></small> Whee!</p>',
+ 'Bar Whee!'
+ ],
+ [ '1<span class="<?php">2</span>3', '123' ],
+ [ '1<span class="<?">2</span>3', '123' ],
+ ];
+ }
+
+ /**
* @expectedException InvalidArgumentException
* @covers Sanitizer::escapeIdInternal()
*/
diff --git a/www/wiki/tests/phpunit/includes/parser/StripStateTest.php b/www/wiki/tests/phpunit/includes/parser/StripStateTest.php
new file mode 100644
index 00000000..0f4f6e0f
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/parser/StripStateTest.php
@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * @covers StripState
+ */
+class StripStateTest extends MediaWikiTestCase {
+ public function setUp() {
+ parent::setUp();
+ $this->setContentLang( 'qqx' );
+ }
+
+ private function getMarker() {
+ static $i;
+ return Parser::MARKER_PREFIX . '-blah-' . sprintf( '%08X', $i++ ) . Parser::MARKER_SUFFIX;
+ }
+
+ private static function getWarning( $message, $max = '' ) {
+ return "<span class=\"error\">($message: $max)</span>";
+ }
+
+ public function testAddNoWiki() {
+ $ss = new StripState;
+ $marker = $this->getMarker();
+ $ss->addNoWiki( $marker, '<>' );
+ $text = "x{$marker}y";
+ $text = $ss->unstripGeneral( $text );
+ $text = str_replace( '<', '', $text );
+ $text = $ss->unstripNoWiki( $text );
+ $this->assertSame( 'x<>y', $text );
+ }
+
+ public function testAddGeneral() {
+ $ss = new StripState;
+ $marker = $this->getMarker();
+ $ss->addGeneral( $marker, '<>' );
+ $text = "x{$marker}y";
+ $text = $ss->unstripNoWiki( $text );
+ $text = str_replace( '<', '', $text );
+ $text = $ss->unstripGeneral( $text );
+ $this->assertSame( 'x<>y', $text );
+ }
+
+ public function testUnstripBoth() {
+ $ss = new StripState;
+ $mk1 = $this->getMarker();
+ $mk2 = $this->getMarker();
+ $ss->addNoWiki( $mk1, '<1>' );
+ $ss->addGeneral( $mk2, '<2>' );
+ $text = "x{$mk1}{$mk2}y";
+ $text = str_replace( '<', '', $text );
+ $text = $ss->unstripBoth( $text );
+ $this->assertSame( 'x<1><2>y', $text );
+ }
+
+ public static function provideUnstripRecursive() {
+ return [
+ [ 0, 'text' ],
+ [ 1, '=text=' ],
+ [ 2, '==text==' ],
+ [ 3, '==' . self::getWarning( 'unstrip-depth-warning', 2 ) . '==' ],
+ ];
+ }
+
+ /** @dataProvider provideUnstripRecursive */
+ public function testUnstripRecursive( $depth, $expected ) {
+ $ss = new StripState( null, [ 'depthLimit' => 2 ] );
+ $text = 'text';
+ for ( $i = 0; $i < $depth; $i++ ) {
+ $mk = $this->getMarker();
+ $ss->addNoWiki( $mk, "={$text}=" );
+ $text = $mk;
+ }
+ $text = $ss->unstripNoWiki( $text );
+ $this->assertSame( $expected, $text );
+ }
+
+ public function testUnstripLoop() {
+ $ss = new StripState( null, [ 'depthLimit' => 2 ] );
+ $mk = $this->getMarker();
+ $ss->addNoWiki( $mk, $mk );
+ $text = $ss->unstripNoWiki( $mk );
+ $this->assertSame( self::getWarning( 'parser-unstrip-loop-warning' ), $text );
+ }
+
+ public static function provideUnstripSize() {
+ return [
+ [ 0, 'x' ],
+ [ 1, 'xx' ],
+ [ 2, str_repeat( self::getWarning( 'unstrip-size-warning', 5 ), 2 ) ]
+ ];
+ }
+
+ /** @dataProvider provideUnstripSize */
+ public function testUnstripSize( $depth, $expected ) {
+ $ss = new StripState( null, [ 'sizeLimit' => 5 ] );
+ $text = 'x';
+ for ( $i = 0; $i < $depth; $i++ ) {
+ $mk = $this->getMarker();
+ $ss->addNoWiki( $mk, $text );
+ $text = "$mk$mk";
+ }
+ $text = $ss->unstripNoWiki( $text );
+ $this->assertSame( $expected, $text );
+ }
+
+ public function provideGetLimitReport() {
+ for ( $i = 1; $i < 4; $i++ ) {
+ yield [ $i ];
+ }
+ }
+
+ /** @dataProvider provideGetLimitReport */
+ public function testGetLimitReport( $depth ) {
+ $sizeLimit = 100000;
+ $ss = new StripState( null, [ 'depthLimit' => 5, 'sizeLimit' => $sizeLimit ] );
+ $text = 'x';
+ for ( $i = 0; $i < $depth; $i++ ) {
+ $mk = $this->getMarker();
+ $ss->addNoWiki( $mk, $text );
+ $text = "$mk$mk";
+ }
+ $text = $ss->unstripNoWiki( $text );
+ $report = $ss->getLimitReport();
+ $messages = [];
+ foreach ( $report as list( $msg, $params ) ) {
+ $messages[$msg] = $params;
+ }
+ $this->assertSame( [ $depth - 1, 5 ], $messages['limitreport-unstrip-depth'] );
+ $this->assertSame(
+ [
+ strlen( $this->getMarker() ) * 2 * ( pow( 2, $depth ) - 2 ) + pow( 2, $depth ),
+ $sizeLimit
+ ],
+ $messages['limitreport-unstrip-size' ] );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/parser/TagHooksTest.php b/www/wiki/tests/phpunit/includes/parser/TagHooksTest.php
index 06fe272b..bc09adc8 100644
--- a/www/wiki/tests/phpunit/includes/parser/TagHooksTest.php
+++ b/www/wiki/tests/phpunit/includes/parser/TagHooksTest.php
@@ -5,6 +5,7 @@
* @group Parser
*
* @covers Parser
+ * @covers BlockLevelPass
* @covers StripState
*
* @covers Preprocessor_DOM
@@ -28,7 +29,7 @@
* @covers PPNode_Hash_Array
* @covers PPNode_Hash_Attr
*/
-class TagHookTest extends MediaWikiTestCase {
+class TagHooksTest extends MediaWikiTestCase {
public static function provideValidNames() {
return [
[ 'foo' ],
@@ -46,7 +47,6 @@ class TagHookTest extends MediaWikiTestCase {
private function getParserOptions() {
global $wgContLang;
$popt = ParserOptions::newFromUserAndLang( new User, $wgContLang );
- $popt->setWrapOutputClass( false );
return $popt;
}
@@ -63,7 +63,7 @@ class TagHookTest extends MediaWikiTestCase {
Title::newFromText( 'Test' ),
$this->getParserOptions()
);
- $this->assertEquals( "<p>FooOneBaz\n</p>", $parserOutput->getText() );
+ $this->assertEquals( "<p>FooOneBaz\n</p>", $parserOutput->getText( [ 'unwrap' => true ] ) );
$parser->mPreprocessor = null; # Break the Parser <-> Preprocessor cycle
}
@@ -98,7 +98,7 @@ class TagHookTest extends MediaWikiTestCase {
Title::newFromText( 'Test' ),
$this->getParserOptions()
);
- $this->assertEquals( "<p>FooOneBaz\n</p>", $parserOutput->getText() );
+ $this->assertEquals( "<p>FooOneBaz\n</p>", $parserOutput->getText( [ 'unwrap' => true ] ) );
$parser->mPreprocessor = null; # Break the Parser <-> Preprocessor cycle
}
diff --git a/www/wiki/tests/phpunit/includes/parser/TidyTest.php b/www/wiki/tests/phpunit/includes/parser/TidyTest.php
index 62b84aa1..be5125c7 100644
--- a/www/wiki/tests/phpunit/includes/parser/TidyTest.php
+++ b/www/wiki/tests/phpunit/includes/parser/TidyTest.php
@@ -55,8 +55,8 @@ MathML;
'<editsection> should survive tidy'
],
[ '<mw:toc>foo</mw:toc>', '<mw:toc>foo</mw:toc>', '<mw:toc> should survive tidy' ],
- [ "<link foo=\"bar\" />\nfoo", '<link foo="bar"/>foo', '<link> should survive tidy' ],
- [ "<meta foo=\"bar\" />\nfoo", '<meta foo="bar"/>foo', '<meta> should survive tidy' ],
+ [ "<link foo=\"bar\" />foo", '<link foo="bar"/>foo', '<link> should survive tidy' ],
+ [ "<meta foo=\"bar\" />foo", '<meta foo="bar"/>foo', '<meta> should survive tidy' ],
[ $testMathML, $testMathML, '<math> should survive tidy' ],
];
}
diff --git a/www/wiki/tests/phpunit/includes/password/BcryptPasswordTest.php b/www/wiki/tests/phpunit/includes/password/BcryptPasswordTest.php
index 9b8e01e7..952f5417 100644
--- a/www/wiki/tests/phpunit/includes/password/BcryptPasswordTest.php
+++ b/www/wiki/tests/phpunit/includes/password/BcryptPasswordTest.php
@@ -10,13 +10,13 @@
class BcryptPasswordTest extends PasswordTestCase {
protected function getTypeConfigs() {
return [ 'bcrypt' => [
- 'class' => 'BcryptPassword',
+ 'class' => BcryptPassword::class,
'cost' => 9,
] ];
}
public static function providePasswordTests() {
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return [
// Tests from glibc bcrypt implementation
[ true, ':bcrypt:5$CCCCCCCCCCCCCCCCCCCCC.$E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW', "U*U" ],
@@ -39,6 +39,6 @@ class BcryptPasswordTest extends PasswordTestCase {
[ false, ':bcrypt:5$CCCCCCCCCCCCCCCCCCCCC.$E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW', "UXU" ],
[ false, ':bcrypt:5$CCCCCCCCCCCCCCCCCCCCC.$E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW', "" ],
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
}
diff --git a/www/wiki/tests/phpunit/includes/password/EncryptedPasswordTest.php b/www/wiki/tests/phpunit/includes/password/EncryptedPasswordTest.php
index 0c856537..6dfdea69 100644
--- a/www/wiki/tests/phpunit/includes/password/EncryptedPasswordTest.php
+++ b/www/wiki/tests/phpunit/includes/password/EncryptedPasswordTest.php
@@ -4,13 +4,12 @@
* @covers EncryptedPassword
* @covers ParameterizedPassword
* @covers Password
- * @codingStandardsIgnoreStart Generic.Files.LineLength
*/
class EncryptedPasswordTest extends PasswordTestCase {
protected function getTypeConfigs() {
return [
'both' => [
- 'class' => 'EncryptedPassword',
+ 'class' => EncryptedPassword::class,
'underlying' => 'pbkdf2',
'secrets' => [
md5( 'secret1' ),
@@ -19,7 +18,7 @@ class EncryptedPasswordTest extends PasswordTestCase {
'cipher' => 'aes-256-cbc',
],
'secret1' => [
- 'class' => 'EncryptedPassword',
+ 'class' => EncryptedPassword::class,
'underlying' => 'pbkdf2',
'secrets' => [
md5( 'secret1' ),
@@ -27,7 +26,7 @@ class EncryptedPasswordTest extends PasswordTestCase {
'cipher' => 'aes-256-cbc',
],
'secret2' => [
- 'class' => 'EncryptedPassword',
+ 'class' => EncryptedPassword::class,
'underlying' => 'pbkdf2',
'secrets' => [
md5( 'secret2' ),
@@ -35,7 +34,7 @@ class EncryptedPasswordTest extends PasswordTestCase {
'cipher' => 'aes-256-cbc',
],
'pbkdf2' => [
- 'class' => 'Pbkdf2Password',
+ 'class' => Pbkdf2Password::class,
'algo' => 'sha256',
'cost' => '10',
'length' => '64',
@@ -44,6 +43,7 @@ class EncryptedPasswordTest extends PasswordTestCase {
}
public static function providePasswordTests() {
+ // phpcs:disable Generic.Files.LineLength
return [
// Encrypted with secret1
[ true, ':both:aes-256-cbc:0:izBpxujqC1YbzpCB3qAzgg==:ZqHnitT1pL4YJqKqFES2KEevZYSy2LtlibW5+IMi4XKOGKGy6sE638BXyBbLQQsBtTSrt+JyzwOayKtwIfRbaQsBridx/O1JwBSai1TkGkOsYMBXnlu2Bu/EquCBj5QpjYh7p3Uq4rpiop1KQlin1BJMwnAa1PovhxjpxnYhlhkM4X5ALoGi3XM0bapN48vt', 'password' ],
@@ -54,6 +54,7 @@ class EncryptedPasswordTest extends PasswordTestCase {
[ true, ':both:aes-256-cbc:1:m1LCnQVIakfYBNlr9KEgQg==:5yPTctqrzsybdgaMEag18AZYbnL37pAtXVBqmWxkjXbnNmiDH+1bHoL8lsEVTH/sJntC82kNVgE7zeiD8xUVLYF2VUnvB5+sU+aysE45/zwsCu7a22TaischMAOWrsHZ/tIgS/TnZY2d+HNyxgsEeeYf/QoL+FhmqHquK02+4SRbA5lLuj9niYy1r5CoM9cQ', 'password' ],
[ true, ':secret2:aes-256-cbc:0:m1LCnQVIakfYBNlr9KEgQg==:5yPTctqrzsybdgaMEag18AZYbnL37pAtXVBqmWxkjXbnNmiDH+1bHoL8lsEVTH/sJntC82kNVgE7zeiD8xUVLYF2VUnvB5+sU+aysE45/zwsCu7a22TaischMAOWrsHZ/tIgS/TnZY2d+HNyxgsEeeYf/QoL+FhmqHquK02+4SRbA5lLuj9niYy1r5CoM9cQ', 'password' ],
];
+ // phpcs:enable
}
/**
@@ -61,12 +62,14 @@ class EncryptedPasswordTest extends PasswordTestCase {
* @expectedException PasswordError
*/
public function testDecryptionError() {
+ // phpcs:ignore Generic.Files.LineLength
$hash = ':secret1:aes-256-cbc:0:m1LCnQVIakfYBNlr9KEgQg==:5yPTctqrzsybdgaMEag18AZYbnL37pAtXVBqmWxkjXbnNmiDH+1bHoL8lsEVTH/sJntC82kNVgE7zeiD8xUVLYF2VUnvB5+sU+aysE45/zwsCu7a22TaischMAOWrsHZ/tIgS/TnZY2d+HNyxgsEeeYf/QoL+FhmqHquK02+4SRbA5lLuj9niYy1r5CoM9cQ';
$password = $this->passwordFactory->newFromCiphertext( $hash );
$password->crypt( 'password' );
}
public function testUpdate() {
+ // phpcs:ignore Generic.Files.LineLength
$hash = ':both:aes-256-cbc:0:izBpxujqC1YbzpCB3qAzgg==:ZqHnitT1pL4YJqKqFES2KEevZYSy2LtlibW5+IMi4XKOGKGy6sE638BXyBbLQQsBtTSrt+JyzwOayKtwIfRbaQsBridx/O1JwBSai1TkGkOsYMBXnlu2Bu/EquCBj5QpjYh7p3Uq4rpiop1KQlin1BJMwnAa1PovhxjpxnYhlhkM4X5ALoGi3XM0bapN48vt';
$fromHash = $this->passwordFactory->newFromCiphertext( $hash );
$fromPlaintext = $this->passwordFactory->newFromPlaintext( 'password', $fromHash );
diff --git a/www/wiki/tests/phpunit/includes/password/LayeredParameterizedPasswordTest.php b/www/wiki/tests/phpunit/includes/password/LayeredParameterizedPasswordTest.php
index cf96d067..6a965a03 100644
--- a/www/wiki/tests/phpunit/includes/password/LayeredParameterizedPasswordTest.php
+++ b/www/wiki/tests/phpunit/includes/password/LayeredParameterizedPasswordTest.php
@@ -8,7 +8,7 @@ class LayeredParameterizedPasswordTest extends PasswordTestCase {
protected function getTypeConfigs() {
return [
'testLargeLayeredTop' => [
- 'class' => 'LayeredParameterizedPassword',
+ 'class' => LayeredParameterizedPassword::class,
'types' => [
'testLargeLayeredBottom',
'testLargeLayeredBottom',
@@ -18,13 +18,13 @@ class LayeredParameterizedPasswordTest extends PasswordTestCase {
],
],
'testLargeLayeredBottom' => [
- 'class' => 'Pbkdf2Password',
+ 'class' => Pbkdf2Password::class,
'algo' => 'sha512',
'cost' => 1024,
'length' => 512,
],
'testLargeLayeredFinal' => [
- 'class' => 'BcryptPassword',
+ 'class' => BcryptPassword::class,
'cost' => 5,
]
];
@@ -35,15 +35,15 @@ class LayeredParameterizedPasswordTest extends PasswordTestCase {
}
public static function providePasswordTests() {
- // @codingStandardsIgnoreStart Generic.Files.LineLength.TooLong
+ // phpcs:disable Generic.Files.LineLength
return [
[
true,
':testLargeLayeredTop:sha512:1024:512!sha512:1024:512!sha512:1024:512!sha512:1024:512!5!vnRy+2SrSA0fHt3dwhTP5g==!AVnwfZsAQjn+gULv7FSGjA==!xvHUX3WcpkeSn1lvjWcvBg==!It+OC/N9tu+d3ByHhuB0BQ==!Tb.gqUOiD.aWktVwHM.Q/O!7CcyMfXUPky5ptyATJsR2nq3vUqtnBC',
- 'testPassword123'
+ 'testPassword123'
],
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
/**
diff --git a/www/wiki/tests/phpunit/includes/password/MWOldPasswordTest.php b/www/wiki/tests/phpunit/includes/password/MWOldPasswordTest.php
index 51e739ca..50100826 100644
--- a/www/wiki/tests/phpunit/includes/password/MWOldPasswordTest.php
+++ b/www/wiki/tests/phpunit/includes/password/MWOldPasswordTest.php
@@ -8,7 +8,7 @@
class MWOldPasswordTest extends PasswordTestCase {
protected function getTypeConfigs() {
return [ 'A' => [
- 'class' => 'MWOldPassword',
+ 'class' => MWOldPassword::class,
] ];
}
diff --git a/www/wiki/tests/phpunit/includes/password/MWSaltedPasswordTest.php b/www/wiki/tests/phpunit/includes/password/MWSaltedPasswordTest.php
index 53a6ad13..5616868d 100644
--- a/www/wiki/tests/phpunit/includes/password/MWSaltedPasswordTest.php
+++ b/www/wiki/tests/phpunit/includes/password/MWSaltedPasswordTest.php
@@ -8,7 +8,7 @@
class MWSaltedPasswordTest extends PasswordTestCase {
protected function getTypeConfigs() {
return [ 'B' => [
- 'class' => 'MWSaltedPassword',
+ 'class' => MWSaltedPassword::class,
] ];
}
diff --git a/www/wiki/tests/phpunit/includes/password/PasswordFactoryTest.php b/www/wiki/tests/phpunit/includes/password/PasswordFactoryTest.php
index 5d585f32..01b0de2c 100644
--- a/www/wiki/tests/phpunit/includes/password/PasswordFactoryTest.php
+++ b/www/wiki/tests/phpunit/includes/password/PasswordFactoryTest.php
@@ -6,14 +6,14 @@
class PasswordFactoryTest extends MediaWikiTestCase {
public function testRegister() {
$pf = new PasswordFactory;
- $pf->register( 'foo', [ 'class' => 'InvalidPassword' ] );
+ $pf->register( 'foo', [ 'class' => InvalidPassword::class ] );
$this->assertArrayHasKey( 'foo', $pf->getTypes() );
}
public function testSetDefaultType() {
$pf = new PasswordFactory;
- $pf->register( '1', [ 'class' => 'InvalidPassword' ] );
- $pf->register( '2', [ 'class' => 'InvalidPassword' ] );
+ $pf->register( '1', [ 'class' => InvalidPassword::class ] );
+ $pf->register( '2', [ 'class' => InvalidPassword::class ] );
$pf->setDefaultType( '1' );
$this->assertSame( '1', $pf->getDefaultType() );
$pf->setDefaultType( '2' );
@@ -31,7 +31,7 @@ class PasswordFactoryTest extends MediaWikiTestCase {
public function testInit() {
$config = new HashConfig( [
'PasswordConfig' => [
- 'foo' => [ 'class' => 'InvalidPassword' ],
+ 'foo' => [ 'class' => InvalidPassword::class ],
],
'PasswordDefault' => 'foo'
] );
@@ -43,7 +43,7 @@ class PasswordFactoryTest extends MediaWikiTestCase {
public function testNewFromCiphertext() {
$pf = new PasswordFactory;
- $pf->register( 'B', [ 'class' => 'MWSaltedPassword' ] );
+ $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
$pw = $pf->newFromCiphertext( ':B:salt:d529e941509eb9e9b9cfaeae1fe7ca23' );
$this->assertInstanceOf( MWSaltedPassword::class, $pw );
}
@@ -58,13 +58,13 @@ class PasswordFactoryTest extends MediaWikiTestCase {
*/
public function testNewFromCiphertextErrors( $hash ) {
$pf = new PasswordFactory;
- $pf->register( 'B', [ 'class' => 'MWSaltedPassword' ] );
+ $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
$pf->newFromCiphertext( $hash );
}
public function testNewFromType() {
$pf = new PasswordFactory;
- $pf->register( 'B', [ 'class' => 'MWSaltedPassword' ] );
+ $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
$pw = $pf->newFromType( 'B' );
$this->assertInstanceOf( MWSaltedPassword::class, $pw );
}
@@ -74,26 +74,26 @@ class PasswordFactoryTest extends MediaWikiTestCase {
*/
public function testNewFromTypeError() {
$pf = new PasswordFactory;
- $pf->register( 'B', [ 'class' => 'MWSaltedPassword' ] );
+ $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
$pf->newFromType( 'bogus' );
}
public function testNewFromPlaintext() {
$pf = new PasswordFactory;
- $pf->register( 'A', [ 'class' => 'MWOldPassword' ] );
- $pf->register( 'B', [ 'class' => 'MWSaltedPassword' ] );
+ $pf->register( 'A', [ 'class' => MWOldPassword::class ] );
+ $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
$pf->setDefaultType( 'A' );
- $this->assertInstanceOf( 'InvalidPassword', $pf->newFromPlaintext( null ) );
- $this->assertInstanceOf( 'MWOldPassword', $pf->newFromPlaintext( 'password' ) );
- $this->assertInstanceOf( 'MWSaltedPassword',
+ $this->assertInstanceOf( InvalidPassword::class, $pf->newFromPlaintext( null ) );
+ $this->assertInstanceOf( MWOldPassword::class, $pf->newFromPlaintext( 'password' ) );
+ $this->assertInstanceOf( MWSaltedPassword::class,
$pf->newFromPlaintext( 'password', $pf->newFromType( 'B' ) ) );
}
public function testNeedsUpdate() {
$pf = new PasswordFactory;
- $pf->register( 'A', [ 'class' => 'MWOldPassword' ] );
- $pf->register( 'B', [ 'class' => 'MWSaltedPassword' ] );
+ $pf->register( 'A', [ 'class' => MWOldPassword::class ] );
+ $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
$pf->setDefaultType( 'A' );
$this->assertFalse( $pf->needsUpdate( $pf->newFromType( 'A' ) ) );
@@ -105,6 +105,6 @@ class PasswordFactoryTest extends MediaWikiTestCase {
}
public function testNewInvalidPassword() {
- $this->assertInstanceOf( 'InvalidPassword', PasswordFactory::newInvalidPassword() );
+ $this->assertInstanceOf( InvalidPassword::class, PasswordFactory::newInvalidPassword() );
}
}
diff --git a/www/wiki/tests/phpunit/includes/password/PasswordTest.php b/www/wiki/tests/phpunit/includes/password/PasswordTest.php
index d0177d0d..65c91993 100644
--- a/www/wiki/tests/phpunit/includes/password/PasswordTest.php
+++ b/www/wiki/tests/phpunit/includes/password/PasswordTest.php
@@ -36,6 +36,6 @@ class PasswordTest extends MediaWikiTestCase {
$passwordFactory = new PasswordFactory();
$invalid = $passwordFactory->newFromPlaintext( null );
- $this->assertInstanceOf( 'InvalidPassword', $invalid );
+ $this->assertInstanceOf( InvalidPassword::class, $invalid );
}
}
diff --git a/www/wiki/tests/phpunit/includes/password/Pbkdf2PasswordFallbackTest.php b/www/wiki/tests/phpunit/includes/password/Pbkdf2PasswordFallbackTest.php
index 605d1905..cf851c81 100644
--- a/www/wiki/tests/phpunit/includes/password/Pbkdf2PasswordFallbackTest.php
+++ b/www/wiki/tests/phpunit/includes/password/Pbkdf2PasswordFallbackTest.php
@@ -9,7 +9,7 @@ class Pbkdf2PasswordFallbackTest extends PasswordTestCase {
protected function getTypeConfigs() {
return [
'pbkdf2' => [
- 'class' => 'Pbkdf2Password',
+ 'class' => Pbkdf2Password::class,
'algo' => 'sha256',
'cost' => '10000',
'length' => '128',
diff --git a/www/wiki/tests/phpunit/includes/password/Pbkdf2PasswordTest.php b/www/wiki/tests/phpunit/includes/password/Pbkdf2PasswordTest.php
index ff069cd9..7e97ab1a 100644
--- a/www/wiki/tests/phpunit/includes/password/Pbkdf2PasswordTest.php
+++ b/www/wiki/tests/phpunit/includes/password/Pbkdf2PasswordTest.php
@@ -10,7 +10,7 @@
class Pbkdf2PasswordTest extends PasswordTestCase {
protected function getTypeConfigs() {
return [ 'pbkdf2' => [
- 'class' => 'Pbkdf2Password',
+ 'class' => Pbkdf2Password::class,
'algo' => 'sha256',
'cost' => '10000',
'length' => '128',
diff --git a/www/wiki/tests/phpunit/includes/password/UserPasswordPolicyTest.php b/www/wiki/tests/phpunit/includes/password/UserPasswordPolicyTest.php
index 0839cfbb..78175fac 100644
--- a/www/wiki/tests/phpunit/includes/password/UserPasswordPolicyTest.php
+++ b/www/wiki/tests/phpunit/includes/password/UserPasswordPolicyTest.php
@@ -26,6 +26,8 @@
*/
class UserPasswordPolicyTest extends MediaWikiTestCase {
+ protected $tablesUsed = [ 'user', 'user_groups' ];
+
protected $policies = [
'checkuser' => [
'MinimalPasswordLength' => 10,
diff --git a/www/wiki/tests/phpunit/includes/poolcounter/PoolCounterTest.php b/www/wiki/tests/phpunit/includes/poolcounter/PoolCounterTest.php
index d57ad041..f7f2013c 100644
--- a/www/wiki/tests/phpunit/includes/poolcounter/PoolCounterTest.php
+++ b/www/wiki/tests/phpunit/includes/poolcounter/PoolCounterTest.php
@@ -9,6 +9,9 @@ abstract class PoolCounterAbstractMock extends PoolCounter {
}
}
+/**
+ * @covers PoolCounter
+ */
class PoolCounterTest extends MediaWikiTestCase {
public function testConstruct() {
$poolCounterConfig = [
@@ -18,13 +21,13 @@ class PoolCounterTest extends MediaWikiTestCase {
'maxqueue' => 100,
];
- $poolCounter = $this->getMockBuilder( 'PoolCounterAbstractMock' )
+ $poolCounter = $this->getMockBuilder( PoolCounterAbstractMock::class )
->setConstructorArgs( [ $poolCounterConfig, 'testCounter', 'someKey' ] )
// don't mock anything - the proper syntax would be setMethods(null), but due
// to a PHPUnit bug that does not work with getMockForAbstractClass()
->setMethods( [ 'idontexist' ] )
->getMockForAbstractClass();
- $this->assertInstanceOf( 'PoolCounter', $poolCounter );
+ $this->assertInstanceOf( PoolCounter::class, $poolCounter );
}
public function testConstructWithSlots() {
@@ -36,15 +39,15 @@ class PoolCounterTest extends MediaWikiTestCase {
'maxqueue' => 100,
];
- $poolCounter = $this->getMockBuilder( 'PoolCounterAbstractMock' )
+ $poolCounter = $this->getMockBuilder( PoolCounterAbstractMock::class )
->setConstructorArgs( [ $poolCounterConfig, 'testCounter', 'key' ] )
->setMethods( [ 'idontexist' ] ) // don't mock anything
->getMockForAbstractClass();
- $this->assertInstanceOf( 'PoolCounter', $poolCounter );
+ $this->assertInstanceOf( PoolCounter::class, $poolCounter );
}
public function testHashKeyIntoSlots() {
- $poolCounter = $this->getMockBuilder( 'PoolCounterAbstractMock' )
+ $poolCounter = $this->getMockBuilder( PoolCounterAbstractMock::class )
// don't mock anything - the proper syntax would be setMethods(null), but due
// to a PHPUnit bug that does not work with getMockForAbstractClass()
->setMethods( [ 'idontexist' ] )
diff --git a/www/wiki/tests/phpunit/includes/preferences/DefaultPreferencesFactoryTest.php b/www/wiki/tests/phpunit/includes/preferences/DefaultPreferencesFactoryTest.php
new file mode 100644
index 00000000..c1015234
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/preferences/DefaultPreferencesFactoryTest.php
@@ -0,0 +1,183 @@
+<?php
+
+use MediaWiki\Auth\AuthManager;
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Preferences\DefaultPreferencesFactory;
+use Wikimedia\ObjectFactory;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * @group Preferences
+ */
+class DefaultPreferencesFactoryTest extends MediaWikiTestCase {
+
+ /** @var IContextSource */
+ protected $context;
+
+ /** @var Config */
+ protected $config;
+
+ public function setUp() {
+ parent::setUp();
+ global $wgParserConf;
+ $this->context = new RequestContext();
+ $this->context->setTitle( Title::newFromText( self::class ) );
+ $this->setMwGlobals( 'wgParser',
+ ObjectFactory::constructClassInstance( $wgParserConf['class'], [ $wgParserConf ] )
+ );
+ $this->config = MediaWikiServices::getInstance()->getMainConfig();
+ }
+
+ /**
+ * Get a basic PreferencesFactory for testing with.
+ * @return DefaultPreferencesFactory
+ */
+ protected function getPreferencesFactory() {
+ return new DefaultPreferencesFactory(
+ $this->config,
+ new Language(),
+ AuthManager::singleton(),
+ MediaWikiServices::getInstance()->getLinkRenderer()
+ );
+ }
+
+ /**
+ * @covers MediaWiki\Preferences\DefaultPreferencesFactory::getForm()
+ */
+ public function testGetForm() {
+ $testUser = $this->getTestUser();
+ $form = $this->getPreferencesFactory()->getForm( $testUser->getUser(), $this->context );
+ $this->assertInstanceOf( PreferencesForm::class, $form );
+ $this->assertCount( 5, $form->getPreferenceSections() );
+ }
+
+ /**
+ * CSS classes for emailauthentication preference field when there's no email.
+ * @see https://phabricator.wikimedia.org/T36302
+ * @covers MediaWiki\Preferences\DefaultPreferencesFactory::profilePreferences()
+ * @dataProvider emailAuthenticationProvider
+ */
+ public function testEmailAuthentication( $user, $cssClass ) {
+ $prefs = $this->getPreferencesFactory()->getFormDescriptor( $user, $this->context );
+ $this->assertArrayHasKey( 'cssclass', $prefs['emailauthentication'] );
+ $this->assertEquals( $cssClass, $prefs['emailauthentication']['cssclass'] );
+ }
+
+ public function emailAuthenticationProvider() {
+ $userNoEmail = new User;
+ $userEmailUnauthed = new User;
+ $userEmailUnauthed->setEmail( 'noauth@example.org' );
+ $userEmailAuthed = new User;
+ $userEmailAuthed->setEmail( 'noauth@example.org' );
+ $userEmailAuthed->setEmailAuthenticationTimestamp( wfTimestamp() );
+ return [
+ [ $userNoEmail, 'mw-email-none' ],
+ [ $userEmailUnauthed, 'mw-email-not-authenticated' ],
+ [ $userEmailAuthed, 'mw-email-authenticated' ],
+ ];
+ }
+
+ /**
+ * Test that PreferencesFormPreSave hook has correct data:
+ * - user Object is passed
+ * - oldUserOptions contains previous user options (before save)
+ * - formData and User object have set up new properties
+ *
+ * @see https://phabricator.wikimedia.org/T169365
+ * @covers MediaWiki\Preferences\DefaultPreferencesFactory::submitForm()
+ */
+ public function testPreferencesFormPreSaveHookHasCorrectData() {
+ $oldOptions = [
+ 'test' => 'abc',
+ 'option' => 'old'
+ ];
+ $newOptions = [
+ 'test' => 'abc',
+ 'option' => 'new'
+ ];
+ $configMock = new HashConfig( [
+ 'HiddenPrefs' => []
+ ] );
+ $form = $this->getMockBuilder( PreferencesForm::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $userMock = $this->getMockBuilder( User::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $userMock->method( 'getOptions' )
+ ->willReturn( $oldOptions );
+ $userMock->method( 'isAllowedAny' )
+ ->willReturn( true );
+ $userMock->method( 'isAllowed' )
+ ->willReturn( true );
+
+ $userMock->expects( $this->exactly( 2 ) )
+ ->method( 'setOption' )
+ ->withConsecutive(
+ [ $this->equalTo( 'test' ), $this->equalTo( $newOptions[ 'test' ] ) ],
+ [ $this->equalTo( 'option' ), $this->equalTo( $newOptions[ 'option' ] ) ]
+ );
+
+ $form->expects( $this->any() )
+ ->method( 'getModifiedUser' )
+ ->willReturn( $userMock );
+
+ $form->expects( $this->any() )
+ ->method( 'getContext' )
+ ->willReturn( $this->context );
+
+ $form->expects( $this->any() )
+ ->method( 'getConfig' )
+ ->willReturn( $configMock );
+
+ $this->setTemporaryHook( 'PreferencesFormPreSave',
+ function ( $formData, $form, $user, &$result, $oldUserOptions )
+ use ( $newOptions, $oldOptions, $userMock ) {
+ $this->assertSame( $userMock, $user );
+ foreach ( $newOptions as $option => $value ) {
+ $this->assertSame( $value, $formData[ $option ] );
+ }
+ foreach ( $oldOptions as $option => $value ) {
+ $this->assertSame( $value, $oldUserOptions[ $option ] );
+ }
+ $this->assertEquals( true, $result );
+ }
+ );
+
+ $factory = TestingAccessWrapper::newFromObject( $this->getPreferencesFactory() );
+ $factory->saveFormData( $newOptions, $form );
+ }
+
+ /**
+ * The rclimit preference should accept non-integer input and filter it to become an integer.
+ */
+ public function testIntvalFilter() {
+ // Test a string with leading zeros (i.e. not octal) and spaces.
+ $this->context->getRequest()->setVal( 'wprclimit', ' 0012 ' );
+ $user = new User;
+ $form = $this->getPreferencesFactory()->getForm( $user, $this->context );
+ $form->show();
+ $form->trySubmit();
+ $this->assertEquals( 12, $user->getOption( 'rclimit' ) );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/rcfeed/RCFeedIntegrationTest.php b/www/wiki/tests/phpunit/includes/rcfeed/RCFeedIntegrationTest.php
index 3e9c567e..871ea911 100644
--- a/www/wiki/tests/phpunit/includes/rcfeed/RCFeedIntegrationTest.php
+++ b/www/wiki/tests/phpunit/includes/rcfeed/RCFeedIntegrationTest.php
@@ -1,7 +1,13 @@
<?php
/**
+ * @group medium
* @group Database
+ * @covers FormattedRCFeed
+ * @covers RecentChange
+ * @covers JSONRCFeedFormatter
+ * @covers MachineReadableRCFeedFormatter
+ * @covers RCFeed
*/
class RCFeedIntegrationTest extends MediaWikiTestCase {
protected function setUp() {
@@ -17,18 +23,9 @@ class RCFeedIntegrationTest extends MediaWikiTestCase {
] );
}
- /**
- * @covers RecentChange::notifyRCFeeds
- * @covers RecentChange::getEngine
- * @covers RCFeed::factory
- * @covers FormattedRCFeed::__construct
- * @covers FormattedRCFeed::notify
- * @covers JSONRCFeedFormatter::formatArray
- * @covers MachineReadableRCFeedFormatter::getLine
- */
public function testNotify() {
- $feed = $this->getMockBuilder( 'RCFeedEngine' )
- ->setConstructorArgs( [ [ 'formatter' => 'JSONRCFeedFormatter' ] ] )
+ $feed = $this->getMockBuilder( RCFeedEngine::class )
+ ->setConstructorArgs( [ [ 'formatter' => JSONRCFeedFormatter::class ] ] )
->setMethods( [ 'send' ] )
->getMock();
@@ -71,7 +68,7 @@ class RCFeedIntegrationTest extends MediaWikiTestCase {
'wgRCFeeds' => [
'myfeed' => [
'uri' => 'test://localhost:1234',
- 'formatter' => 'JSONRCFeedFormatter',
+ 'formatter' => JSONRCFeedFormatter::class,
],
],
'wgRCEngines' => [
diff --git a/www/wiki/tests/phpunit/includes/registration/CoreVersionCheckerTest.php b/www/wiki/tests/phpunit/includes/registration/CoreVersionCheckerTest.php
deleted file mode 100644
index 4aa4f415..00000000
--- a/www/wiki/tests/phpunit/includes/registration/CoreVersionCheckerTest.php
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-
-/**
- * @covers CoreVersionChecker
- */
-class CoreVersionCheckerTest extends PHPUnit_Framework_TestCase {
- /**
- * @dataProvider provideCheck
- */
- public function testCheck( $coreVersion, $constraint, $expected ) {
- $checker = new CoreVersionChecker( $coreVersion );
- $this->assertEquals( $expected, $checker->check( $constraint ) );
- }
-
- public static function provideCheck() {
- return [
- // array( $wgVersion, constraint, expected )
- [ '1.25alpha', '>= 1.26', false ],
- [ '1.25.0', '>= 1.26', false ],
- [ '1.26alpha', '>= 1.26', true ],
- [ '1.26alpha', '>= 1.26.0', true ],
- [ '1.26alpha', '>= 1.26.0-stable', false ],
- [ '1.26.0', '>= 1.26.0-stable', true ],
- [ '1.26.1', '>= 1.26.0-stable', true ],
- [ '1.27.1', '>= 1.26.0-stable', true ],
- [ '1.26alpha', '>= 1.26.1', false ],
- [ '1.26alpha', '>= 1.26alpha', true ],
- [ '1.26alpha', '>= 1.25', true ],
- [ '1.26.0-alpha.14', '>= 1.26.0-alpha.15', false ],
- [ '1.26.0-alpha.14', '>= 1.26.0-alpha.10', true ],
- [ '1.26.1', '>= 1.26.2, <=1.26.0', false ],
- [ '1.26.1', '^1.26.2', false ],
- // Accept anything for un-parsable version strings
- [ '1.26mwf14', '== 1.25alpha', true ],
- [ 'totallyinvalid', '== 1.0', true ],
- ];
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/registration/ExtensionJsonValidatorTest.php b/www/wiki/tests/phpunit/includes/registration/ExtensionJsonValidatorTest.php
new file mode 100644
index 00000000..d69ad597
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/registration/ExtensionJsonValidatorTest.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Copyright (C) 2018 Kunal Mehta <legoktm@member.fsf.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/**
+ * @covers ExtensionJsonValidator
+ */
+class ExtensionJsonValidatorTest extends MediaWikiTestCase {
+
+ /**
+ * @dataProvider provideValidate
+ */
+ public function testValidate( $file, $expected ) {
+ // If a dependency is missing, skip this test.
+ $validator = new ExtensionJsonValidator( function ( $msg ) {
+ $this->markTestSkipped( $msg );
+ } );
+
+ if ( is_string( $expected ) ) {
+ $this->setExpectedException(
+ ExtensionJsonValidationError::class,
+ $expected
+ );
+ }
+
+ $dir = __DIR__ . '/../../data/registration/';
+ $this->assertSame(
+ $expected,
+ $validator->validate( $dir . $file )
+ );
+ }
+
+ public function provideValidate() {
+ return [
+ [
+ 'notjson.txt',
+ 'notjson.txt is not valid JSON'
+ ],
+ [
+ 'no_manifest_version.json',
+ 'no_manifest_version.json does not have manifest_version set.'
+ ],
+ [
+ 'old_manifest_version.json',
+ 'old_manifest_version.json is using a non-supported schema version'
+ ],
+ [
+ 'newer_manifest_version.json',
+ 'newer_manifest_version.json is using a non-supported schema version'
+ ],
+ [
+ 'bad_spdx.json',
+ "bad_spdx.json did not pass validation.
+[license-name] Invalid SPDX license identifier, see <https://spdx.org/licenses/>"
+ ],
+ [
+ 'invalid.json',
+ "invalid.json did not pass validation.
+[license-name] Array value found, but a string is required"
+ ],
+ [
+ 'good.json',
+ true
+ ],
+ ];
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/registration/ExtensionProcessorTest.php b/www/wiki/tests/phpunit/includes/registration/ExtensionProcessorTest.php
index 7b56def1..d9e091dc 100644
--- a/www/wiki/tests/phpunit/includes/registration/ExtensionProcessorTest.php
+++ b/www/wiki/tests/phpunit/includes/registration/ExtensionProcessorTest.php
@@ -2,6 +2,9 @@
use Wikimedia\TestingAccessWrapper;
+/**
+ * @covers ExtensionProcessor
+ */
class ExtensionProcessorTest extends MediaWikiTestCase {
private $dir, $dirname;
@@ -21,9 +24,6 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
'name' => 'FooBar',
];
- /**
- * @covers ExtensionProcessor::extractInfo
- */
public function testExtractInfo() {
// Test that attributes that begin with @ are ignored
$processor = new ExtensionProcessor();
@@ -31,6 +31,8 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
'@metadata' => [ 'foobarbaz' ],
'AnAttribute' => [ 'omg' ],
'AutoloadClasses' => [ 'FooBar' => 'includes/FooBar.php' ],
+ 'SpecialPages' => [ 'Foo' => 'SpecialFoo' ],
+ 'callback' => 'FooBar::onRegistration',
], 1 );
$extracted = $processor->getExtractedInfo();
@@ -38,12 +40,17 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
$this->assertArrayHasKey( 'AnAttribute', $attributes );
$this->assertArrayNotHasKey( '@metadata', $attributes );
$this->assertArrayNotHasKey( 'AutoloadClasses', $attributes );
+ $this->assertSame(
+ [ 'FooBar' => 'FooBar::onRegistration' ],
+ $extracted['callbacks']
+ );
+ $this->assertSame(
+ [ 'Foo' => 'SpecialFoo' ],
+ $extracted['globals']['wgSpecialPages']
+ );
}
- /**
- * @covers ExtensionProcessor::extractInfo
- */
- public function testExtractInfo_namespaces() {
+ public function testExtractNamespaces() {
// Test that namespace IDs can be overwritten
if ( !defined( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X' ) ) {
define( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X', 123456 );
@@ -56,13 +63,20 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
'id' => 332200,
'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A',
'name' => 'Test_A',
- 'content' => 'TestModel'
+ 'defaultcontentmodel' => 'TestModel',
+ 'gender' => [
+ 'male' => 'Male test',
+ 'female' => 'Female test',
+ ],
+ 'subpages' => true,
+ 'content' => true,
+ 'protection' => 'userright',
],
[ // Test_X will use ID 123456 not 334400
'id' => 334400,
'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X',
'name' => 'Test_X',
- 'content' => 'TestModel'
+ 'defaultcontentmodel' => 'TestModel'
],
]
], 1 );
@@ -90,6 +104,13 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
$this->assertSame( 'Test_X', $extracted['attributes']['ExtensionNamespaces'][123456] );
$this->assertSame( 'Test_A', $extracted['attributes']['ExtensionNamespaces'][332200] );
+ $this->assertSame(
+ [ 'male' => 'Male test', 'female' => 'Female test' ],
+ $extracted['globals']['wgExtraGenderNamespaces'][332200]
+ );
+ // A has subpages, X does not
+ $this->assertTrue( $extracted['globals']['wgNamespacesWithSubpages'][332200] );
+ $this->assertArrayNotHasKey( 123456, $extracted['globals']['wgNamespacesWithSubpages'] );
}
public static function provideRegisterHooks() {
@@ -152,7 +173,6 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
}
/**
- * @covers ExtensionProcessor::extractHooks
* @dataProvider provideRegisterHooks
*/
public function testRegisterHooks( $pre, $info, $expected ) {
@@ -162,9 +182,6 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
$this->assertEquals( $expected, $extracted['globals']['wgHooks'] );
}
- /**
- * @covers ExtensionProcessor::extractConfig1
- */
public function testExtractConfig1() {
$processor = new ExtensionProcessor;
$info = [
@@ -191,9 +208,6 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
$this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
}
- /**
- * @covers ExtensionProcessor::extractConfig2
- */
public function testExtractConfig2() {
$processor = new ExtensionProcessor;
$info = [
@@ -201,6 +215,13 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
'Bar' => [ 'value' => 'somevalue' ],
'Foo' => [ 'value' => 10 ],
'Path' => [ 'value' => 'foo.txt', 'path' => true ],
+ 'Namespaces' => [
+ 'value' => [
+ '10' => true,
+ '12' => false,
+ ],
+ 'merge_strategy' => 'array_plus',
+ ],
],
] + self::$default;
$info2 = [
@@ -218,6 +239,50 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
$this->assertEquals( "{$this->dirname}/foo.txt", $extracted['globals']['wgPath'] );
// Custom prefix:
$this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
+ $this->assertSame(
+ [ 10 => true, 12 => false, ExtensionRegistry::MERGE_STRATEGY => 'array_plus' ],
+ $extracted['globals']['wgNamespaces']
+ );
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testDuplicateConfigKey1() {
+ $processor = new ExtensionProcessor;
+ $info = [
+ 'config' => [
+ 'Bar' => '',
+ ]
+ ] + self::$default;
+ $info2 = [
+ 'config' => [
+ 'Bar' => 'g',
+ ],
+ 'name' => 'FooBar2',
+ ];
+ $processor->extractInfo( $this->dir, $info, 1 );
+ $processor->extractInfo( $this->dir, $info2, 1 );
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testDuplicateConfigKey2() {
+ $processor = new ExtensionProcessor;
+ $info = [
+ 'config' => [
+ 'Bar' => [ 'value' => 'somevalue' ],
+ ]
+ ] + self::$default;
+ $info2 = [
+ 'config' => [
+ 'Bar' => [ 'value' => 'somevalue' ],
+ ],
+ 'name' => 'FooBar2',
+ ];
+ $processor->extractInfo( $this->dir, $info, 2 );
+ $processor->extractInfo( $this->dir, $info2, 2 );
}
public static function provideExtractExtensionMessagesFiles() {
@@ -245,7 +310,6 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
}
/**
- * @covers ExtensionProcessor::extractExtensionMessagesFiles
* @dataProvider provideExtractExtensionMessagesFiles
*/
public function testExtractExtensionMessagesFiles( $input, $expected ) {
@@ -272,7 +336,6 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
}
/**
- * @covers ExtensionProcessor::extractMessagesDirs
* @dataProvider provideExtractMessagesDirs
*/
public function testExtractMessagesDirs( $input, $expected ) {
@@ -284,18 +347,14 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
}
}
- /**
- * @covers ExtensionProcessor::extractCredits
- */
public function testExtractCredits() {
$processor = new ExtensionProcessor();
$processor->extractInfo( $this->dir, self::$default, 1 );
- $this->setExpectedException( 'Exception' );
+ $this->setExpectedException( Exception::class );
$processor->extractInfo( $this->dir, self::$default, 1 );
}
/**
- * @covers ExtensionProcessor::extractResourceLoaderModules
* @dataProvider provideExtractResourceLoaderModules
*/
public function testExtractResourceLoaderModules( $input, $expected ) {
@@ -338,8 +397,8 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
// Input
[
'ResourceFileModulePaths' => [
- 'localBasePath' => '',
- 'remoteExtPath' => 'FooBar',
+ 'localBasePath' => 'modules',
+ 'remoteExtPath' => 'FooBar/modules',
],
'ResourceModules' => [
// No paths
@@ -370,8 +429,8 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
'wgResourceModules' => [
'test.foo' => [
'styles' => 'foo.js',
- 'localBasePath' => $dir,
- 'remoteExtPath' => 'FooBar',
+ 'localBasePath' => "$dir/modules",
+ 'remoteExtPath' => 'FooBar/modules',
],
'test.bar' => [
'styles' => 'bar.js',
@@ -381,14 +440,14 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
'test.class' => [
'class' => 'FooBarModule',
'extra' => 'argument',
- 'localBasePath' => $dir,
- 'remoteExtPath' => 'FooBar',
+ 'localBasePath' => "$dir/modules",
+ 'remoteExtPath' => 'FooBar/modules',
],
'test.class.with.path' => [
'class' => 'FooBarPathModule',
'extra' => 'argument',
'localBasePath' => $dir,
- 'remoteExtPath' => 'FooBar',
+ 'remoteExtPath' => 'FooBar/modules',
]
],
],
@@ -501,9 +560,6 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
/**
* Attributes under manifest_version 2
- *
- * @covers ExtensionProcessor::extractAttributes
- * @covers ExtensionProcessor::getExtractedInfo
*/
public function testExtractAttributes() {
$processor = new ExtensionProcessor();
@@ -539,8 +595,6 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
/**
* Attributes under manifest_version 1
- *
- * @covers ExtensionProcessor::extractInfo
*/
public function testAttributes1() {
$processor = new ExtensionProcessor();
@@ -557,14 +611,104 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
],
1
);
+ $processor->extractInfo(
+ $this->dir,
+ [
+ 'name' => 'FooBar2',
+ 'FizzBuzzMorePlugins' => [
+ 'ext.bar.fizzbuzz',
+ ]
+ ],
+ 1
+ );
$info = $processor->getExtractedInfo();
$this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] );
$this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] );
$this->assertArrayHasKey( 'FizzBuzzMorePlugins', $info['attributes'] );
- $this->assertSame( [ 'ext.baz.fizzbuzz' ], $info['attributes']['FizzBuzzMorePlugins'] );
+ $this->assertSame(
+ [ 'ext.baz.fizzbuzz', 'ext.bar.fizzbuzz' ],
+ $info['attributes']['FizzBuzzMorePlugins']
+ );
+ }
+
+ public function testAttributes1_notarray() {
+ $processor = new ExtensionProcessor();
+ $this->setExpectedException(
+ InvalidArgumentException::class,
+ "The value for 'FooBarPlugins' should be an array (from {$this->dir})"
+ );
+ $processor->extractInfo(
+ $this->dir,
+ [
+ 'FooBarPlugins' => 'ext.baz.foobar',
+ ] + self::$default,
+ 1
+ );
}
+ public function testExtractPathBasedGlobal() {
+ $processor = new ExtensionProcessor();
+ $processor->extractInfo(
+ $this->dir,
+ [
+ 'ParserTestFiles' => [
+ 'tests/parserTests.txt',
+ 'tests/extraParserTests.txt',
+ ],
+ 'ServiceWiringFiles' => [
+ 'includes/ServiceWiring.php'
+ ],
+ ] + self::$default,
+ 1
+ );
+ $globals = $processor->getExtractedInfo()['globals'];
+ $this->assertArrayHasKey( 'wgParserTestFiles', $globals );
+ $this->assertSame( [
+ "{$this->dirname}/tests/parserTests.txt",
+ "{$this->dirname}/tests/extraParserTests.txt"
+ ], $globals['wgParserTestFiles'] );
+ $this->assertArrayHasKey( 'wgServiceWiringFiles', $globals );
+ $this->assertSame( [
+ "{$this->dirname}/includes/ServiceWiring.php"
+ ], $globals['wgServiceWiringFiles'] );
+ }
+
+ public function testGetRequirements() {
+ $info = self::$default + [
+ 'requires' => [
+ 'MediaWiki' => '>= 1.25.0',
+ 'extensions' => [
+ 'Bar' => '*'
+ ]
+ ]
+ ];
+ $processor = new ExtensionProcessor();
+ $this->assertSame(
+ $info['requires'],
+ $processor->getRequirements( $info )
+ );
+ $this->assertSame(
+ [],
+ $processor->getRequirements( [] )
+ );
+ }
+
+ public function testGetExtraAutoloaderPaths() {
+ $processor = new ExtensionProcessor();
+ $this->assertSame(
+ [ "{$this->dirname}/vendor/autoload.php" ],
+ $processor->getExtraAutoloaderPaths( $this->dirname, [
+ 'load_composer_autoloader' => true,
+ ] )
+ );
+ }
+
+ /**
+ * Verify that extension.schema.json is in sync with ExtensionProcessor
+ *
+ * @coversNothing
+ */
public function testGlobalSettingsDocumentedInSchema() {
global $IP;
$globalSettings = TestingAccessWrapper::newFromClass(
diff --git a/www/wiki/tests/phpunit/includes/registration/ExtensionRegistryTest.php b/www/wiki/tests/phpunit/includes/registration/ExtensionRegistryTest.php
index 9b57e1c3..67bc088d 100644
--- a/www/wiki/tests/phpunit/includes/registration/ExtensionRegistryTest.php
+++ b/www/wiki/tests/phpunit/includes/registration/ExtensionRegistryTest.php
@@ -1,9 +1,57 @@
<?php
+/**
+ * @covers ExtensionRegistry
+ */
class ExtensionRegistryTest extends MediaWikiTestCase {
+ private $dataDir;
+
+ public function setUp() {
+ parent::setUp();
+ $this->dataDir = __DIR__ . '/../../data/registration';
+ }
+
+ public function testQueue_invalid() {
+ $registry = new ExtensionRegistry();
+ $path = __DIR__ . '/doesnotexist.json';
+ $this->setExpectedException(
+ Exception::class,
+ "$path does not exist!"
+ );
+ $registry->queue( $path );
+ }
+
+ public function testQueue() {
+ $registry = new ExtensionRegistry();
+ $path = "{$this->dataDir}/good.json";
+ $registry->queue( $path );
+ $this->assertArrayHasKey(
+ $path,
+ $registry->getQueue()
+ );
+ $registry->clearQueue();
+ $this->assertEmpty( $registry->getQueue() );
+ }
+
+ public function testLoadFromQueue_empty() {
+ $registry = new ExtensionRegistry();
+ $registry->loadFromQueue();
+ $this->assertEmpty( $registry->getAllThings() );
+ }
+
+ public function testLoadFromQueue_late() {
+ $registry = new ExtensionRegistry();
+ $registry->finish();
+ $registry->queue( "{$this->dataDir}/good.json" );
+ $this->setExpectedException(
+ MWException::class,
+ "The following paths tried to load late: {$this->dataDir}/good.json"
+ );
+ $registry->loadFromQueue();
+ }
+
/**
- * @covers ExtensionRegistry::exportExtractedData
* @dataProvider provideExportExtractedDataGlobals
*/
public function testExportExtractedDataGlobals( $desc, $before, $globals, $expected ) {
@@ -28,7 +76,7 @@ class ExtensionRegistryTest extends MediaWikiTestCase {
'autoloaderPaths' => []
];
$registry = new ExtensionRegistry();
- $class = new ReflectionClass( 'ExtensionRegistry' );
+ $class = new ReflectionClass( ExtensionRegistry::class );
$method = $class->getMethod( 'exportExtractedData' );
$method->setAccessible( true );
$method->invokeArgs( $registry, [ $info ] );
@@ -287,6 +335,18 @@ class ExtensionRegistryTest extends MediaWikiTestCase {
],
],
],
+ [
+ 'global is null before',
+ [
+ 'NullGlobal' => null,
+ ],
+ [
+ 'NullGlobal' => 'not-null'
+ ],
+ [
+ 'NullGlobal' => null
+ ],
+ ],
];
}
}
diff --git a/www/wiki/tests/phpunit/includes/registration/VersionCheckerTest.php b/www/wiki/tests/phpunit/includes/registration/VersionCheckerTest.php
index 9ee58816..b668a9ad 100644
--- a/www/wiki/tests/phpunit/includes/registration/VersionCheckerTest.php
+++ b/www/wiki/tests/phpunit/includes/registration/VersionCheckerTest.php
@@ -3,7 +3,11 @@
/**
* @covers VersionChecker
*/
-class VersionCheckerTest extends PHPUnit_Framework_TestCase {
+class VersionCheckerTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+ use PHPUnit4And6Compat;
+
/**
* @dataProvider provideCheck
*/
@@ -13,8 +17,7 @@ class VersionCheckerTest extends PHPUnit_Framework_TestCase {
'FakeExtension' => [
'MediaWiki' => $constraint,
],
- ] )
- );
+ ] ) );
}
public static function provideCheck() {
@@ -46,16 +49,15 @@ class VersionCheckerTest extends PHPUnit_Framework_TestCase {
*/
public function testType( $given, $expected ) {
$checker = new VersionChecker( '1.0.0' );
- $checker
- ->setLoadedExtensionsAndSkins( [
+ $checker->setLoadedExtensionsAndSkins( [
'FakeDependency' => [
'version' => '1.0.0',
],
+ 'NoVersionGiven' => [],
] );
$this->assertEquals( $expected, $checker->checkArray( [
'FakeExtension' => $given,
- ] )
- );
+ ] ) );
}
public static function provideType() {
@@ -64,16 +66,81 @@ class VersionCheckerTest extends PHPUnit_Framework_TestCase {
[
[
'extensions' => [
- 'FakeDependency' => '1.0.0'
- ]
+ 'FakeDependency' => '1.0.0',
+ ],
+ ],
+ [],
+ ],
+ [
+ [
+ 'MediaWiki' => '1.0.0',
],
- []
+ [],
],
[
[
- 'MediaWiki' => '1.0.0'
+ 'extensions' => [
+ 'NoVersionGiven' => '*',
+ ],
+ ],
+ [],
+ ],
+ [
+ [
+ 'extensions' => [
+ 'NoVersionGiven' => '1.0',
+ ],
+ ],
+ [
+ [
+ 'incompatible' => 'FakeExtension',
+ 'type' => 'incompatible-extensions',
+ 'msg' => 'NoVersionGiven does not expose its version, but FakeExtension requires: 1.0.',
+ ],
+ ],
+ ],
+ [
+ [
+ 'extensions' => [
+ 'Missing' => '*',
+ ],
+ ],
+ [
+ [
+ 'missing' => 'Missing',
+ 'type' => 'missing-extensions',
+ 'msg' => 'FakeExtension requires Missing to be installed.',
+ ],
+ ],
+ ],
+ [
+ [
+ 'extensions' => [
+ 'FakeDependency' => '2.0.0',
+ ],
+ ],
+ [
+ [
+ 'incompatible' => 'FakeExtension',
+ 'type' => 'incompatible-extensions',
+ // phpcs:ignore Generic.Files.LineLength.TooLong
+ 'msg' => 'FakeExtension is not compatible with the current installed version of FakeDependency (1.0.0), it requires: 2.0.0.',
+ ],
+ ],
+ ],
+ [
+ [
+ 'skins' => [
+ 'FakeSkin' => '*',
+ ],
+ ],
+ [
+ [
+ 'missing' => 'FakeSkin',
+ 'type' => 'missing-skins',
+ 'msg' => 'FakeExtension requires FakeSkin to be installed.',
+ ],
],
- []
],
];
}
@@ -84,35 +151,57 @@ class VersionCheckerTest extends PHPUnit_Framework_TestCase {
*/
public function testInvalidConstraint() {
$checker = new VersionChecker( '1.0.0' );
- $checker
- ->setLoadedExtensionsAndSkins( [
+ $checker->setLoadedExtensionsAndSkins( [
'FakeDependency' => [
'version' => 'not really valid',
],
] );
- $this->assertEquals( [ "FakeDependency does not have a valid version string." ],
- $checker->checkArray( [
- 'FakeExtension' => [
- 'extensions' => [
- 'FakeDependency' => '1.24.3',
- ],
+ $this->assertEquals( [
+ [
+ 'type' => 'invalid-version',
+ 'msg' => "FakeDependency does not have a valid version string.",
+ ],
+ ], $checker->checkArray( [
+ 'FakeExtension' => [
+ 'extensions' => [
+ 'FakeDependency' => '1.24.3',
],
- ] )
- );
+ ],
+ ] ) );
$checker = new VersionChecker( '1.0.0' );
- $checker
- ->setLoadedExtensionsAndSkins( [
+ $checker->setLoadedExtensionsAndSkins( [
'FakeDependency' => [
'version' => '1.24.3',
],
] );
- $this->setExpectedException( 'UnexpectedValueException' );
+ $this->setExpectedException( UnexpectedValueException::class );
$checker->checkArray( [
'FakeExtension' => [
'FakeDependency' => 'not really valid',
- ]
+ ],
] );
}
+
+ /**
+ * T197478
+ */
+ public function testInvalidDependency() {
+ $checker = new VersionChecker( '1.0.0' );
+ $this->setExpectedException( UnexpectedValueException::class,
+ 'Dependency type skin unknown in FakeExtension' );
+ $this->assertEquals( [
+ [
+ 'type' => 'invalid-version',
+ 'msg' => 'FakeDependency does not have a valid version string.',
+ ],
+ ], $checker->checkArray( [
+ 'FakeExtension' => [
+ 'skin' => [
+ 'FakeSkin' => '*',
+ ],
+ ],
+ ] ) );
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php b/www/wiki/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php
index 0be04efd..e4f58eb1 100644
--- a/www/wiki/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php
+++ b/www/wiki/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php
@@ -4,7 +4,9 @@
* @group ResourceLoader
* @covers DerivativeResourceLoaderContext
*/
-class DerivativeResourceLoaderContextTest extends PHPUnit_Framework_TestCase {
+class DerivativeResourceLoaderContextTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
protected static function getContext() {
$request = new FauxRequest( [
diff --git a/www/wiki/tests/phpunit/includes/resourceloader/MessageBlobStoreTest.php b/www/wiki/tests/phpunit/includes/resourceloader/MessageBlobStoreTest.php
index cea1a560..7eb09441 100644
--- a/www/wiki/tests/phpunit/includes/resourceloader/MessageBlobStoreTest.php
+++ b/www/wiki/tests/phpunit/includes/resourceloader/MessageBlobStoreTest.php
@@ -6,13 +6,15 @@ use Wikimedia\TestingAccessWrapper;
* @group Cache
* @covers MessageBlobStore
*/
-class MessageBlobStoreTest extends PHPUnit_Framework_TestCase {
+class MessageBlobStoreTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
protected function setUp() {
parent::setUp();
// MediaWiki tests defaults $wgMainWANCache to CACHE_NONE.
// Use hash instead so that caching is observed
- $this->wanCache = $this->getMockBuilder( 'WANObjectCache' )
+ $this->wanCache = $this->getMockBuilder( WANObjectCache::class )
->setConstructorArgs( [ [
'cache' => new HashBagOStuff(),
'pool' => 'test',
@@ -24,13 +26,17 @@ class MessageBlobStoreTest extends PHPUnit_Framework_TestCase {
$this->wanCache->expects( $this->any() )
->method( 'makePurgeValue' )
->will( $this->returnCallback( function ( $timestamp, $holdoff ) {
- // Disable holdoff as it messes with testing
- return WANObjectCache::PURGE_VAL_PREFIX . (float)$timestamp . ':0';
+ // Disable holdoff as it messes with testing. Aside from a 0-second holdoff,
+ // make sure that "time" passes between getMulti() check init and the set()
+ // in recacheMessageBlob(). This especially matters for Windows clocks.
+ $ts = (float)$timestamp - 0.0001;
+
+ return WANObjectCache::PURGE_VAL_PREFIX . $ts . ':0';
} ) );
}
protected function makeBlobStore( $methods = null, $rl = null ) {
- $blobStore = $this->getMockBuilder( 'MessageBlobStore' )
+ $blobStore = $this->getMockBuilder( MessageBlobStore::class )
->setConstructorArgs( [ $rl ] )
->setMethods( $methods )
->getMock();
@@ -200,12 +206,16 @@ class MessageBlobStoreTest extends PHPUnit_Framework_TestCase {
->method( 'fetchMessage' )
->will( $this->onConsecutiveCalls( 'First', 'Second' ) );
+ $now = microtime( true );
+ $this->wanCache->setMockTime( $now );
+
$blob = $blobStore->getBlob( $module, 'en' );
$this->assertEquals( '{"example":"First"}', $blob, 'Generated blob' );
$blob = $blobStore->getBlob( $module, 'en' );
$this->assertEquals( '{"example":"First"}', $blob, 'Cache-hit' );
+ $now += 1;
$blobStore->clear();
$blob = $blobStore->getBlob( $module, 'en' );
diff --git a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderClientHtmlTest.php b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderClientHtmlTest.php
index 3530d3c1..07956f1d 100644
--- a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderClientHtmlTest.php
+++ b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderClientHtmlTest.php
@@ -5,7 +5,9 @@ use Wikimedia\TestingAccessWrapper;
/**
* @group ResourceLoader
*/
-class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
+class ResourceLoaderClientHtmlTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
protected static function expandVariables( $text ) {
return strtr( $text, [
@@ -40,9 +42,8 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
protected static function makeSampleModules() {
$modules = [
'test' => [],
- 'test.top' => [ 'position' => 'top' ],
- 'test.private.top' => [ 'group' => 'private', 'position' => 'top' ],
- 'test.private.bottom' => [ 'group' => 'private', 'position' => 'bottom' ],
+ 'test.private' => [ 'group' => 'private' ],
+ 'test.shouldembed.empty' => [ 'shouldEmbed' => true, 'isKnownEmpty' => true ],
'test.shouldembed' => [ 'shouldEmbed' => true ],
'test.styles.pure' => [ 'type' => ResourceLoaderModule::LOAD_STYLES ],
@@ -72,7 +73,6 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
],
'test.scripts' => [],
- 'test.scripts.top' => [ 'position' => 'top' ],
'test.scripts.user' => [ 'group' => 'user' ],
'test.scripts.user.empty' => [ 'group' => 'user', 'isKnownEmpty' => true ],
'test.scripts.raw' => [ 'isRaw' => true ],
@@ -112,9 +112,8 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
$client = new ResourceLoaderClientHtml( $context );
$client->setModules( [
'test',
- 'test.private.bottom',
- 'test.private.top',
- 'test.top',
+ 'test.private',
+ 'test.shouldembed.empty',
'test.shouldembed',
'test.unregistered',
] );
@@ -128,43 +127,41 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
] );
$client->setModuleScripts( [
'test.scripts',
+ 'test.scripts.user',
'test.scripts.user.empty',
- 'test.scripts.top',
'test.scripts.shouldembed',
'test.unregistered.scripts',
] );
$expected = [
'states' => [
- 'test.private.top' => 'loading',
- 'test.private.bottom' => 'loading',
+ 'test.private' => 'loading',
+ 'test.shouldembed.empty' => 'ready',
'test.shouldembed' => 'loading',
'test.styles.pure' => 'ready',
'test.styles.user.empty' => 'ready',
'test.styles.private' => 'ready',
'test.styles.shouldembed' => 'ready',
'test.scripts' => 'loading',
- 'test.scripts.top' => 'loading',
+ 'test.scripts.user' => 'loading',
'test.scripts.user.empty' => 'ready',
'test.scripts.shouldembed' => 'loading',
],
'general' => [
'test',
- 'test.top',
],
'styles' => [
'test.styles.pure',
],
'scripts' => [
'test.scripts',
- 'test.scripts.top',
+ 'test.scripts.user',
'test.scripts.shouldembed',
],
'embed' => [
'styles' => [ 'test.styles.private', 'test.styles.shouldembed' ],
'general' => [
- 'test.private.bottom',
- 'test.private.top',
+ 'test.private',
'test.shouldembed',
],
],
@@ -188,39 +185,77 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
$client = new ResourceLoaderClientHtml( $context );
$client->setConfig( [ 'key' => 'value' ] );
$client->setModules( [
- 'test.top',
- 'test.private.top',
+ 'test',
+ 'test.private',
] );
$client->setModuleStyles( [
'test.styles.pure',
'test.styles.private',
] );
$client->setModuleScripts( [
- 'test.scripts.top',
+ 'test.scripts',
] );
$client->setExemptStates( [
'test.exempt' => 'ready',
] );
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
$expected = '<script>document.documentElement.className = document.documentElement.className.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );</script>' . "\n"
. '<script>(window.RLQ=window.RLQ||[]).push(function(){'
. 'mw.config.set({"key":"value"});'
- . 'mw.loader.state({"test.exempt":"ready","test.private.top":"loading","test.styles.pure":"ready","test.styles.private":"ready","test.scripts.top":"loading"});'
- . 'mw.loader.implement("test.private.top@{blankVer}",function($,jQuery,require,module){},{"css":[]});'
- . 'mw.loader.load(["test.top"]);'
- . 'mw.loader.load("/w/load.php?debug=false\u0026lang=nl\u0026modules=test.scripts.top\u0026only=scripts\u0026skin=fallback");'
+ . 'mw.loader.state({"test.exempt":"ready","test.private":"loading","test.styles.pure":"ready","test.styles.private":"ready","test.scripts":"loading"});'
+ . 'mw.loader.implement("test.private@{blankVer}",function($,jQuery,require,module){},{"css":[]});'
+ . 'mw.loader.load(["test"]);'
+ . 'mw.loader.load("/w/load.php?debug=false\u0026lang=nl\u0026modules=test.scripts\u0026only=scripts\u0026skin=fallback");'
. '});</script>' . "\n"
. '<link rel="stylesheet" href="/w/load.php?debug=false&amp;lang=nl&amp;modules=test.styles.pure&amp;only=styles&amp;skin=fallback"/>' . "\n"
. '<style>.private{}</style>' . "\n"
. '<script async="" src="/w/load.php?debug=false&amp;lang=nl&amp;modules=startup&amp;only=scripts&amp;skin=fallback"></script>';
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
$expected = self::expandVariables( $expected );
$this->assertEquals( $expected, $client->getHeadHtml() );
}
/**
+ * Confirm that 'target' is passed down to the startup module's load url.
+ *
+ * @covers ResourceLoaderClientHtml::getHeadHtml
+ */
+ public function testGetHeadHtmlWithTarget() {
+ $client = new ResourceLoaderClientHtml(
+ self::makeContext(),
+ [ 'target' => 'example' ]
+ );
+
+ // phpcs:disable Generic.Files.LineLength
+ $expected = '<script>document.documentElement.className = document.documentElement.className.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );</script>' . "\n"
+ . '<script async="" src="/w/load.php?debug=false&amp;lang=nl&amp;modules=startup&amp;only=scripts&amp;skin=fallback&amp;target=example"></script>';
+ // phpcs:enable
+
+ $this->assertEquals( $expected, $client->getHeadHtml() );
+ }
+
+ /**
+ * Confirm that a null 'target' is the same as no target.
+ *
+ * @covers ResourceLoaderClientHtml::getHeadHtml
+ */
+ public function testGetHeadHtmlWithNullTarget() {
+ $client = new ResourceLoaderClientHtml(
+ self::makeContext(),
+ [ 'target' => null ]
+ );
+
+ // phpcs:disable Generic.Files.LineLength
+ $expected = '<script>document.documentElement.className = document.documentElement.className.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );</script>' . "\n"
+ . '<script async="" src="/w/load.php?debug=false&amp;lang=nl&amp;modules=startup&amp;only=scripts&amp;skin=fallback"></script>';
+ // phpcs:enable
+
+ $this->assertEquals( $expected, $client->getHeadHtml() );
+ }
+
+ /**
* @covers ResourceLoaderClientHtml::getBodyHtml
* @covers ResourceLoaderClientHtml::getLoad
*/
@@ -245,8 +280,8 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
}
public static function provideMakeLoad() {
+ // phpcs:disable Generic.Files.LineLength
return [
- // @codingStandardsIgnoreStart Generic.Files.LineLength
[
'context' => [],
'modules' => [ 'test.unknown' ],
@@ -261,9 +296,9 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
],
[
'context' => [],
- 'modules' => [ 'test.private.top' ],
+ 'modules' => [ 'test.private' ],
'only' => ResourceLoaderModule::TYPE_COMBINED,
- 'output' => '<script>(window.RLQ=window.RLQ||[]).push(function(){mw.loader.implement("test.private.top@{blankVer}",function($,jQuery,require,module){},{"css":[]});});</script>',
+ 'output' => '<script>(window.RLQ=window.RLQ||[]).push(function(){mw.loader.implement("test.private@{blankVer}",function($,jQuery,require,module){},{"css":[]});});</script>',
],
[
'context' => [],
@@ -273,6 +308,12 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
'output' => '<script async="" src="/w/load.php?debug=false&amp;lang=nl&amp;modules=test.scripts.raw&amp;only=scripts&amp;skin=fallback"></script>',
],
[
+ 'context' => [ 'sync' => true ],
+ 'modules' => [ 'test.scripts.raw' ],
+ 'only' => ResourceLoaderModule::TYPE_SCRIPTS,
+ 'output' => '<script src="/w/load.php?debug=false&amp;lang=nl&amp;modules=test.scripts.raw&amp;only=scripts&amp;skin=fallback&amp;sync=1"></script>',
+ ],
+ [
'context' => [],
'modules' => [ 'test.scripts.user' ],
'only' => ResourceLoaderModule::TYPE_SCRIPTS,
@@ -338,8 +379,8 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
. '<style>.orderingC{}.orderingD{}</style>' . "\n"
. '<link rel="stylesheet" href="/w/load.php?debug=false&amp;lang=nl&amp;modules=test.ordering.e&amp;only=styles&amp;skin=fallback"/>'
],
- // @codingStandardsIgnoreEnd
];
+ // phpcs:enable
}
/**
@@ -357,7 +398,7 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
public function testMakeLoad( array $extraQuery, array $modules, $type, $expected ) {
$context = self::makeContext( $extraQuery );
$context->getResourceLoader()->register( self::makeSampleModules() );
- $actual = ResourceLoaderClientHtml::makeLoad( $context, $modules, $type );
+ $actual = ResourceLoaderClientHtml::makeLoad( $context, $modules, $type, $extraQuery );
$expected = self::expandVariables( $expected );
$this->assertEquals( $expected, (string)$actual );
}
diff --git a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderContextTest.php b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderContextTest.php
index b658efba..b226ee1c 100644
--- a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderContextTest.php
+++ b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderContextTest.php
@@ -8,7 +8,10 @@
* @group Cache
* @covers ResourceLoaderContext
*/
-class ResourceLoaderContextTest extends PHPUnit_Framework_TestCase {
+class ResourceLoaderContextTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
protected static function getResourceLoader() {
return new EmptyResourceLoader( new HashConfig( [
'ResourceLoaderDebug' => false,
diff --git a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php
index f53cd069..3f5704d6 100644
--- a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php
+++ b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php
@@ -59,7 +59,7 @@ class ResourceLoaderImageModuleTest extends ResourceLoaderTestCase {
return [
[
[
- 'class' => 'ResourceLoaderImageModule',
+ 'class' => ResourceLoaderImageModule::class,
'prefix' => 'oo-ui-icon',
'variants' => self::$commonImageVariants,
'images' => self::$commonImageData,
@@ -100,7 +100,7 @@ class ResourceLoaderImageModuleTest extends ResourceLoaderTestCase {
],
[
[
- 'class' => 'ResourceLoaderImageModule',
+ 'class' => ResourceLoaderImageModule::class,
'selectorWithoutVariant' => '.mw-ui-icon-{name}:after, .mw-ui-icon-{name}:before',
'selectorWithVariant' =>
'.mw-ui-icon-{name}-{variant}:after, .mw-ui-icon-{name}-{variant}:before',
@@ -207,7 +207,6 @@ class ResourceLoaderImageModuleTest extends ResourceLoaderTestCase {
<<<TEXT
background-image: url(rasterized.png);
background-image: linear-gradient(transparent, transparent), url(original.svg);
- background-image: -o-linear-gradient(transparent, transparent), url(rasterized.png);
TEXT
],
[
@@ -215,7 +214,6 @@ TEXT
<<<TEXT
background-image: url(rasterized.png);
background-image: linear-gradient(transparent, transparent), url(data:image/svg+xml);
- background-image: -o-linear-gradient(transparent, transparent), url(rasterized.png);
TEXT
],
@@ -241,7 +239,7 @@ TEXT
}
private function getImageMock( ResourceLoaderContext $context, $dataUriReturnValue ) {
- $image = $this->getMockBuilder( 'ResourceLoaderImage' )
+ $image = $this->getMockBuilder( ResourceLoaderImage::class )
->disableOriginalConstructor()
->getMock();
$image->method( 'getDataUri' )
diff --git a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php
index aea27764..35c3ef64 100644
--- a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php
+++ b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php
@@ -15,8 +15,8 @@ class ResourceLoaderImageTest extends ResourceLoaderTestCase {
protected function getTestImage( $name ) {
$options = ResourceLoaderImageModuleTest::$commonImageData[$name];
$fileDescriptor = is_string( $options ) ? $options : $options['file'];
- $allowedVariants = is_array( $options ) &&
- isset( $options['variants'] ) ? $options['variants'] : [];
+ $allowedVariants = ( is_array( $options ) && isset( $options['variants'] ) ) ?
+ $options['variants'] : [];
$variants = array_fill_keys( $allowedVariants, [ 'color' => 'red' ] );
return new ResourceLoaderImageTestable(
$name,
@@ -39,7 +39,9 @@ class ResourceLoaderImageTest extends ResourceLoaderTestCase {
[ 'mno', 'ar', 'mno-rtl.svg' ],
[ 'mno', 'he', 'mno-ltr.svg' ],
[ 'pqr', 'en', 'pqr-b.svg' ],
+ [ 'pqr', 'en-gb', 'pqr-b.svg' ],
[ 'pqr', 'de', 'pqr-f.svg' ],
+ [ 'pqr', 'de-formal', 'pqr-f.svg' ],
[ 'pqr', 'ar', 'pqr-f.svg' ],
[ 'pqr', 'fr', 'pqr-a.svg' ],
[ 'pqr', 'he', 'pqr-a.svg' ],
@@ -53,7 +55,9 @@ class ResourceLoaderImageTest extends ResourceLoaderTestCase {
public function testGetPath( $imageName, $languageCode, $path ) {
static $dirMap = [
'en' => 'ltr',
+ 'en-gb' => 'ltr',
'de' => 'ltr',
+ 'de-formal' => 'ltr',
'fr' => 'ltr',
'he' => 'rtl',
'ar' => 'rtl',
diff --git a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php
index 7c7f1cf5..c917882a 100644
--- a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php
+++ b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php
@@ -4,6 +4,8 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase {
/**
* @covers ResourceLoaderModule::getVersionHash
+ * @covers ResourceLoaderModule::getModifiedTime
+ * @covers ResourceLoaderModule::getModifiedHash
*/
public function testGetVersionHash() {
$context = $this->getResourceLoaderContext();
@@ -149,9 +151,9 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase {
* @covers ResourceLoaderModule::expandRelativePaths
*/
public function testPlaceholderize() {
- $getRelativePaths = new ReflectionMethod( 'ResourceLoaderModule', 'getRelativePaths' );
+ $getRelativePaths = new ReflectionMethod( ResourceLoaderModule::class, 'getRelativePaths' );
$getRelativePaths->setAccessible( true );
- $expandRelativePaths = new ReflectionMethod( 'ResourceLoaderModule', 'expandRelativePaths' );
+ $expandRelativePaths = new ReflectionMethod( ResourceLoaderModule::class, 'expandRelativePaths' );
$expandRelativePaths->setAccessible( true );
$this->setMwGlobals( [
diff --git a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderOOUIImageModuleTest.php b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderOOUIImageModuleTest.php
index 491fff6b..ea220f11 100644
--- a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderOOUIImageModuleTest.php
+++ b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderOOUIImageModuleTest.php
@@ -10,7 +10,7 @@ class ResourceLoaderOOUIImageModuleTest extends ResourceLoaderTestCase {
*/
public function testNonDefaultSkin() {
$module = new ResourceLoaderOOUIImageModule( [
- 'class' => 'ResourceLoaderOOUIImageModule',
+ 'class' => ResourceLoaderOOUIImageModule::class,
'name' => 'icons',
'rootPath' => 'tests/phpunit/data/resourceloader/oouiimagemodule',
] );
@@ -22,7 +22,7 @@ class ResourceLoaderOOUIImageModuleTest extends ResourceLoaderTestCase {
function () {
}
);
- $r = new ReflectionMethod( 'ExtensionRegistry', 'exportExtractedData' );
+ $r = new ReflectionMethod( ExtensionRegistry::class, 'exportExtractedData' );
$r->setAccessible( true );
$r->invoke( ExtensionRegistry::getInstance(), [
'globals' => [],
diff --git a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderSkinModuleTest.php b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderSkinModuleTest.php
index c5676982..a1b14220 100644
--- a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderSkinModuleTest.php
+++ b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderSkinModuleTest.php
@@ -1,15 +1,18 @@
<?php
/**
- * @group Database
* @group ResourceLoader
*/
-class ResourceLoaderSkinModuleTest extends PHPUnit_Framework_TestCase {
+class ResourceLoaderSkinModuleTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
public static function provideGetStyles() {
+ // phpcs:disable Generic.Files.LineLength
return [
[
'parent' => [],
+ 'logo' => '/logo.png',
'expected' => [
'all' => [ '.mw-wiki-logo { background-image: url(/logo.png); }' ],
],
@@ -18,12 +21,52 @@ class ResourceLoaderSkinModuleTest extends PHPUnit_Framework_TestCase {
'parent' => [
'screen' => '.example {}',
],
+ 'logo' => '/logo.png',
'expected' => [
'screen' => [ '.example {}' ],
'all' => [ '.mw-wiki-logo { background-image: url(/logo.png); }' ],
],
],
+ [
+ 'parent' => [],
+ 'logo' => [
+ '1x' => '/logo.png',
+ '1.5x' => '/logo@1.5x.png',
+ '2x' => '/logo@2x.png',
+ ],
+ 'expected' => [
+ 'all' => [ <<<CSS
+.mw-wiki-logo { background-image: url(/logo.png); }
+CSS
+ ],
+ '(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (min-resolution: 1.5dppx), (min-resolution: 144dpi)' => [ <<<CSS
+.mw-wiki-logo { background-image: url(/logo@1.5x.png);background-size: 135px auto; }
+CSS
+ ],
+ '(-webkit-min-device-pixel-ratio: 2), (min--moz-device-pixel-ratio: 2), (min-resolution: 2dppx), (min-resolution: 192dpi)' => [ <<<CSS
+.mw-wiki-logo { background-image: url(/logo@2x.png);background-size: 135px auto; }
+CSS
+ ],
+ ],
+ ],
+ [
+ 'parent' => [],
+ 'logo' => [
+ '1x' => '/logo.png',
+ 'svg' => '/logo.svg',
+ ],
+ 'expected' => [
+ 'all' => [ <<<CSS
+.mw-wiki-logo { background-image: url(/logo.png); }
+CSS
+ , <<<CSS
+.mw-wiki-logo { background-image: -webkit-linear-gradient(transparent, transparent), url(/logo.svg); background-image: linear-gradient(transparent, transparent), url(/logo.svg);background-size: 135px auto; }
+CSS
+ ],
+ ],
+ ],
];
+ // phpcs:enable
}
/**
@@ -31,25 +74,24 @@ class ResourceLoaderSkinModuleTest extends PHPUnit_Framework_TestCase {
* @covers ResourceLoaderSkinModule::normalizeStyles
* @covers ResourceLoaderSkinModule::getStyles
*/
- public function testGetStyles( $parent, $expected ) {
+ public function testGetStyles( $parent, $logo, $expected ) {
$module = $this->getMockBuilder( ResourceLoaderSkinModule::class )
->disableOriginalConstructor()
- ->setMethods( [ 'readStyleFiles' ] )
+ ->setMethods( [ 'readStyleFiles', 'getConfig', 'getLogoData' ] )
->getMock();
$module->expects( $this->once() )->method( 'readStyleFiles' )
->willReturn( $parent );
- $module->setConfig( new HashConfig( [
- 'ResourceBasePath' => '/w',
- 'Logo' => '/logo.png',
- 'LogoHD' => false,
- ] ) );
+ $module->expects( $this->once() )->method( 'getConfig' )
+ ->willReturn( new HashConfig() );
+ $module->expects( $this->once() )->method( 'getLogoData' )
+ ->willReturn( $logo );
$ctx = $this->getMockBuilder( ResourceLoaderContext::class )
->disableOriginalConstructor()->getMock();
$this->assertEquals(
- $module->getStyles( $ctx ),
- $expected
+ $expected,
+ $module->getStyles( $ctx )
);
}
@@ -64,4 +106,102 @@ class ResourceLoaderSkinModuleTest extends PHPUnit_Framework_TestCase {
$this->assertFalse( $module->isKnownEmpty( $ctx ) );
}
+
+ /**
+ * @dataProvider provideGetLogo
+ * @covers ResourceLoaderSkinModule::getLogo
+ */
+ public function testGetLogo( $config, $expected, $baseDir = null ) {
+ if ( $baseDir ) {
+ $oldIP = $GLOBALS['IP'];
+ $GLOBALS['IP'] = $baseDir;
+ $teardown = new Wikimedia\ScopedCallback( function () use ( $oldIP ) {
+ $GLOBALS['IP'] = $oldIP;
+ } );
+ }
+
+ $this->assertEquals(
+ $expected,
+ ResourceLoaderSkinModule::getLogo( new HashConfig( $config ) )
+ );
+ }
+
+ public function provideGetLogo() {
+ return [
+ 'simple' => [
+ 'config' => [
+ 'ResourceBasePath' => '/w',
+ 'Logo' => '/img/default.png',
+ 'LogoHD' => false,
+ ],
+ 'expected' => '/img/default.png',
+ ],
+ 'default and 2x' => [
+ 'config' => [
+ 'ResourceBasePath' => '/w',
+ 'Logo' => '/img/default.png',
+ 'LogoHD' => [
+ '2x' => '/img/two-x.png',
+ ],
+ ],
+ 'expected' => [
+ '1x' => '/img/default.png',
+ '2x' => '/img/two-x.png',
+ ],
+ ],
+ 'default and all HiDPIs' => [
+ 'config' => [
+ 'ResourceBasePath' => '/w',
+ 'Logo' => '/img/default.png',
+ 'LogoHD' => [
+ '1.5x' => '/img/one-point-five.png',
+ '2x' => '/img/two-x.png',
+ ],
+ ],
+ 'expected' => [
+ '1x' => '/img/default.png',
+ '1.5x' => '/img/one-point-five.png',
+ '2x' => '/img/two-x.png',
+ ],
+ ],
+ 'default and SVG' => [
+ 'config' => [
+ 'ResourceBasePath' => '/w',
+ 'Logo' => '/img/default.png',
+ 'LogoHD' => [
+ 'svg' => '/img/vector.svg',
+ ],
+ ],
+ 'expected' => [
+ '1x' => '/img/default.png',
+ 'svg' => '/img/vector.svg',
+ ],
+ ],
+ 'everything' => [
+ 'config' => [
+ 'ResourceBasePath' => '/w',
+ 'Logo' => '/img/default.png',
+ 'LogoHD' => [
+ '1.5x' => '/img/one-point-five.png',
+ '2x' => '/img/two-x.png',
+ 'svg' => '/img/vector.svg',
+ ],
+ ],
+ 'expected' => [
+ '1x' => '/img/default.png',
+ 'svg' => '/img/vector.svg',
+ ],
+ ],
+ 'versioned url' => [
+ 'config' => [
+ 'ResourceBasePath' => '/w',
+ 'Logo' => '/w/test.jpg',
+ 'LogoHD' => false,
+ 'UploadPath' => '/w/images',
+ ],
+ 'expected' => '/w/test.jpg?edcf2',
+ 'baseDir' => dirname( dirname( __DIR__ ) ) . '/data/media',
+ ],
+ ];
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php
index 03a609b6..564f50bc 100644
--- a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php
+++ b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php
@@ -56,7 +56,7 @@ mw.loader.register( [
'msg' => 'Version falls back gracefully if getVersionHash throws',
'modules' => [
'test.fail' => (
- ( $mock = $this->getMockBuilder( 'ResourceLoaderTestModule' )
+ ( $mock = $this->getMockBuilder( ResourceLoaderTestModule::class )
->setMethods( [ 'getVersionHash' ] )->getMock() )
&& $mock->method( 'getVersionHash' )->will(
$this->throwException( new Exception )
@@ -81,7 +81,7 @@ mw.loader.state( {
'msg' => 'Use version from getVersionHash',
'modules' => [
'test.version' => (
- ( $mock = $this->getMockBuilder( 'ResourceLoaderTestModule' )
+ ( $mock = $this->getMockBuilder( ResourceLoaderTestModule::class )
->setMethods( [ 'getVersionHash' ] )->getMock() )
&& $mock->method( 'getVersionHash' )->willReturn( '1234567' )
) ? $mock : $mock
@@ -101,7 +101,7 @@ mw.loader.register( [
'msg' => 'Re-hash version from getVersionHash if too long',
'modules' => [
'test.version' => (
- ( $mock = $this->getMockBuilder( 'ResourceLoaderTestModule' )
+ ( $mock = $this->getMockBuilder( ResourceLoaderTestModule::class )
->setMethods( [ 'getVersionHash' ] )->getMock() )
&& $mock->method( 'getVersionHash' )->willReturn( '12345678' )
) ? $mock : $mock
diff --git a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php
index e9d022f6..4e9f5399 100644
--- a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php
+++ b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php
@@ -86,7 +86,7 @@ class ResourceLoaderTest extends ResourceLoaderTestCase {
*/
public function testRegisterInvalidName() {
$resourceLoader = new EmptyResourceLoader();
- $this->setExpectedException( 'MWException', "name 'test!invalid' is invalid" );
+ $this->setExpectedException( MWException::class, "name 'test!invalid' is invalid" );
$resourceLoader->register( 'test!invalid', new ResourceLoaderTestModule() );
}
@@ -95,7 +95,7 @@ class ResourceLoaderTest extends ResourceLoaderTestCase {
*/
public function testRegisterInvalidType() {
$resourceLoader = new EmptyResourceLoader();
- $this->setExpectedException( 'MWException', 'ResourceLoader module info type error' );
+ $this->setExpectedException( MWException::class, 'ResourceLoader module info type error' );
$resourceLoader->register( 'test', new stdClass() );
}
@@ -261,7 +261,7 @@ class ResourceLoaderTest extends ResourceLoaderTestCase {
'jquery.foo,bar|jquery.ui.baz,quux',
],
[
- 'Regression fixed in r88706 with dotless names',
+ 'Regression fixed in r87497 (7fee86c38e) with dotless names',
[ 'foo', 'bar', 'baz' ],
'foo,bar,baz',
],
@@ -336,7 +336,9 @@ class ResourceLoaderTest extends ResourceLoaderTestCase {
*/
public function testAddSourceDupe() {
$rl = new ResourceLoader;
- $this->setExpectedException( 'MWException', 'ResourceLoader duplicate source addition error' );
+ $this->setExpectedException(
+ MWException::class, 'ResourceLoader duplicate source addition error'
+ );
$rl->addSource( 'foo', 'https://example.org/w/load.php' );
$rl->addSource( 'foo', 'https://example.com/w/load.php' );
}
@@ -346,7 +348,7 @@ class ResourceLoaderTest extends ResourceLoaderTestCase {
*/
public function testAddSourceInvalid() {
$rl = new ResourceLoader;
- $this->setExpectedException( 'MWException', 'with no "loadScript" key' );
+ $this->setExpectedException( MWException::class, 'with no "loadScript" key' );
$rl->addSource( 'foo', [ 'x' => 'https://example.org/w/load.php' ] );
}
@@ -446,7 +448,7 @@ mw.example();
ResourceLoader::clearCache();
$this->setMwGlobals( 'wgResourceLoaderDebug', true );
- $rl = TestingAccessWrapper::newFromClass( 'ResourceLoader' );
+ $rl = TestingAccessWrapper::newFromClass( ResourceLoader::class );
$this->assertEquals(
$case['expected'],
$rl->makeLoaderImplementScript(
@@ -465,8 +467,8 @@ mw.example();
* @covers ResourceLoader::makeLoaderImplementScript
*/
public function testMakeLoaderImplementScriptInvalid() {
- $this->setExpectedException( 'MWException', 'Invalid scripts error' );
- $rl = TestingAccessWrapper::newFromClass( 'ResourceLoader' );
+ $this->setExpectedException( MWException::class, 'Invalid scripts error' );
+ $rl = TestingAccessWrapper::newFromClass( ResourceLoader::class );
$rl->makeLoaderImplementScript(
'test', // name
123, // scripts
@@ -753,7 +755,7 @@ mw.example();
'foo' => self::getSimpleModuleMock( 'foo();' ),
'ferry' => self::getFailFerryMock(),
'bar' => self::getSimpleModuleMock( 'bar();' ),
- 'startup' => [ 'class' => 'ResourceLoaderStartUpModule' ],
+ 'startup' => [ 'class' => ResourceLoaderStartUpModule::class ],
] );
$context = $this->getResourceLoaderContext(
[
@@ -869,4 +871,41 @@ mw.example();
'Extra headers'
);
}
+
+ /**
+ * @covers ResourceLoader::respond
+ */
+ public function testRespond() {
+ $rl = $this->getMockBuilder( EmptyResourceLoader::class )
+ ->setMethods( [
+ 'tryRespondNotModified',
+ 'sendResponseHeaders',
+ 'measureResponseTime',
+ ] )
+ ->getMock();
+ $context = $this->getResourceLoaderContext( [ 'modules' => '' ], $rl );
+
+ $rl->expects( $this->once() )->method( 'measureResponseTime' );
+ $this->expectOutputRegex( '/no modules were requested/' );
+
+ $rl->respond( $context );
+ }
+
+ /**
+ * @covers ResourceLoader::measureResponseTime
+ */
+ public function testMeasureResponseTime() {
+ $stats = $this->getMockBuilder( NullStatsdDataFactory::class )
+ ->setMethods( [ 'timing' ] )->getMock();
+ $this->setService( 'StatsdDataFactory', $stats );
+
+ $stats->expects( $this->once() )->method( 'timing' )
+ ->with( 'resourceloader.responseTime', $this->anything() );
+
+ $timing = new Timing();
+ $timing->mark( 'requestShutdown' );
+ $rl = TestingAccessWrapper::newFromObject( new EmptyResourceLoader );
+ $rl->measureResponseTime( $timing );
+ DeferredUpdates::doUpdates();
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php
index 78eec6a6..0aa37d23 100644
--- a/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php
+++ b/www/wiki/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php
@@ -12,7 +12,7 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase {
*/
public function testConstructor( $params ) {
$module = new ResourceLoaderWikiModule( $params );
- $this->assertInstanceOf( 'ResourceLoaderWikiModule', $module );
+ $this->assertInstanceOf( ResourceLoaderWikiModule::class, $module );
}
public static function provideConstructor() {
@@ -97,7 +97,7 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase {
* @dataProvider provideIsKnownEmpty
*/
public function testIsKnownEmpty( $titleInfo, $group, $expected ) {
- $module = $this->getMockBuilder( 'ResourceLoaderWikiModule' )
+ $module = $this->getMockBuilder( ResourceLoaderWikiModule::class )
->setMethods( [ 'getTitleInfo', 'getGroup' ] )
->getMock();
$module->expects( $this->any() )
@@ -106,7 +106,7 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase {
$module->expects( $this->any() )
->method( 'getGroup' )
->will( $this->returnValue( $group ) );
- $context = $this->getMockBuilder( 'ResourceLoaderContext' )
+ $context = $this->getMockBuilder( ResourceLoaderContext::class )
->disableOriginalConstructor()
->getMock();
$this->assertEquals( $expected, $module->isKnownEmpty( $context ) );
@@ -157,14 +157,14 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase {
];
$expected = $titleInfo;
- $module = $this->getMockBuilder( 'TestResourceLoaderWikiModule' )
+ $module = $this->getMockBuilder( TestResourceLoaderWikiModule::class )
->setMethods( [ 'getPages' ] )
->getMock();
$module->method( 'getPages' )->willReturn( $pages );
// Can't mock static methods
$module::$returnFetchTitleInfo = $titleInfo;
- $context = $this->getMockBuilder( 'ResourceLoaderContext' )
+ $context = $this->getMockBuilder( ResourceLoaderContext::class )
->disableOriginalConstructor()
->getMock();
@@ -192,7 +192,7 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase {
];
$expected = $titleInfo;
- $module = $this->getMockBuilder( 'TestResourceLoaderWikiModule' )
+ $module = $this->getMockBuilder( TestResourceLoaderWikiModule::class )
->setMethods( [ 'getPages' ] )
->getMock();
$module->method( 'getPages' )->willReturn( $pages );
@@ -231,7 +231,7 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase {
$titleInfo = [];
// Set up objects
- $module = $this->getMockBuilder( 'TestResourceLoaderWikiModule' )
+ $module = $this->getMockBuilder( TestResourceLoaderWikiModule::class )
->setMethods( [ 'getPages' ] ) ->getMock();
$module->method( 'getPages' )->willReturn( $pages );
$module::$returnFetchTitleInfo = $titleInfo;
@@ -299,7 +299,7 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase {
*/
public function testGetContent( $expected, $title ) {
$context = $this->getResourceLoaderContext( [], new EmptyResourceLoader );
- $module = $this->getMockBuilder( 'ResourceLoaderWikiModule' )
+ $module = $this->getMockBuilder( ResourceLoaderWikiModule::class )
->setMethods( [ 'getContentObj' ] ) ->getMock();
$module->expects( $this->any() )
->method( 'getContentObj' )->willReturn( null );
@@ -331,7 +331,7 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase {
public function testGetContentForRedirects() {
// Set up context and module object
$context = $this->getResourceLoaderContext( [], new EmptyResourceLoader );
- $module = $this->getMockBuilder( 'ResourceLoaderWikiModule' )
+ $module = $this->getMockBuilder( ResourceLoaderWikiModule::class )
->setMethods( [ 'getPages', 'getContentObj' ] )
->getMock();
$module->expects( $this->any() )
diff --git a/www/wiki/tests/phpunit/includes/search/SearchEnginePrefixTest.php b/www/wiki/tests/phpunit/includes/search/SearchEnginePrefixTest.php
index 4c5bab3f..3f59295a 100644
--- a/www/wiki/tests/phpunit/includes/search/SearchEnginePrefixTest.php
+++ b/www/wiki/tests/phpunit/includes/search/SearchEnginePrefixTest.php
@@ -63,8 +63,8 @@ class SearchEnginePrefixTest extends MediaWikiLangTestCase {
$this->search = MediaWikiServices::getInstance()->newSearchEngine();
$this->search->setNamespaces( [] );
- $this->originalHandlers = TestingAccessWrapper::newFromClass( 'Hooks' )->handlers;
- TestingAccessWrapper::newFromClass( 'Hooks' )->handlers = [];
+ $this->originalHandlers = TestingAccessWrapper::newFromClass( Hooks::class )->handlers;
+ TestingAccessWrapper::newFromClass( Hooks::class )->handlers = [];
SpecialPageFactory::resetList();
}
@@ -72,7 +72,7 @@ class SearchEnginePrefixTest extends MediaWikiLangTestCase {
public function tearDown() {
parent::tearDown();
- TestingAccessWrapper::newFromClass( 'Hooks' )->handlers = $this->originalHandlers;
+ TestingAccessWrapper::newFromClass( Hooks::class )->handlers = $this->originalHandlers;
SpecialPageFactory::resetList();
}
@@ -337,7 +337,7 @@ class SearchEnginePrefixTest extends MediaWikiLangTestCase {
* @covers PrefixSearch::searchBackend
*/
public function testSearchBackend( array $case ) {
- $search = $stub = $this->getMockBuilder( 'SearchEngine' )
+ $search = $stub = $this->getMockBuilder( SearchEngine::class )
->setMethods( [ 'completionSearchBackend' ] )->getMock();
$return = SearchSuggestionSet::fromStrings( $case['provision'] );
diff --git a/www/wiki/tests/phpunit/includes/search/SearchEngineTest.php b/www/wiki/tests/phpunit/includes/search/SearchEngineTest.php
index 9711eabb..b7bc1530 100644
--- a/www/wiki/tests/phpunit/includes/search/SearchEngineTest.php
+++ b/www/wiki/tests/phpunit/includes/search/SearchEngineTest.php
@@ -32,7 +32,11 @@ class SearchEngineTest extends MediaWikiLangTestCase {
$searchType = SearchEngineFactory::getSearchEngineClass( $this->db );
$this->setMwGlobals( [
- 'wgSearchType' => $searchType
+ 'wgSearchType' => $searchType,
+ 'wgCapitalLinks' => true,
+ 'wgCapitalLinkOverrides' => [
+ NS_CATEGORY => false // for testCompletionSearchMustRespectCapitalLinkOverrides
+ ]
] );
$this->search = new $searchType( $this->db );
@@ -52,7 +56,13 @@ class SearchEngineTest extends MediaWikiLangTestCase {
// Reset the search type back to default - some extensions may have
// overridden it.
- $this->setMwGlobals( [ 'wgSearchType' => null ] );
+ $this->setMwGlobals( [
+ 'wgSearchType' => null,
+ 'wgCapitalLinks' => true,
+ 'wgCapitalLinkOverrides' => [
+ NS_CATEGORY => false // for testCompletionSearchMustRespectCapitalLinkOverrides
+ ]
+ ] );
$this->insertPage( 'Not_Main_Page', 'This is not a main page' );
$this->insertPage(
@@ -74,6 +84,9 @@ class SearchEngineTest extends MediaWikiLangTestCase {
$this->insertPage( 'HalfNumbers', '1234567890' );
$this->insertPage( 'FullNumbers', '1234567890' );
$this->insertPage( 'DomainName', 'example.com' );
+ $this->insertPage( 'DomainName', 'example.com' );
+ $this->insertPage( 'Category:search is not Search', '' );
+ $this->insertPage( 'Category:Search is not search', '' );
}
protected function fetchIds( $results ) {
@@ -213,6 +226,48 @@ class SearchEngineTest extends MediaWikiLangTestCase {
"Title power search" );
}
+ public function provideCompletionSearchMustRespectCapitalLinkOverrides() {
+ return [
+ 'Searching for "smithee" finds Smithee on NS_MAIN' => [
+ 'smithee',
+ 'Smithee',
+ [ NS_MAIN ],
+ ],
+ 'Searching for "search is" will finds "search is not Search" on NS_CATEGORY' => [
+ 'search is',
+ 'Category:search is not Search',
+ [ NS_CATEGORY ],
+ ],
+ 'Searching for "Search is" will finds "search is not Search" on NS_CATEGORY' => [
+ 'Search is',
+ 'Category:Search is not search',
+ [ NS_CATEGORY ],
+ ],
+ ];
+ }
+
+ /**
+ * Test that the search query is not munged using wrong CapitalLinks setup
+ * (in other test that the default search backend can benefit from wgCapitalLinksOverride)
+ * Guard against regressions like T208255
+ * @dataProvider provideCompletionSearchMustRespectCapitalLinkOverrides
+ * @covers SearchEngine::completionSearch
+ * @covers PrefixSearch::defaultSearchBackend
+ * @param string $search
+ * @param string $expectedSuggestion
+ * @param int[] $namespaces
+ */
+ public function testCompletionSearchMustRespectCapitalLinkOverrides(
+ $search,
+ $expectedSuggestion,
+ array $namespaces
+ ) {
+ $this->search->setNamespaces( $namespaces );
+ $results = $this->search->completionSearch( $search );
+ $this->assertEquals( 1, $results->getSize() );
+ $this->assertEquals( $expectedSuggestion, $results->getSuggestions()[0]->getText() );
+ }
+
/**
* @covers SearchEngine::getSearchIndexFields
*/
@@ -220,12 +275,12 @@ class SearchEngineTest extends MediaWikiLangTestCase {
/**
* @var $mockEngine SearchEngine
*/
- $mockEngine = $this->getMockBuilder( 'SearchEngine' )
+ $mockEngine = $this->getMockBuilder( SearchEngine::class )
->setMethods( [ 'makeSearchFieldMapping' ] )->getMock();
$mockFieldBuilder = function ( $name, $type ) {
$mockField =
- $this->getMockBuilder( 'SearchIndexFieldDefinition' )->setConstructorArgs( [
+ $this->getMockBuilder( SearchIndexFieldDefinition::class )->setConstructorArgs( [
$name,
$type
] )->getMock();
@@ -258,7 +313,7 @@ class SearchEngineTest extends MediaWikiLangTestCase {
$fields = $mockEngine->getSearchIndexFields();
$this->assertArrayHasKey( 'language', $fields );
$this->assertArrayHasKey( 'category', $fields );
- $this->assertInstanceOf( 'SearchIndexField', $fields['testField'] );
+ $this->assertInstanceOf( SearchIndexField::class, $fields['testField'] );
$mapping = $fields['testField']->getMapping( $mockEngine );
$this->assertArrayHasKey( 'testData', $mapping );
@@ -287,7 +342,7 @@ class SearchEngineTest extends MediaWikiLangTestCase {
}
public function addAugmentors( &$setAugmentors, &$rowAugmentors ) {
- $setAugmentor = $this->createMock( 'ResultSetAugmentor' );
+ $setAugmentor = $this->createMock( ResultSetAugmentor::class );
$setAugmentor->expects( $this->once() )
->method( 'augmentAll' )
->willReturnCallback( function ( SearchResultSet $resultSet ) {
@@ -301,7 +356,7 @@ class SearchEngineTest extends MediaWikiLangTestCase {
} );
$setAugmentors['testSet'] = $setAugmentor;
- $rowAugmentor = $this->createMock( 'ResultAugmentor' );
+ $rowAugmentor = $this->createMock( ResultAugmentor::class );
$rowAugmentor->expects( $this->exactly( 2 ) )
->method( 'augment' )
->willReturnCallback( function ( SearchResult $result ) {
diff --git a/www/wiki/tests/phpunit/includes/search/SearchSuggestionSetTest.php b/www/wiki/tests/phpunit/includes/search/SearchSuggestionSetTest.php
index 28c69fa4..54533a73 100644
--- a/www/wiki/tests/phpunit/includes/search/SearchSuggestionSetTest.php
+++ b/www/wiki/tests/phpunit/includes/search/SearchSuggestionSetTest.php
@@ -19,7 +19,7 @@
* http://www.gnu.org/copyleft/gpl.html
*/
-class SearchSuggestionSetTest extends \PHPUnit_Framework_TestCase {
+class SearchSuggestionSetTest extends \PHPUnit\Framework\TestCase {
/**
* Test that adding a new suggestion at the end
* will keep proper score ordering
diff --git a/www/wiki/tests/phpunit/includes/Services/ServiceContainerTest.php b/www/wiki/tests/phpunit/includes/services/ServiceContainerTest.php
index b68ee48b..a760908f 100644
--- a/www/wiki/tests/phpunit/includes/Services/ServiceContainerTest.php
+++ b/www/wiki/tests/phpunit/includes/services/ServiceContainerTest.php
@@ -6,7 +6,10 @@ use MediaWiki\Services\ServiceContainer;
*
* @group MediaWiki
*/
-class ServiceContainerTest extends PHPUnit_Framework_TestCase {
+class ServiceContainerTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+ use PHPUnit4And6Compat;
private function newServiceContainer( $extraArgs = [] ) {
return new ServiceContainer( $extraArgs );
@@ -69,7 +72,7 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
$name = 'TestService92834576';
- $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
+ $this->setExpectedException( MediaWiki\Services\NoSuchServiceException::class );
$services->getService( $name );
}
@@ -111,7 +114,7 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
$name = 'TestService92834576';
- $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
+ $this->setExpectedException( MediaWiki\Services\NoSuchServiceException::class );
$services->peekService( $name );
}
@@ -141,7 +144,7 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
return $theService;
} );
- $this->setExpectedException( 'MediaWiki\Services\ServiceAlreadyDefinedException' );
+ $this->setExpectedException( MediaWiki\Services\ServiceAlreadyDefinedException::class );
$services->defineService( $name, function () use ( $theService ) {
return $theService;
@@ -238,7 +241,7 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
];
// loading the same file twice should fail, because
- $this->setExpectedException( 'MediaWiki\Services\ServiceAlreadyDefinedException' );
+ $this->setExpectedException( MediaWiki\Services\ServiceAlreadyDefinedException::class );
$services->loadWiringFiles( $wiringFiles );
}
@@ -296,7 +299,7 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
$theService = new stdClass();
$name = 'TestService92834576';
- $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
+ $this->setExpectedException( MediaWiki\Services\NoSuchServiceException::class );
$services->redefineService( $name, function () use ( $theService ) {
return $theService;
@@ -316,7 +319,7 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
// create the service, so it can no longer be redefined
$services->getService( $name );
- $this->setExpectedException( 'MediaWiki\Services\CannotReplaceActiveServiceException' );
+ $this->setExpectedException( MediaWiki\Services\CannotReplaceActiveServiceException::class );
$services->redefineService( $name, function () use ( $theService ) {
return $theService;
@@ -326,7 +329,7 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
public function testDisableService() {
$services = $this->newServiceContainer( [ 'Foo' ] );
- $destructible = $this->getMockBuilder( 'MediaWiki\Services\DestructibleService' )
+ $destructible = $this->getMockBuilder( MediaWiki\Services\DestructibleService::class )
->getMock();
$destructible->expects( $this->once() )
->method( 'destroy' );
@@ -365,7 +368,7 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
$this->assertContains( 'Bar', $services->getServiceNames() );
$this->assertContains( 'Qux', $services->getServiceNames() );
- $this->setExpectedException( 'MediaWiki\Services\ServiceDisabledException' );
+ $this->setExpectedException( MediaWiki\Services\ServiceDisabledException::class );
$services->getService( 'Qux' );
}
@@ -375,7 +378,7 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
$theService = new stdClass();
$name = 'TestService92834576';
- $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
+ $this->setExpectedException( MediaWiki\Services\NoSuchServiceException::class );
$services->redefineService( $name, function () use ( $theService ) {
return $theService;
@@ -385,7 +388,7 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
public function testDestroy() {
$services = $this->newServiceContainer();
- $destructible = $this->getMockBuilder( 'MediaWiki\Services\DestructibleService' )
+ $destructible = $this->getMockBuilder( MediaWiki\Services\DestructibleService::class )
->getMock();
$destructible->expects( $this->once() )
->method( 'destroy' );
@@ -404,7 +407,7 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
// destroy the container
$services->destroy();
- $this->setExpectedException( 'MediaWiki\Services\ContainerDisabledException' );
+ $this->setExpectedException( MediaWiki\Services\ContainerDisabledException::class );
$services->getService( 'Bar' );
}
diff --git a/www/wiki/tests/phpunit/includes/Services/TestWiring1.php b/www/wiki/tests/phpunit/includes/services/TestWiring1.php
index b6ff4eb3..b6ff4eb3 100644
--- a/www/wiki/tests/phpunit/includes/Services/TestWiring1.php
+++ b/www/wiki/tests/phpunit/includes/services/TestWiring1.php
diff --git a/www/wiki/tests/phpunit/includes/Services/TestWiring2.php b/www/wiki/tests/phpunit/includes/services/TestWiring2.php
index dfff64f0..dfff64f0 100644
--- a/www/wiki/tests/phpunit/includes/Services/TestWiring2.php
+++ b/www/wiki/tests/phpunit/includes/services/TestWiring2.php
diff --git a/www/wiki/tests/phpunit/includes/session/BotPasswordSessionProviderTest.php b/www/wiki/tests/phpunit/includes/session/BotPasswordSessionProviderTest.php
index 90550d2b..47679940 100644
--- a/www/wiki/tests/phpunit/includes/session/BotPasswordSessionProviderTest.php
+++ b/www/wiki/tests/phpunit/includes/session/BotPasswordSessionProviderTest.php
@@ -184,7 +184,7 @@ class BotPasswordSessionProviderTest extends MediaWikiTestCase {
public function testNewSessionInfoForRequest() {
$provider = $this->getProvider();
$user = static::getTestSysop()->getUser();
- $request = $this->getMockBuilder( 'FauxRequest' )
+ $request = $this->getMockBuilder( \FauxRequest::class )
->setMethods( [ 'getIP' ] )->getMock();
$request->expects( $this->any() )->method( 'getIP' )
->will( $this->returnValue( '127.0.0.1' ) );
@@ -212,7 +212,7 @@ class BotPasswordSessionProviderTest extends MediaWikiTestCase {
$provider->setLogger( $logger );
$user = static::getTestSysop()->getUser();
- $request = $this->getMockBuilder( 'FauxRequest' )
+ $request = $this->getMockBuilder( \FauxRequest::class )
->setMethods( [ 'getIP' ] )->getMock();
$request->expects( $this->any() )->method( 'getIP' )
->will( $this->returnValue( '127.0.0.1' ) );
@@ -264,7 +264,7 @@ class BotPasswordSessionProviderTest extends MediaWikiTestCase {
], $logger->getBuffer() );
$logger->clearBuffer();
- $request2 = $this->getMockBuilder( 'FauxRequest' )
+ $request2 = $this->getMockBuilder( \FauxRequest::class )
->setMethods( [ 'getIP' ] )->getMock();
$request2->expects( $this->any() )->method( 'getIP' )
->will( $this->returnValue( '10.0.0.1' ) );
diff --git a/www/wiki/tests/phpunit/includes/session/CookieSessionProviderTest.php b/www/wiki/tests/phpunit/includes/session/CookieSessionProviderTest.php
index a47fd9a1..c1df365a 100644
--- a/www/wiki/tests/phpunit/includes/session/CookieSessionProviderTest.php
+++ b/www/wiki/tests/phpunit/includes/session/CookieSessionProviderTest.php
@@ -157,7 +157,7 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
);
$msg = $provider->whyNoSession();
- $this->assertInstanceOf( 'Message', $msg );
+ $this->assertInstanceOf( \Message::class, $msg );
$this->assertSame( 'sessionprovider-nocookies', $msg->getKey() );
}
@@ -415,7 +415,7 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
);
TestingAccessWrapper::newFromObject( $backend )->usePhpSessionHandling = false;
- $mock = $this->getMockBuilder( 'stdClass' )
+ $mock = $this->getMockBuilder( stdClass::class )
->setMethods( [ 'onUserSetCookies' ] )
->getMock();
$mock->expects( $this->never() )->method( 'onUserSetCookies' );
@@ -563,14 +563,14 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
}
protected function getSentRequest() {
- $sentResponse = $this->getMockBuilder( 'FauxResponse' )
+ $sentResponse = $this->getMockBuilder( \FauxResponse::class )
->setMethods( [ 'headersSent', 'setCookie', 'header' ] )->getMock();
$sentResponse->expects( $this->any() )->method( 'headersSent' )
->will( $this->returnValue( true ) );
$sentResponse->expects( $this->never() )->method( 'setCookie' );
$sentResponse->expects( $this->never() )->method( 'header' );
- $sentRequest = $this->getMockBuilder( 'FauxRequest' )
+ $sentRequest = $this->getMockBuilder( \FauxRequest::class )
->setMethods( [ 'response' ] )->getMock();
$sentRequest->expects( $this->any() )->method( 'response' )
->will( $this->returnValue( $sentResponse ) );
@@ -608,7 +608,7 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
TestingAccessWrapper::newFromObject( $backend )->usePhpSessionHandling = false;
// Anonymous user
- $mock = $this->getMockBuilder( 'stdClass' )
+ $mock = $this->getMockBuilder( stdClass::class )
->setMethods( [ 'onUserSetCookies' ] )->getMock();
$mock->expects( $this->never() )->method( 'onUserSetCookies' );
$this->mergeMwGlobalArrayValue( 'wgHooks', [ 'UserSetCookies' => [ $mock ] ] );
diff --git a/www/wiki/tests/phpunit/includes/session/ImmutableSessionProviderWithCookieTest.php b/www/wiki/tests/phpunit/includes/session/ImmutableSessionProviderWithCookieTest.php
index 086fa28b..6dd32fcd 100644
--- a/www/wiki/tests/phpunit/includes/session/ImmutableSessionProviderWithCookieTest.php
+++ b/www/wiki/tests/phpunit/includes/session/ImmutableSessionProviderWithCookieTest.php
@@ -91,7 +91,7 @@ class ImmutableSessionProviderWithCookieTest extends MediaWikiTestCase {
$this->assertFalse( $provider->canChangeUser() );
$msg = $provider->whyNoSession();
- $this->assertInstanceOf( 'Message', $msg );
+ $this->assertInstanceOf( \Message::class, $msg );
$this->assertSame( 'sessionprovider-nocookies', $msg->getKey() );
}
@@ -158,7 +158,7 @@ class ImmutableSessionProviderWithCookieTest extends MediaWikiTestCase {
}
protected function getSentRequest() {
- $sentResponse = $this->getMockBuilder( 'FauxResponse' )
+ $sentResponse = $this->getMockBuilder( \FauxResponse::class )
->setMethods( [ 'headersSent', 'setCookie', 'header' ] )
->getMock();
$sentResponse->expects( $this->any() )->method( 'headersSent' )
@@ -166,7 +166,7 @@ class ImmutableSessionProviderWithCookieTest extends MediaWikiTestCase {
$sentResponse->expects( $this->never() )->method( 'setCookie' );
$sentResponse->expects( $this->never() )->method( 'header' );
- $sentRequest = $this->getMockBuilder( 'FauxRequest' )
+ $sentRequest = $this->getMockBuilder( \FauxRequest::class )
->setMethods( [ 'response' ] )->getMock();
$sentRequest->expects( $this->any() )->method( 'response' )
->will( $this->returnValue( $sentResponse ) );
diff --git a/www/wiki/tests/phpunit/includes/session/MetadataMergeExceptionTest.php b/www/wiki/tests/phpunit/includes/session/MetadataMergeExceptionTest.php
index 0981f026..8cb4302a 100644
--- a/www/wiki/tests/phpunit/includes/session/MetadataMergeExceptionTest.php
+++ b/www/wiki/tests/phpunit/includes/session/MetadataMergeExceptionTest.php
@@ -14,7 +14,7 @@ class MetadataMergeExceptionTest extends MediaWikiTestCase {
$data = [ 'foo' => 'bar' ];
$ex = new MetadataMergeException();
- $this->assertInstanceOf( 'UnexpectedValueException', $ex );
+ $this->assertInstanceOf( \UnexpectedValueException::class, $ex );
$this->assertSame( [], $ex->getContext() );
$ex2 = new MetadataMergeException( 'Message', 42, $ex, $data );
diff --git a/www/wiki/tests/phpunit/includes/session/PHPSessionHandlerTest.php b/www/wiki/tests/phpunit/includes/session/PHPSessionHandlerTest.php
index 0a2e84e1..045ba2f0 100644
--- a/www/wiki/tests/phpunit/includes/session/PHPSessionHandlerTest.php
+++ b/www/wiki/tests/phpunit/includes/session/PHPSessionHandlerTest.php
@@ -15,15 +15,6 @@ class PHPSessionHandlerTest extends MediaWikiTestCase {
private function getResetter( &$rProp = null ) {
$reset = [];
- // Ignore "headers already sent" warnings during this test
- set_error_handler( function ( $errno, $errstr ) use ( &$warnings ) {
- if ( preg_match( '/headers already sent/', $errstr ) ) {
- return true;
- }
- return false;
- } );
- $reset[] = new \Wikimedia\ScopedCallback( 'restore_error_handler' );
-
$rProp = new \ReflectionProperty( PHPSessionHandler::class, 'instance' );
$rProp->setAccessible( true );
if ( $rProp->getValue() ) {
@@ -109,7 +100,7 @@ class PHPSessionHandlerTest extends MediaWikiTestCase {
$reset[] = $this->getResetter( $rProp );
$this->setMwGlobals( [
- 'wgSessionProviders' => [ [ 'class' => 'DummySessionProvider' ] ],
+ 'wgSessionProviders' => [ [ 'class' => \DummySessionProvider::class ] ],
'wgObjectCacheSessionExpiry' => 2,
] );
@@ -130,9 +121,9 @@ class PHPSessionHandlerTest extends MediaWikiTestCase {
);
$wrap->setEnableFlags( 'warn' );
- \MediaWiki\suppressWarnings();
+ \Wikimedia\suppressWarnings();
ini_set( 'session.serialize_handler', $handler );
- \MediaWiki\restoreWarnings();
+ \Wikimedia\restoreWarnings();
if ( ini_get( 'session.serialize_handler' ) !== $handler ) {
$this->markTestSkipped( "Cannot set session.serialize_handler to \"$handler\"" );
}
diff --git a/www/wiki/tests/phpunit/includes/session/SessionBackendTest.php b/www/wiki/tests/phpunit/includes/session/SessionBackendTest.php
index e0d1c307..48c3d179 100644
--- a/www/wiki/tests/phpunit/includes/session/SessionBackendTest.php
+++ b/www/wiki/tests/phpunit/includes/session/SessionBackendTest.php
@@ -2,6 +2,7 @@
namespace MediaWiki\Session;
+use Config;
use MediaWikiTestCase;
use User;
use Wikimedia\TestingAccessWrapper;
@@ -14,9 +15,16 @@ use Wikimedia\TestingAccessWrapper;
class SessionBackendTest extends MediaWikiTestCase {
const SESSIONID = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
+ /** @var SessionManager */
protected $manager;
+
+ /** @var Config */
protected $config;
+
+ /** @var SessionProvider */
protected $provider;
+
+ /** @var TestBagOStuff */
protected $store;
protected $onSessionMetadataCalled = false;
@@ -25,6 +33,7 @@ class SessionBackendTest extends MediaWikiTestCase {
* Returns a non-persistent backend that thinks it has at least one session active
* @param User|null $user
* @param string $id
+ * @return SessionBackend
*/
protected function getBackend( User $user = null, $id = null ) {
if ( !$this->config ) {
@@ -142,14 +151,14 @@ class SessionBackendTest extends MediaWikiTestCase {
$this->assertSame( self::SESSIONID, $backend->getId() );
$this->assertSame( $id, $backend->getSessionId() );
$this->assertSame( $this->provider, $backend->getProvider() );
- $this->assertInstanceOf( 'User', $backend->getUser() );
+ $this->assertInstanceOf( User::class, $backend->getUser() );
$this->assertSame( 'UTSysop', $backend->getUser()->getName() );
$this->assertSame( $info->wasPersisted(), $backend->isPersistent() );
$this->assertSame( $info->wasRemembered(), $backend->shouldRememberUser() );
$this->assertSame( $info->forceHTTPS(), $backend->shouldForceHTTPS() );
$expire = time() + 100;
- $this->store->setSessionMeta( self::SESSIONID, [ 'expires' => $expire ], 2 );
+ $this->store->setSessionMeta( self::SESSIONID, [ 'expires' => $expire ] );
$info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
'provider' => $this->provider,
@@ -164,7 +173,7 @@ class SessionBackendTest extends MediaWikiTestCase {
$this->assertSame( self::SESSIONID, $backend->getId() );
$this->assertSame( $id, $backend->getSessionId() );
$this->assertSame( $this->provider, $backend->getProvider() );
- $this->assertInstanceOf( 'User', $backend->getUser() );
+ $this->assertInstanceOf( User::class, $backend->getUser() );
$this->assertTrue( $backend->getUser()->isAnon() );
$this->assertSame( $info->wasPersisted(), $backend->isPersistent() );
$this->assertSame( $info->wasRemembered(), $backend->shouldRememberUser() );
@@ -258,7 +267,7 @@ class SessionBackendTest extends MediaWikiTestCase {
public function testResetId() {
$id = session_id();
- $builder = $this->getMockBuilder( 'DummySessionProvider' )
+ $builder = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods( [ 'persistsSessionId', 'sessionIdWasReset' ] );
$this->provider = $builder->getMock();
@@ -294,7 +303,7 @@ class SessionBackendTest extends MediaWikiTestCase {
}
public function testPersist() {
- $this->provider = $this->getMockBuilder( 'DummySessionProvider' )
+ $this->provider = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods( [ 'persistSession' ] )->getMock();
$this->provider->expects( $this->once() )->method( 'persistSession' );
$backend = $this->getBackend();
@@ -314,7 +323,7 @@ class SessionBackendTest extends MediaWikiTestCase {
}
public function testUnpersist() {
- $this->provider = $this->getMockBuilder( 'DummySessionProvider' )
+ $this->provider = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods( [ 'unpersistSession' ] )->getMock();
$this->provider->expects( $this->once() )->method( 'unpersistSession' );
$backend = $this->getBackend();
@@ -367,7 +376,7 @@ class SessionBackendTest extends MediaWikiTestCase {
public function testSetUser() {
$user = static::getTestSysop()->getUser();
- $this->provider = $this->getMockBuilder( 'DummySessionProvider' )
+ $this->provider = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods( [ 'canChangeUser' ] )->getMock();
$this->provider->expects( $this->any() )->method( 'canChangeUser' )
->will( $this->returnValue( false ) );
@@ -498,7 +507,7 @@ class SessionBackendTest extends MediaWikiTestCase {
->setMethods( [ 'onSessionMetadata' ] )->getMock();
$neverHook->expects( $this->never() )->method( 'onSessionMetadata' );
- $builder = $this->getMockBuilder( 'DummySessionProvider' )
+ $builder = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods( [ 'persistSession', 'unpersistSession' ] );
$neverProvider = $builder->getMock();
@@ -746,7 +755,7 @@ class SessionBackendTest extends MediaWikiTestCase {
$testData = [ 'foo' => 'foo!', 'bar', [ 'baz', null ] ];
// Not persistent
- $this->provider = $this->getMockBuilder( 'DummySessionProvider' )
+ $this->provider = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods( [ 'persistSession' ] )->getMock();
$this->provider->expects( $this->never() )->method( 'persistSession' );
$this->onSessionMetadataCalled = false;
@@ -772,7 +781,7 @@ class SessionBackendTest extends MediaWikiTestCase {
$this->assertNotEquals( 0, $wrap->expires );
// Persistent
- $this->provider = $this->getMockBuilder( 'DummySessionProvider' )
+ $this->provider = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods( [ 'persistSession' ] )->getMock();
$this->provider->expects( $this->atLeastOnce() )->method( 'persistSession' );
$this->onSessionMetadataCalled = false;
@@ -799,7 +808,7 @@ class SessionBackendTest extends MediaWikiTestCase {
$this->assertNotEquals( 0, $wrap->expires );
// Not persistent, not expiring
- $this->provider = $this->getMockBuilder( 'DummySessionProvider' )
+ $this->provider = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods( [ 'persistSession' ] )->getMock();
$this->provider->expects( $this->never() )->method( 'persistSession' );
$this->onSessionMetadataCalled = false;
@@ -891,7 +900,7 @@ class SessionBackendTest extends MediaWikiTestCase {
$manager->globalSessionRequest = $request;
session_id( self::SESSIONID );
- \MediaWiki\quietCall( 'session_start' );
+ \Wikimedia\quietCall( 'session_start' );
$_SESSION['foo'] = __METHOD__;
$backend->resetId();
$this->assertNotEquals( self::SESSIONID, $backend->getId() );
@@ -929,9 +938,10 @@ class SessionBackendTest extends MediaWikiTestCase {
$manager->globalSessionRequest = $request;
session_id( self::SESSIONID . 'x' );
- \MediaWiki\quietCall( 'session_start' );
+ \Wikimedia\quietCall( 'session_start' );
$backend->unpersist();
$this->assertSame( self::SESSIONID . 'x', session_id() );
+ session_write_close();
session_id( self::SESSIONID );
$wrap->persist = true;
@@ -940,7 +950,7 @@ class SessionBackendTest extends MediaWikiTestCase {
}
public function testGetAllowedUserRights() {
- $this->provider = $this->getMockBuilder( 'DummySessionProvider' )
+ $this->provider = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods( [ 'getAllowedUserRights' ] )
->getMock();
$this->provider->expects( $this->any() )->method( 'getAllowedUserRights' )
diff --git a/www/wiki/tests/phpunit/includes/session/SessionManagerTest.php b/www/wiki/tests/phpunit/includes/session/SessionManagerTest.php
index 9eb46bc3..b33cd24a 100644
--- a/www/wiki/tests/phpunit/includes/session/SessionManagerTest.php
+++ b/www/wiki/tests/phpunit/includes/session/SessionManagerTest.php
@@ -14,7 +14,14 @@ use Wikimedia\TestingAccessWrapper;
*/
class SessionManagerTest extends MediaWikiTestCase {
- protected $config, $logger, $store;
+ /** @var \HashConfig */
+ private $config;
+
+ /** @var \TestLogger */
+ private $logger;
+
+ /** @var TestBagOStuff */
+ private $store;
protected function getManager() {
\ObjectCache::$instances['testSessionStore'] = new TestBagOStuff();
@@ -23,7 +30,7 @@ class SessionManagerTest extends MediaWikiTestCase {
'SessionCacheType' => 'testSessionStore',
'ObjectCacheSessionExpiry' => 100,
'SessionProviders' => [
- [ 'class' => 'DummySessionProvider' ],
+ [ 'class' => \DummySessionProvider::class ],
]
] );
$this->logger = new \TestLogger( false, function ( $m ) {
@@ -75,6 +82,7 @@ class SessionManagerTest extends MediaWikiTestCase {
$context->setRequest( $request );
$id = $request->getSession()->getId();
+ session_write_close();
session_id( '' );
$session = SessionManager::getGlobalSession();
$this->assertSame( $id, $session->getId() );
@@ -138,7 +146,7 @@ class SessionManagerTest extends MediaWikiTestCase {
$id2 = '';
$idEmpty = 'empty-session-------------------';
- $providerBuilder = $this->getMockBuilder( 'DummySessionProvider' )
+ $providerBuilder = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods(
[ 'provideSessionInfo', 'newSessionInfo', '__toString', 'describe', 'unpersistSession' ]
);
@@ -397,7 +405,7 @@ class SessionManagerTest extends MediaWikiTestCase {
// Failure to create an empty session
$manager = $this->getManager();
- $provider = $this->getMockBuilder( 'DummySessionProvider' )
+ $provider = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods( [ 'provideSessionInfo', 'newSessionInfo', '__toString' ] )
->getMock();
$provider->expects( $this->any() )->method( 'provideSessionInfo' )
@@ -422,7 +430,7 @@ class SessionManagerTest extends MediaWikiTestCase {
$pmanager = TestingAccessWrapper::newFromObject( $manager );
$request = new \FauxRequest();
- $providerBuilder = $this->getMockBuilder( 'DummySessionProvider' )
+ $providerBuilder = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods( [ 'provideSessionInfo', 'newSessionInfo', '__toString' ] );
$expectId = null;
@@ -646,7 +654,7 @@ class SessionManagerTest extends MediaWikiTestCase {
$user = User::newFromName( 'UTSysop' );
$manager = $this->getManager();
- $providerBuilder = $this->getMockBuilder( 'DummySessionProvider' )
+ $providerBuilder = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods( [ 'invalidateSessionsForUser', '__toString' ] );
$provider1 = $providerBuilder->getMock();
@@ -674,7 +682,7 @@ class SessionManagerTest extends MediaWikiTestCase {
public function testGetVaryHeaders() {
$manager = $this->getManager();
- $providerBuilder = $this->getMockBuilder( 'DummySessionProvider' )
+ $providerBuilder = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods( [ 'getVaryHeaders', '__toString' ] );
$provider1 = $providerBuilder->getMock();
@@ -718,7 +726,7 @@ class SessionManagerTest extends MediaWikiTestCase {
public function testGetVaryCookies() {
$manager = $this->getManager();
- $providerBuilder = $this->getMockBuilder( 'DummySessionProvider' )
+ $providerBuilder = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods( [ 'getVaryCookies', '__toString' ] );
$provider1 = $providerBuilder->getMock();
@@ -751,7 +759,7 @@ class SessionManagerTest extends MediaWikiTestCase {
$manager = TestingAccessWrapper::newFromObject( $realManager );
$this->config->set( 'SessionProviders', [
- [ 'class' => 'DummySessionProvider' ],
+ [ 'class' => \DummySessionProvider::class ],
] );
$providers = $manager->getProviders();
$this->assertArrayHasKey( 'DummySessionProvider', $providers );
@@ -761,8 +769,8 @@ class SessionManagerTest extends MediaWikiTestCase {
$this->assertSame( $realManager, $provider->getManager() );
$this->config->set( 'SessionProviders', [
- [ 'class' => 'DummySessionProvider' ],
- [ 'class' => 'DummySessionProvider' ],
+ [ 'class' => \DummySessionProvider::class ],
+ [ 'class' => \DummySessionProvider::class ],
] );
$manager->sessionProviders = null;
try {
@@ -780,7 +788,7 @@ class SessionManagerTest extends MediaWikiTestCase {
$manager = TestingAccessWrapper::newFromObject( $this->getManager() );
$manager->setLogger( new \Psr\Log\NullLogger() );
- $mock = $this->getMockBuilder( 'stdClass' )
+ $mock = $this->getMockBuilder( stdClass::class )
->setMethods( [ 'shutdown' ] )->getMock();
$mock->expects( $this->once() )->method( 'shutdown' );
@@ -871,7 +879,7 @@ class SessionManagerTest extends MediaWikiTestCase {
public function testPreventSessionsForUser() {
$manager = $this->getManager();
- $providerBuilder = $this->getMockBuilder( 'DummySessionProvider' )
+ $providerBuilder = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods( [ 'preventSessionsForUser', '__toString' ] );
$provider1 = $providerBuilder->getMock();
diff --git a/www/wiki/tests/phpunit/includes/session/SessionTest.php b/www/wiki/tests/phpunit/includes/session/SessionTest.php
index adf0f5db..f84d435f 100644
--- a/www/wiki/tests/phpunit/includes/session/SessionTest.php
+++ b/www/wiki/tests/phpunit/includes/session/SessionTest.php
@@ -365,9 +365,9 @@ class SessionTest extends MediaWikiTestCase {
$hmac = hash_hmac( 'sha256', $sealed, $hmacKey, true );
$encrypted = base64_encode( $hmac ) . '.' . $sealed;
$session->set( 'test', $encrypted );
- \MediaWiki\suppressWarnings();
+ \Wikimedia\suppressWarnings();
$this->assertEquals( 'defaulted', $session->getSecret( 'test', 'defaulted' ) );
- \MediaWiki\restoreWarnings();
+ \Wikimedia\restoreWarnings();
}
}
diff --git a/www/wiki/tests/phpunit/includes/session/TestBagOStuff.php b/www/wiki/tests/phpunit/includes/session/TestBagOStuff.php
index fd02a2e9..f9e30f06 100644
--- a/www/wiki/tests/phpunit/includes/session/TestBagOStuff.php
+++ b/www/wiki/tests/phpunit/includes/session/TestBagOStuff.php
@@ -14,53 +14,44 @@ class TestBagOStuff extends \CachedBagOStuff {
/**
* @param string $id Session ID
* @param array $data Session data
- * @param int $expiry Expiry
- * @param User $user User for metadata
*/
- public function setSessionData( $id, array $data, $expiry = 0, User $user = null ) {
- $this->setSession( $id, [ 'data' => $data ], $expiry, $user );
+ public function setSessionData( $id, array $data ) {
+ $this->setSession( $id, [ 'data' => $data ] );
}
/**
* @param string $id Session ID
* @param array $metadata Session metadata
- * @param int $expiry Expiry
*/
- public function setSessionMeta( $id, array $metadata, $expiry = 0 ) {
- $this->setSession( $id, [ 'metadata' => $metadata ], $expiry );
+ public function setSessionMeta( $id, array $metadata ) {
+ $this->setSession( $id, [ 'metadata' => $metadata ] );
}
/**
* @param string $id Session ID
* @param array $blob Session metadata and data
- * @param int $expiry Expiry
- * @param User $user User for metadata
*/
- public function setSession( $id, array $blob, $expiry = 0, User $user = null ) {
+ public function setSession( $id, array $blob ) {
$blob += [
'data' => [],
'metadata' => [],
];
$blob['metadata'] += [
- 'userId' => $user ? $user->getId() : 0,
- 'userName' => $user ? $user->getName() : null,
- 'userToken' => $user ? $user->getToken( true ) : null,
+ 'userId' => 0,
+ 'userName' => null,
+ 'userToken' => null,
'provider' => 'DummySessionProvider',
];
- $this->setRawSession( $id, $blob, $expiry, $user );
+ $this->setRawSession( $id, $blob );
}
/**
* @param string $id Session ID
* @param array|mixed $blob Session metadata and data
- * @param int $expiry Expiry
*/
- public function setRawSession( $id, $blob, $expiry = 0 ) {
- if ( $expiry <= 0 ) {
- $expiry = \RequestContext::getMain()->getConfig()->get( 'ObjectCacheSessionExpiry' );
- }
-
+ public function setRawSession( $id, $blob ) {
+ $expiry = \RequestContext::getMain()->getConfig()->get( 'ObjectCacheSessionExpiry' );
$this->set( $this->makeKey( 'MWSession', $id ), $blob, $expiry );
}
diff --git a/www/wiki/tests/phpunit/includes/session/TestUtils.php b/www/wiki/tests/phpunit/includes/session/TestUtils.php
index af29d6bd..5db1ad0e 100644
--- a/www/wiki/tests/phpunit/includes/session/TestUtils.php
+++ b/www/wiki/tests/phpunit/includes/session/TestUtils.php
@@ -79,7 +79,7 @@ class TestUtils {
* If you need a Session for testing but don't want to create a backend to
* construct one, use this.
* @param object $backend Object to serve as the SessionBackend
- * @param int $index Index
+ * @param int $index
* @param LoggerInterface $logger
* @return Session
*/
diff --git a/www/wiki/tests/phpunit/includes/session/UserInfoTest.php b/www/wiki/tests/phpunit/includes/session/UserInfoTest.php
index c38edd69..4d79a956 100644
--- a/www/wiki/tests/phpunit/includes/session/UserInfoTest.php
+++ b/www/wiki/tests/phpunit/includes/session/UserInfoTest.php
@@ -41,7 +41,7 @@ class UserInfoTest extends MediaWikiTestCase {
$this->assertSame( $user->getId(), $userinfo->getId() );
$this->assertSame( $user->getName(), $userinfo->getName() );
$this->assertSame( $user->getToken( true ), $userinfo->getToken() );
- $this->assertInstanceOf( 'User', $userinfo->getUser() );
+ $this->assertInstanceOf( User::class, $userinfo->getUser() );
$userinfo2 = $userinfo->verified();
$this->assertNotSame( $userinfo2, $userinfo );
$this->assertSame( "<-:{$user->getId()}:{$user->getName()}>", (string)$userinfo );
@@ -51,7 +51,7 @@ class UserInfoTest extends MediaWikiTestCase {
$this->assertSame( $user->getId(), $userinfo2->getId() );
$this->assertSame( $user->getName(), $userinfo2->getName() );
$this->assertSame( $user->getToken( true ), $userinfo2->getToken() );
- $this->assertInstanceOf( 'User', $userinfo2->getUser() );
+ $this->assertInstanceOf( User::class, $userinfo2->getUser() );
$this->assertSame( $userinfo2, $userinfo2->verified() );
$this->assertSame( "<+:{$user->getId()}:{$user->getName()}>", (string)$userinfo2 );
@@ -76,7 +76,7 @@ class UserInfoTest extends MediaWikiTestCase {
$this->assertSame( $user->getId(), $userinfo->getId() );
$this->assertSame( $user->getName(), $userinfo->getName() );
$this->assertSame( $user->getToken( true ), $userinfo->getToken() );
- $this->assertInstanceOf( 'User', $userinfo->getUser() );
+ $this->assertInstanceOf( User::class, $userinfo->getUser() );
$userinfo2 = $userinfo->verified();
$this->assertNotSame( $userinfo2, $userinfo );
$this->assertSame( "<-:{$user->getId()}:{$user->getName()}>", (string)$userinfo );
@@ -86,7 +86,7 @@ class UserInfoTest extends MediaWikiTestCase {
$this->assertSame( $user->getId(), $userinfo2->getId() );
$this->assertSame( $user->getName(), $userinfo2->getName() );
$this->assertSame( $user->getToken( true ), $userinfo2->getToken() );
- $this->assertInstanceOf( 'User', $userinfo2->getUser() );
+ $this->assertInstanceOf( User::class, $userinfo2->getUser() );
$this->assertSame( $userinfo2, $userinfo2->verified() );
$this->assertSame( "<+:{$user->getId()}:{$user->getName()}>", (string)$userinfo2 );
@@ -103,7 +103,7 @@ class UserInfoTest extends MediaWikiTestCase {
$this->assertSame( $user->getId(), $userinfo->getId() );
$this->assertSame( $user->getName(), $userinfo->getName() );
$this->assertSame( '', $userinfo->getToken() );
- $this->assertInstanceOf( 'User', $userinfo->getUser() );
+ $this->assertInstanceOf( User::class, $userinfo->getUser() );
$userinfo2 = $userinfo->verified();
$this->assertNotSame( $userinfo2, $userinfo );
$this->assertSame( "<-:{$user->getId()}:{$user->getName()}>", (string)$userinfo );
@@ -113,7 +113,7 @@ class UserInfoTest extends MediaWikiTestCase {
$this->assertSame( $user->getId(), $userinfo2->getId() );
$this->assertSame( $user->getName(), $userinfo2->getName() );
$this->assertSame( '', $userinfo2->getToken() );
- $this->assertInstanceOf( 'User', $userinfo2->getUser() );
+ $this->assertInstanceOf( User::class, $userinfo2->getUser() );
$this->assertSame( $userinfo2, $userinfo2->verified() );
$this->assertSame( "<+:{$user->getId()}:{$user->getName()}>", (string)$userinfo2 );
diff --git a/www/wiki/tests/phpunit/includes/shell/CommandFactoryTest.php b/www/wiki/tests/phpunit/includes/shell/CommandFactoryTest.php
index aacfd43c..b031431a 100644
--- a/www/wiki/tests/phpunit/includes/shell/CommandFactoryTest.php
+++ b/www/wiki/tests/phpunit/includes/shell/CommandFactoryTest.php
@@ -1,13 +1,18 @@
<?php
+use MediaWiki\Shell\Command;
use MediaWiki\Shell\CommandFactory;
+use MediaWiki\Shell\FirejailCommand;
use Psr\Log\NullLogger;
use Wikimedia\TestingAccessWrapper;
/**
* @group Shell
*/
-class CommandFactoryTest extends PHPUnit_Framework_TestCase {
+class CommandFactoryTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
/**
* @covers MediaWiki\Shell\CommandFactory::create
*/
@@ -21,13 +26,25 @@ class CommandFactoryTest extends PHPUnit_Framework_TestCase {
'walltime' => 40,
];
- $factory = new CommandFactory( $limits, $cgroup );
+ $factory = new CommandFactory( $limits, $cgroup, false );
$factory->setLogger( $logger );
+ $factory->logStderr();
$command = $factory->create();
+ $this->assertInstanceOf( Command::class, $command );
$wrapper = TestingAccessWrapper::newFromObject( $command );
$this->assertSame( $logger, $wrapper->logger );
$this->assertSame( $cgroup, $wrapper->cgroup );
$this->assertSame( $limits, $wrapper->limits );
+ $this->assertTrue( $wrapper->doLogStderr );
+ }
+
+ /**
+ * @covers MediaWiki\Shell\CommandFactory::create
+ */
+ public function testFirejailCreate() {
+ $factory = new CommandFactory( [], false, 'firejail' );
+ $factory->setLogger( new NullLogger() );
+ $this->assertInstanceOf( FirejailCommand::class, $factory->create() );
}
}
diff --git a/www/wiki/tests/phpunit/includes/shell/CommandTest.php b/www/wiki/tests/phpunit/includes/shell/CommandTest.php
index d57b1b1c..2e031638 100644
--- a/www/wiki/tests/phpunit/includes/shell/CommandTest.php
+++ b/www/wiki/tests/phpunit/includes/shell/CommandTest.php
@@ -4,9 +4,13 @@ use MediaWiki\Shell\Command;
use Wikimedia\TestingAccessWrapper;
/**
+ * @covers \MediaWiki\Shell\Command
* @group Shell
*/
-class CommandTest extends PHPUnit_Framework_TestCase {
+class CommandTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
private function requirePosix() {
if ( wfIsWindows() ) {
$this->markTestSkipped( 'This test requires a POSIX environment.' );
@@ -47,23 +51,61 @@ class CommandTest extends PHPUnit_Framework_TestCase {
$this->assertSame( "bar\n", $result->getStdout() );
}
+ public function testStdout() {
+ $this->requirePosix();
+
+ $command = new Command();
+
+ $result = $command
+ ->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' )
+ ->execute();
+
+ $this->assertNotContains( 'ThisIsStderr', $result->getStdout() );
+ $this->assertEquals( "ThisIsStderr\n", $result->getStderr() );
+ }
+
+ public function testStdoutRedirection() {
+ $this->requirePosix();
+
+ $command = new Command();
+
+ $result = $command
+ ->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' )
+ ->includeStderr( true )
+ ->execute();
+
+ $this->assertEquals( "ThisIsStderr\n", $result->getStdout() );
+ $this->assertNull( $result->getStderr() );
+ }
+
public function testOutput() {
global $IP;
$this->requirePosix();
+ chdir( $IP );
$command = new Command();
$result = $command
- ->params( [ 'ls', "$IP/index.php" ] )
+ ->params( [ 'ls', 'index.php' ] )
->execute();
- $this->assertSame( "$IP/index.php", trim( $result->getStdout() ) );
+ $this->assertRegExp( '/^index.php$/m', $result->getStdout() );
+ $this->assertSame( null, $result->getStderr() );
$command = new Command();
$result = $command
->params( [ 'ls', 'index.php', 'no-such-file' ] )
->includeStderr()
->execute();
+ $this->assertRegExp( '/^index.php$/m', $result->getStdout() );
$this->assertRegExp( '/^.+no-such-file.*$/m', $result->getStdout() );
+ $this->assertSame( null, $result->getStderr() );
+
+ $command = new Command();
+ $result = $command
+ ->params( [ 'ls', 'index.php', 'no-such-file' ] )
+ ->execute();
+ $this->assertRegExp( '/^index.php$/m', $result->getStdout() );
+ $this->assertRegExp( '/^.+no-such-file.*$/m', $result->getStderr() );
}
/**
@@ -91,4 +133,49 @@ class CommandTest extends PHPUnit_Framework_TestCase {
$this->assertEquals( 333333, strlen( $output ) );
}
}
+
+ public function testLogStderr() {
+ $this->requirePosix();
+
+ $logger = new TestLogger( true, function ( $message, $level, $context ) {
+ return $level === Psr\Log\LogLevel::ERROR ? '1' : null;
+ }, true );
+ $command = new Command();
+ $command->setLogger( $logger );
+ $command->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' );
+ $command->execute();
+ $this->assertEmpty( $logger->getBuffer() );
+
+ $command = new Command();
+ $command->setLogger( $logger );
+ $command->logStderr();
+ $command->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' );
+ $command->execute();
+ $this->assertSame( 1, count( $logger->getBuffer() ) );
+ $this->assertSame( trim( $logger->getBuffer()[0][2]['error'] ), 'ThisIsStderr' );
+ }
+
+ public function testInput() {
+ $this->requirePosix();
+
+ $command = new Command();
+ $command->params( 'cat' );
+ $command->input( 'abc' );
+ $result = $command->execute();
+ $this->assertSame( 'abc', $result->getStdout() );
+
+ // now try it with something that does not fit into a single block
+ $command = new Command();
+ $command->params( 'cat' );
+ $command->input( str_repeat( '!', 1000000 ) );
+ $result = $command->execute();
+ $this->assertSame( 1000000, strlen( $result->getStdout() ) );
+
+ // And try it with empty input
+ $command = new Command();
+ $command->params( 'cat' );
+ $command->input( '' );
+ $result = $command->execute();
+ $this->assertSame( '', $result->getStdout() );
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/shell/FirejailCommandTest.php b/www/wiki/tests/phpunit/includes/shell/FirejailCommandTest.php
new file mode 100644
index 00000000..681c3dcd
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/shell/FirejailCommandTest.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * Copyright (C) 2017 Kunal Mehta <legoktm@member.fsf.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+use MediaWiki\Shell\FirejailCommand;
+use MediaWiki\Shell\Shell;
+use Wikimedia\TestingAccessWrapper;
+
+class FirejailCommandTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
+ public function provideBuildFinalCommand() {
+ global $IP;
+ // phpcs:ignore Generic.Files.LineLength
+ $env = "'MW_INCLUDE_STDERR=;MW_CPU_LIMIT=180; MW_CGROUP='\'''\''; MW_MEM_LIMIT=307200; MW_FILE_SIZE_LIMIT=102400; MW_WALL_CLOCK_LIMIT=180; MW_USE_LOG_PIPE=yes'";
+ $limit = "/bin/bash '$IP/includes/shell/limit.sh'";
+ $profile = "--profile=$IP/includes/shell/firejail.profile";
+ $blacklist = '--blacklist=' . realpath( MW_CONFIG_FILE );
+ $default = "$blacklist --noroot --seccomp --private-dev";
+ return [
+ [
+ 'No restrictions',
+ 'ls', 0, "$limit ''\''ls'\''' $env"
+ ],
+ [
+ 'default restriction',
+ 'ls', Shell::RESTRICT_DEFAULT,
+ "$limit 'firejail --quiet $profile $default -- '\''ls'\''' $env"
+ ],
+ [
+ 'no network',
+ 'ls', Shell::NO_NETWORK,
+ "$limit 'firejail --quiet $profile --net=none -- '\''ls'\''' $env"
+ ],
+ [
+ 'default restriction & no network',
+ 'ls', Shell::RESTRICT_DEFAULT | Shell::NO_NETWORK,
+ "$limit 'firejail --quiet $profile $default --net=none -- '\''ls'\''' $env"
+ ],
+ [
+ 'seccomp',
+ 'ls', Shell::SECCOMP,
+ "$limit 'firejail --quiet $profile --seccomp -- '\''ls'\''' $env"
+ ],
+ [
+ 'seccomp & no execve',
+ 'ls', Shell::SECCOMP | Shell::NO_EXECVE,
+ "$limit 'firejail --quiet $profile --shell=none --seccomp=execve -- '\''ls'\''' $env"
+ ],
+ ];
+ }
+
+ /**
+ * @covers \MediaWiki\Shell\FirejailCommand::buildFinalCommand()
+ * @dataProvider provideBuildFinalCommand
+ */
+ public function testBuildFinalCommand( $desc, $params, $flags, $expected ) {
+ $command = new FirejailCommand( 'firejail' );
+ $command
+ ->params( $params )
+ ->restrict( $flags );
+ $wrapper = TestingAccessWrapper::newFromObject( $command );
+ $output = $wrapper->buildFinalCommand( $wrapper->command );
+ $this->assertEquals( $expected, $output[0], $desc );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/shell/ShellTest.php b/www/wiki/tests/phpunit/includes/shell/ShellTest.php
index 7c96c3c8..bf46f44b 100644
--- a/www/wiki/tests/phpunit/includes/shell/ShellTest.php
+++ b/www/wiki/tests/phpunit/includes/shell/ShellTest.php
@@ -1,11 +1,17 @@
<?php
+use MediaWiki\Shell\Command;
use MediaWiki\Shell\Shell;
+use Wikimedia\TestingAccessWrapper;
/**
+ * @covers \MediaWiki\Shell\Shell
* @group Shell
*/
-class ShellTest extends PHPUnit_Framework_TestCase {
+class ShellTest extends MediaWikiTestCase {
+
+ use MediaWikiCoversValidator;
+
public function testIsDisabled() {
$this->assertInternalType( 'bool', Shell::isDisabled() ); // sanity
}
@@ -28,4 +34,72 @@ class ShellTest extends PHPUnit_Framework_TestCase {
'skip nulls' => [ [ 'ls', null ], "'ls'" ],
];
}
+
+ /**
+ * @covers \MediaWiki\Shell\Shell::makeScriptCommand
+ * @dataProvider provideMakeScriptCommand
+ *
+ * @param string $expected
+ * @param string $script
+ * @param string[] $parameters
+ * @param string[] $options
+ * @param callable|null $hook
+ */
+ public function testMakeScriptCommand( $expected,
+ $script,
+ $parameters,
+ $options = [],
+ $hook = null
+ ) {
+ // Running tests under Vagrant involves MWMultiVersion that uses the below hook
+ $this->setMwGlobals( 'wgHooks', [] );
+
+ if ( $hook ) {
+ $this->setTemporaryHook( 'wfShellWikiCmd', $hook );
+ }
+
+ $command = Shell::makeScriptCommand( $script, $parameters, $options );
+ $command->params( 'safe' )
+ ->unsafeParams( 'unsafe' );
+
+ $this->assertType( Command::class, $command );
+
+ $wrapper = TestingAccessWrapper::newFromObject( $command );
+ $this->assertEquals( $expected, $wrapper->command );
+ $this->assertEquals( 0, $wrapper->restrictions & Shell::NO_LOCALSETTINGS );
+ }
+
+ public function provideMakeScriptCommand() {
+ global $wgPhpCli;
+
+ return [
+ [
+ "'$wgPhpCli' 'maintenance/foobar.php' 'bar'\\''\"baz' 'safe' unsafe",
+ 'maintenance/foobar.php',
+ [ 'bar\'"baz' ],
+ ],
+ [
+ "'$wgPhpCli' 'changed.php' '--wiki=somewiki' 'bar'\\''\"baz' 'safe' unsafe",
+ 'maintenance/foobar.php',
+ [ 'bar\'"baz' ],
+ [],
+ function ( &$script, array &$parameters ) {
+ $script = 'changed.php';
+ array_unshift( $parameters, '--wiki=somewiki' );
+ }
+ ],
+ [
+ "'/bin/perl' 'maintenance/foobar.php' 'bar'\\''\"baz' 'safe' unsafe",
+ 'maintenance/foobar.php',
+ [ 'bar\'"baz' ],
+ [ 'php' => '/bin/perl' ],
+ ],
+ [
+ "'$wgPhpCli' 'foobinize' 'maintenance/foobar.php' 'bar'\\''\"baz' 'safe' unsafe",
+ 'maintenance/foobar.php',
+ [ 'bar\'"baz' ],
+ [ 'wrapper' => 'foobinize' ],
+ ],
+ ];
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/site/CachingSiteStoreTest.php b/www/wiki/tests/phpunit/includes/site/CachingSiteStoreTest.php
index adf95e6c..0fdcf6da 100644
--- a/www/wiki/tests/phpunit/includes/site/CachingSiteStoreTest.php
+++ b/www/wiki/tests/phpunit/includes/site/CachingSiteStoreTest.php
@@ -42,13 +42,13 @@ class CachingSiteStoreTest extends MediaWikiTestCase {
$sites = $store->getSites();
- $this->assertInstanceOf( 'SiteList', $sites );
+ $this->assertInstanceOf( SiteList::class, $sites );
/**
* @var Site $site
*/
foreach ( $sites as $site ) {
- $this->assertInstanceOf( 'Site', $site );
+ $this->assertInstanceOf( Site::class, $site );
}
foreach ( $testSites as $site ) {
@@ -79,11 +79,11 @@ class CachingSiteStoreTest extends MediaWikiTestCase {
$this->assertTrue( $store->saveSites( $sites ) );
$site = $store->getSite( 'ertrywuutr' );
- $this->assertInstanceOf( 'Site', $site );
+ $this->assertInstanceOf( Site::class, $site );
$this->assertEquals( 'en', $site->getLanguageCode() );
$site = $store->getSite( 'sdfhxujgkfpth' );
- $this->assertInstanceOf( 'Site', $site );
+ $this->assertInstanceOf( Site::class, $site );
$this->assertEquals( 'nl', $site->getLanguageCode() );
}
@@ -91,7 +91,7 @@ class CachingSiteStoreTest extends MediaWikiTestCase {
* @covers CachingSiteStore::reset
*/
public function testReset() {
- $dbSiteStore = $this->getMockBuilder( 'SiteStore' )
+ $dbSiteStore = $this->getMockBuilder( SiteStore::class )
->disableOriginalConstructor()
->getMock();
diff --git a/www/wiki/tests/phpunit/includes/site/DBSiteStoreTest.php b/www/wiki/tests/phpunit/includes/site/DBSiteStoreTest.php
index 32dd7f28..7c16f6c5 100644
--- a/www/wiki/tests/phpunit/includes/site/DBSiteStoreTest.php
+++ b/www/wiki/tests/phpunit/includes/site/DBSiteStoreTest.php
@@ -51,13 +51,13 @@ class DBSiteStoreTest extends MediaWikiTestCase {
$sites = $store->getSites();
- $this->assertInstanceOf( 'SiteList', $sites );
+ $this->assertInstanceOf( SiteList::class, $sites );
/**
* @var Site $site
*/
foreach ( $sites as $site ) {
- $this->assertInstanceOf( 'Site', $site );
+ $this->assertInstanceOf( Site::class, $site );
}
foreach ( $expectedSites as $site ) {
@@ -88,15 +88,15 @@ class DBSiteStoreTest extends MediaWikiTestCase {
$this->assertTrue( $store->saveSites( $sites ) );
$site = $store->getSite( 'ertrywuutr' );
- $this->assertInstanceOf( 'Site', $site );
+ $this->assertInstanceOf( Site::class, $site );
$this->assertEquals( 'en', $site->getLanguageCode() );
- $this->assertTrue( is_integer( $site->getInternalId() ) );
+ $this->assertTrue( is_int( $site->getInternalId() ) );
$this->assertTrue( $site->getInternalId() >= 0 );
$site = $store->getSite( 'sdfhxujgkfpth' );
- $this->assertInstanceOf( 'Site', $site );
+ $this->assertInstanceOf( Site::class, $site );
$this->assertEquals( 'nl', $site->getLanguageCode() );
- $this->assertTrue( is_integer( $site->getInternalId() ) );
+ $this->assertTrue( is_int( $site->getInternalId() ) );
$this->assertTrue( $site->getInternalId() >= 0 );
}
diff --git a/www/wiki/tests/phpunit/includes/site/FileBasedSiteLookupTest.php b/www/wiki/tests/phpunit/includes/site/FileBasedSiteLookupTest.php
index 7984795b..69e0e389 100644
--- a/www/wiki/tests/phpunit/includes/site/FileBasedSiteLookupTest.php
+++ b/www/wiki/tests/phpunit/includes/site/FileBasedSiteLookupTest.php
@@ -27,7 +27,9 @@
*
* @author Katie Filbert < aude.wiki@gmail.com >
*/
-class FileBasedSiteLookupTest extends PHPUnit_Framework_TestCase {
+class FileBasedSiteLookupTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
protected function setUp() {
$this->cacheFile = $this->getCacheFile();
@@ -64,7 +66,7 @@ class FileBasedSiteLookupTest extends PHPUnit_Framework_TestCase {
}
private function getSiteLookup( SiteList $sites ) {
- $siteLookup = $this->getMockBuilder( 'SiteLookup' )
+ $siteLookup = $this->getMockBuilder( SiteLookup::class )
->disableOriginalConstructor()
->getMock();
diff --git a/www/wiki/tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php b/www/wiki/tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php
index 64cdbaa9..2ac27146 100644
--- a/www/wiki/tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php
+++ b/www/wiki/tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php
@@ -27,7 +27,9 @@ use MediaWiki\Site\MediaWikiPageNameNormalizer;
*
* @author Marius Hoch
*/
-class MediaWikiPageNameNormalizerTest extends PHPUnit_Framework_TestCase {
+class MediaWikiPageNameNormalizerTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
/**
* @dataProvider normalizePageTitleProvider
diff --git a/www/wiki/tests/phpunit/includes/site/SiteExporterTest.php b/www/wiki/tests/phpunit/includes/site/SiteExporterTest.php
index c0d8c001..db900da9 100644
--- a/www/wiki/tests/phpunit/includes/site/SiteExporterTest.php
+++ b/www/wiki/tests/phpunit/includes/site/SiteExporterTest.php
@@ -29,10 +29,13 @@
*
* @author Daniel Kinzler
*/
-class SiteExporterTest extends PHPUnit_Framework_TestCase {
+class SiteExporterTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+ use PHPUnit4And6Compat;
public function testConstructor_InvalidArgument() {
- $this->setExpectedException( 'InvalidArgumentException' );
+ $this->setExpectedException( InvalidArgumentException::class );
new SiteExporter( 'Foo' );
}
@@ -75,7 +78,7 @@ class SiteExporterTest extends PHPUnit_Framework_TestCase {
}
private function newSiteStore( SiteList $sites ) {
- $store = $this->getMockBuilder( 'SiteStore' )->getMock();
+ $store = $this->getMockBuilder( SiteStore::class )->getMock();
$store->expects( $this->once() )
->method( 'saveSites' )
diff --git a/www/wiki/tests/phpunit/includes/site/SiteImporterTest.php b/www/wiki/tests/phpunit/includes/site/SiteImporterTest.php
index ea49429c..bd95a501 100644
--- a/www/wiki/tests/phpunit/includes/site/SiteImporterTest.php
+++ b/www/wiki/tests/phpunit/includes/site/SiteImporterTest.php
@@ -29,10 +29,13 @@
*
* @author Daniel Kinzler
*/
-class SiteImporterTest extends PHPUnit_Framework_TestCase {
+class SiteImporterTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+ use PHPUnit4And6Compat;
private function newSiteImporter( array $expectedSites, $errorCount ) {
- $store = $this->getMockBuilder( 'SiteStore' )->getMock();
+ $store = $this->getMockBuilder( SiteStore::class )->getMock();
$store->expects( $this->once() )
->method( 'saveSites' )
@@ -44,7 +47,7 @@ class SiteImporterTest extends PHPUnit_Framework_TestCase {
->method( 'getSites' )
->will( $this->returnValue( new SiteList() ) );
- $errorHandler = $this->getMockBuilder( 'Psr\Log\LoggerInterface' )->getMock();
+ $errorHandler = $this->getMockBuilder( Psr\Log\LoggerInterface::class )->getMock();
$errorHandler->expects( $this->exactly( $errorCount ) )
->method( 'error' );
@@ -146,9 +149,9 @@ class SiteImporterTest extends PHPUnit_Framework_TestCase {
}
public function testImportFromXML_malformed() {
- $this->setExpectedException( 'Exception' );
+ $this->setExpectedException( Exception::class );
- $store = $this->getMockBuilder( 'SiteStore' )->getMock();
+ $store = $this->getMockBuilder( SiteStore::class )->getMock();
$importer = new SiteImporter( $store );
$importer->importFromXML( 'THIS IS NOT XML' );
}
diff --git a/www/wiki/tests/phpunit/includes/site/SiteListTest.php b/www/wiki/tests/phpunit/includes/site/SiteListTest.php
index f7d01171..a4a171c9 100644
--- a/www/wiki/tests/phpunit/includes/site/SiteListTest.php
+++ b/www/wiki/tests/phpunit/includes/site/SiteListTest.php
@@ -99,7 +99,7 @@ class SiteListTest extends MediaWikiTestCase {
* @var Site $site
*/
foreach ( $sites as $site ) {
- if ( is_integer( $site->getInternalId() ) ) {
+ if ( is_int( $site->getInternalId() ) ) {
$this->assertEquals( $site, $sites->getSiteByInternalId( $site->getInternalId() ) );
}
}
@@ -155,7 +155,7 @@ class SiteListTest extends MediaWikiTestCase {
* @var Site $site
*/
foreach ( $sites as $site ) {
- if ( is_integer( $site->getInternalId() ) ) {
+ if ( is_int( $site->getInternalId() ) ) {
$this->assertTrue( $site, $sites->hasInternalId( $site->getInternalId() ) );
}
}
diff --git a/www/wiki/tests/phpunit/includes/site/SiteTest.php b/www/wiki/tests/phpunit/includes/site/SiteTest.php
index 59f046b2..ac5f956e 100644
--- a/www/wiki/tests/phpunit/includes/site/SiteTest.php
+++ b/www/wiki/tests/phpunit/includes/site/SiteTest.php
@@ -283,12 +283,12 @@ class SiteTest extends MediaWikiTestCase {
* @covers Site::unserialize
*/
public function testSerialization( Site $site ) {
- $this->assertInstanceOf( 'Serializable', $site );
+ $this->assertInstanceOf( Serializable::class, $site );
$serialization = serialize( $site );
$newInstance = unserialize( $serialization );
- $this->assertInstanceOf( 'Site', $newInstance );
+ $this->assertInstanceOf( Site::class, $newInstance );
$this->assertEquals( $serialization, serialize( $newInstance ) );
}
diff --git a/www/wiki/tests/phpunit/includes/site/SitesCacheFileBuilderTest.php b/www/wiki/tests/phpunit/includes/site/SitesCacheFileBuilderTest.php
index af94a9d2..8c84ce57 100644
--- a/www/wiki/tests/phpunit/includes/site/SitesCacheFileBuilderTest.php
+++ b/www/wiki/tests/phpunit/includes/site/SitesCacheFileBuilderTest.php
@@ -27,7 +27,9 @@
*
* @author Katie Filbert < aude.wiki@gmail.com >
*/
-class SitesCacheFileBuilderTest extends PHPUnit_Framework_TestCase {
+class SitesCacheFileBuilderTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
protected function setUp() {
$this->cacheFile = $this->getCacheFile();
@@ -98,7 +100,7 @@ class SitesCacheFileBuilderTest extends PHPUnit_Framework_TestCase {
}
private function getSiteLookup( SiteList $sites ) {
- $siteLookup = $this->getMockBuilder( 'SiteLookup' )
+ $siteLookup = $this->getMockBuilder( SiteLookup::class )
->disableOriginalConstructor()
->getMock();
diff --git a/www/wiki/tests/phpunit/includes/site/TestSites.php b/www/wiki/tests/phpunit/includes/site/TestSites.php
index 39c34217..a66fce29 100644
--- a/www/wiki/tests/phpunit/includes/site/TestSites.php
+++ b/www/wiki/tests/phpunit/includes/site/TestSites.php
@@ -24,8 +24,6 @@
* @ingroup Site
* @ingroup Test
*
- * @group Site
- *
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
class TestSites {
diff --git a/www/wiki/tests/phpunit/includes/skins/SkinFactoryTest.php b/www/wiki/tests/phpunit/includes/skins/SkinFactoryTest.php
index d3663c84..4289fd91 100644
--- a/www/wiki/tests/phpunit/includes/skins/SkinFactoryTest.php
+++ b/www/wiki/tests/phpunit/includes/skins/SkinFactoryTest.php
@@ -11,7 +11,7 @@ class SkinFactoryTest extends MediaWikiTestCase {
return new SkinFallback();
} );
$this->assertTrue( true ); // No exception thrown
- $this->setExpectedException( 'InvalidArgumentException' );
+ $this->setExpectedException( InvalidArgumentException::class );
$factory->register( 'invalid', 'Invalid', 'Invalid callback' );
}
@@ -20,7 +20,7 @@ class SkinFactoryTest extends MediaWikiTestCase {
*/
public function testMakeSkinWithNoBuilders() {
$factory = new SkinFactory();
- $this->setExpectedException( 'SkinException' );
+ $this->setExpectedException( SkinException::class );
$factory->makeSkin( 'nobuilderregistered' );
}
@@ -32,7 +32,7 @@ class SkinFactoryTest extends MediaWikiTestCase {
$factory->register( 'unittest', 'Unittest', function () {
return true; // Not a Skin object
} );
- $this->setExpectedException( 'UnexpectedValueException' );
+ $this->setExpectedException( UnexpectedValueException::class );
$factory->makeSkin( 'unittest' );
}
@@ -46,8 +46,20 @@ class SkinFactoryTest extends MediaWikiTestCase {
} );
$skin = $factory->makeSkin( 'testfallback' );
- $this->assertInstanceOf( 'Skin', $skin );
- $this->assertInstanceOf( 'SkinFallback', $skin );
+ $this->assertInstanceOf( Skin::class, $skin );
+ $this->assertInstanceOf( SkinFallback::class, $skin );
+ $this->assertEquals( 'fallback', $skin->getSkinName() );
+ }
+
+ /**
+ * @covers Skin::__construct
+ * @covers Skin::getSkinName
+ */
+ public function testGetSkinName() {
+ $skin = new SkinFallback();
+ $this->assertEquals( 'fallback', $skin->getSkinName(), 'Default' );
+ $skin = new SkinFallback( 'testname' );
+ $this->assertEquals( 'testname', $skin->getSkinName(), 'Constructor argument' );
}
/**
diff --git a/www/wiki/tests/phpunit/includes/skins/SkinTemplateTest.php b/www/wiki/tests/phpunit/includes/skins/SkinTemplateTest.php
index e8260ac2..06b06677 100644
--- a/www/wiki/tests/phpunit/includes/skins/SkinTemplateTest.php
+++ b/www/wiki/tests/phpunit/includes/skins/SkinTemplateTest.php
@@ -7,13 +7,12 @@
*
* @author Bene* < benestar.wikimedia@gmail.com >
*/
-
class SkinTemplateTest extends MediaWikiTestCase {
/**
* @dataProvider makeListItemProvider
*/
public function testMakeListItem( $expected, $key, $item, $options, $message ) {
- $template = $this->getMockForAbstractClass( 'BaseTemplate' );
+ $template = $this->getMockForAbstractClass( BaseTemplate::class );
$this->assertEquals(
$expected,
diff --git a/www/wiki/tests/phpunit/includes/sparql/SparqlClientTest.php b/www/wiki/tests/phpunit/includes/sparql/SparqlClientTest.php
new file mode 100644
index 00000000..b217af15
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/sparql/SparqlClientTest.php
@@ -0,0 +1,190 @@
+<?php
+namespace MediaWiki\Sparql;
+
+use Http;
+use MediaWiki\Http\HttpRequestFactory;
+use MWHttpRequest;
+use PHPUnit4And6Compat;
+
+/**
+ * @covers \MediaWiki\Sparql\SparqlClient
+ */
+class SparqlClientTest extends \PHPUnit\Framework\TestCase {
+
+ use PHPUnit4And6Compat;
+
+ private function getRequestFactory( $request ) {
+ $requestFactory = $this->getMock( HttpRequestFactory::class );
+ $requestFactory->method( 'create' )->willReturn( $request );
+ return $requestFactory;
+ }
+
+ private function getRequestMock( $content ) {
+ $request = $this->getMockBuilder( MWHttpRequest::class )->disableOriginalConstructor()->getMock();
+ $request->method( 'execute' )->willReturn( \Status::newGood( 200 ) );
+ $request->method( 'getContent' )->willReturn( $content );
+ return $request;
+ }
+
+ public function testQuery() {
+ $json = <<<JSON
+{
+ "head" : {
+ "vars" : [ "x", "y", "z" ]
+ },
+ "results" : {
+ "bindings" : [ {
+ "x" : {
+ "type" : "uri",
+ "value" : "http://wikiba.se/ontology#Dump"
+ },
+ "y" : {
+ "type" : "uri",
+ "value" : "http://creativecommons.org/ns#license"
+ },
+ "z" : {
+ "type" : "uri",
+ "value" : "http://creativecommons.org/publicdomain/zero/1.0/"
+ }
+ }, {
+ "x" : {
+ "type" : "uri",
+ "value" : "http://wikiba.se/ontology#Dump"
+ },
+ "z" : {
+ "type" : "literal",
+ "value" : "0.1.0"
+ }
+ } ]
+ }
+}
+JSON;
+
+ $request = $this->getRequestMock( $json );
+ $client = new SparqlClient( 'http://acme.test/', $this->getRequestFactory( $request ) );
+
+ // values only
+ $result = $client->query( "TEST SPARQL" );
+ $this->assertCount( 2, $result );
+ $this->assertEquals( 'http://wikiba.se/ontology#Dump', $result[0]['x'] );
+ $this->assertEquals( 'http://creativecommons.org/ns#license', $result[0]['y'] );
+ $this->assertEquals( '0.1.0', $result[1]['z'] );
+ $this->assertNull( $result[1]['y'] );
+ // raw data format
+ $result = $client->query( "TEST SPARQL 2", true );
+ $this->assertCount( 2, $result );
+ $this->assertEquals( 'uri', $result[0]['x']['type'] );
+ $this->assertEquals( 'http://wikiba.se/ontology#Dump', $result[0]['x']['value'] );
+ $this->assertEquals( 'literal', $result[1]['z']['type'] );
+ $this->assertEquals( '0.1.0', $result[1]['z']['value'] );
+ $this->assertNull( $result[1]['y'] );
+ }
+
+ /**
+ * @expectedException \Mediawiki\Sparql\SparqlException
+ */
+ public function testBadQuery() {
+ $request = $this->getMockBuilder( MWHttpRequest::class )->disableOriginalConstructor()->getMock();
+ $client = new SparqlClient( 'http://acme.test/', $this->getRequestFactory( $request ) );
+
+ $request->method( 'execute' )->willReturn( \Status::newFatal( "Bad query" ) );
+ $result = $client->query( "TEST SPARQL 3" );
+ }
+
+ public function optionsProvider() {
+ return [
+ 'defaults' => [
+ 'TEST тест SPARQL 4 ',
+ null,
+ null,
+ [
+ 'http://acme.test/',
+ 'query=TEST+%D1%82%D0%B5%D1%81%D1%82+SPARQL+4+',
+ 'format=json',
+ 'maxQueryTimeMillis=30000',
+ ],
+ [
+ 'method' => 'GET',
+ 'userAgent' => Http::userAgent() ." SparqlClient",
+ 'timeout' => 30
+ ]
+ ],
+ 'big query' => [
+ str_repeat( 'ZZ', SparqlClient::MAX_GET_SIZE ),
+ null,
+ null,
+ [
+ 'format=json',
+ 'maxQueryTimeMillis=30000',
+ ],
+ [
+ 'method' => 'POST',
+ 'postData' => 'query=' . str_repeat( 'ZZ', SparqlClient::MAX_GET_SIZE ),
+ ]
+ ],
+ 'timeout 1s' => [
+ 'TEST SPARQL 4',
+ null,
+ 1,
+ [
+ 'maxQueryTimeMillis=1000',
+ ],
+ [
+ 'timeout' => 1
+ ]
+ ],
+ 'more options' => [
+ 'TEST SPARQL 5',
+ [
+ 'userAgent' => 'My Test',
+ 'randomOption' => 'duck',
+ ],
+ null,
+ [],
+ [
+ 'userAgent' => 'My Test',
+ 'randomOption' => 'duck',
+ ]
+ ],
+
+ ];
+ }
+
+ /**
+ * @dataProvider optionsProvider
+ * @param string $sparql
+ * @param array|null $options
+ * @param int|null $timeout
+ * @param array $expectedUrl
+ * @param array $expectedOptions
+ */
+ public function testOptions( $sparql, $options, $timeout, $expectedUrl, $expectedOptions ) {
+ $requestFactory = $this->getMock( HttpRequestFactory::class );
+ $client = new SparqlClient( 'http://acme.test/', $requestFactory );
+
+ $request = $this->getRequestMock( '{}' );
+
+ $requestFactory->method( 'create' )->willReturnCallback(
+ function ( $url, $options ) use ( $request, $expectedUrl, $expectedOptions ) {
+ foreach ( $expectedUrl as $eurl ) {
+ $this->assertContains( $eurl, $url );
+ }
+ foreach ( $expectedOptions as $ekey => $evalue ) {
+ $this->assertArrayHasKey( $ekey, $options );
+ $this->assertEquals( $options[$ekey], $evalue );
+ }
+ return $request;
+ }
+ );
+
+ if ( !is_null( $options ) ) {
+ $client->setClientOptions( $options );
+ }
+ if ( !is_null( $timeout ) ) {
+ $client->setTimeout( $timeout );
+ }
+
+ $result = $client->query( $sparql );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/specialpage/AbstractChangesListSpecialPageTestCase.php b/www/wiki/tests/phpunit/includes/specialpage/AbstractChangesListSpecialPageTestCase.php
index b101857e..8b8ba0c0 100644
--- a/www/wiki/tests/phpunit/includes/specialpage/AbstractChangesListSpecialPageTestCase.php
+++ b/www/wiki/tests/phpunit/includes/specialpage/AbstractChangesListSpecialPageTestCase.php
@@ -19,7 +19,10 @@ abstract class AbstractChangesListSpecialPageTestCase extends MediaWikiTestCase
global $wgGroupPermissions;
parent::setUp();
- $this->setMwGlobals( 'wgRCWatchCategoryMembership', true );
+ $this->setMwGlobals( [
+ 'wgRCWatchCategoryMembership' => true,
+ 'wgUseRCPatrol' => true,
+ ] );
if ( isset( $wgGroupPermissions['patrollers'] ) ) {
$this->oldPatrollersGroup = $wgGroupPermissions['patrollers'];
@@ -44,6 +47,8 @@ abstract class AbstractChangesListSpecialPageTestCase extends MediaWikiTestCase
$this->changesListSpecialPage->registerFilters();
}
+ abstract protected function getPage();
+
protected function tearDown() {
global $wgGroupPermissions;
@@ -54,6 +59,8 @@ abstract class AbstractChangesListSpecialPageTestCase extends MediaWikiTestCase
}
}
+ abstract public function provideParseParameters();
+
/**
* @dataProvider provideParseParameters
*/
diff --git a/www/wiki/tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php b/www/wiki/tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php
index f494785b..aeaa1aee 100644
--- a/www/wiki/tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php
+++ b/www/wiki/tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php
@@ -15,6 +15,13 @@ use Wikimedia\TestingAccessWrapper;
* @covers ChangesListSpecialPage
*/
class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase {
+ public function setUp() {
+ parent::setUp();
+ $this->setMwGlobals( [
+ 'wgStructuredChangeFiltersShowPreference' => true,
+ ] );
+ }
+
protected function getPage() {
$mock = $this->getMockBuilder( ChangesListSpecialPage::class )
->setConstructorArgs(
@@ -98,9 +105,14 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
}
private static function normalizeCondition( $conds ) {
+ $dbr = wfGetDB( DB_REPLICA );
$normalized = array_map(
- function ( $k, $v ) {
- return is_numeric( $k ) ? $v : "$k = $v";
+ function ( $k, $v ) use ( $dbr ) {
+ if ( is_array( $v ) ) {
+ sort( $v );
+ }
+ // (Ab)use makeList() to format only this entry
+ return $dbr->makeList( [ $k => $v ], Database::LIST_AND );
},
array_keys( $conds ),
$conds
@@ -109,9 +121,9 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
return $normalized;
}
- /** return false if condition begin with 'rc_timestamp ' */
+ /** return false if condition begins with 'rc_timestamp ' */
private static function filterOutRcTimestampCondition( $var ) {
- return ( false === strpos( $var, 'rc_timestamp ' ) );
+ return ( is_array( $var ) || false === strpos( $var, 'rc_timestamp ' ) );
}
public function testRcNsFilter() {
@@ -192,10 +204,15 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
}
public function testRcHidemyselfFilter() {
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->overrideMwServices();
+
$user = $this->getTestUser()->getUser();
+ $user->getActorId( wfGetDB( DB_MASTER ) );
$this->assertConditions(
[ # expected
- "rc_user_text != '{$user->getName()}'",
+ "NOT((rc_actor = '{$user->getActorId()}') OR "
+ . "(rc_actor = '0' AND rc_user = '{$user->getId()}'))",
],
[
'hidemyself' => 1,
@@ -205,9 +222,10 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
);
$user = User::newFromName( '10.11.12.13', false );
+ $id = $user->getActorId( wfGetDB( DB_MASTER ) );
$this->assertConditions(
[ # expected
- "rc_user_text != '10.11.12.13'",
+ "NOT((rc_actor = '$id') OR (rc_actor = '0' AND rc_user_text = '10.11.12.13'))",
],
[
'hidemyself' => 1,
@@ -218,10 +236,15 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
}
public function testRcHidebyothersFilter() {
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->overrideMwServices();
+
$user = $this->getTestUser()->getUser();
+ $user->getActorId( wfGetDB( DB_MASTER ) );
$this->assertConditions(
[ # expected
- "rc_user_text = '{$user->getName()}'",
+ "(rc_actor = '{$user->getActorId()}') OR "
+ . "(rc_actor = '0' AND rc_user_text = '{$user->getName()}')",
],
[
'hidebyothers' => 1,
@@ -231,9 +254,10 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
);
$user = User::newFromName( '10.11.12.13', false );
+ $id = $user->getActorId( wfGetDB( DB_MASTER ) );
$this->assertConditions(
[ # expected
- "rc_user_text = '10.11.12.13'",
+ "(rc_actor = '$id') OR (rc_actor = '0' AND rc_user_text = '10.11.12.13')",
],
[
'hidebyothers' => 1,
@@ -293,6 +317,7 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
}
public function testRcHidepatrolledDisabledFilter() {
+ $this->setMwGlobals( 'wgUseRCPatrol', false );
$user = $this->getTestUser()->getUser();
$this->assertConditions(
[ # expected
@@ -306,6 +331,7 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
}
public function testRcHideunpatrolledDisabledFilter() {
+ $this->setMwGlobals( 'wgUseRCPatrol', false );
$user = $this->getTestUser()->getUser();
$this->assertConditions(
[ # expected
@@ -321,7 +347,7 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
$user = $this->getTestSysop()->getUser();
$this->assertConditions(
[ # expected
- "rc_patrolled = 0",
+ 'rc_patrolled' => 0,
],
[
'hidepatrolled' => 1,
@@ -335,7 +361,7 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
$user = $this->getTestSysop()->getUser();
$this->assertConditions(
[ # expected
- "rc_patrolled = 1",
+ 'rc_patrolled' => [ 1, 2 ],
],
[
'hideunpatrolled' => 1,
@@ -345,6 +371,30 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
);
}
+ public function testRcReviewStatusFilter() {
+ $user = $this->getTestSysop()->getUser();
+ $this->assertConditions(
+ [ #expected
+ 'rc_patrolled' => 1,
+ ],
+ [
+ 'reviewStatus' => 'manual'
+ ],
+ "rc conditions: reviewStatus=manual",
+ $user
+ );
+ $this->assertConditions(
+ [ #expected
+ 'rc_patrolled' => [ 0, 2 ],
+ ],
+ [
+ 'reviewStatus' => 'unpatrolled;auto'
+ ],
+ "rc conditions: reviewStatus=unpatrolled;auto",
+ $user
+ );
+ }
+
public function testRcHideminorFilter() {
$this->assertConditions(
[ # expected
@@ -419,10 +469,13 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
}
public function testFilterUserExpLevelAllExperienceLevels() {
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->overrideMwServices();
+
$this->assertConditions(
[
# expected
- 'rc_user != 0',
+ 'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
],
[
'userExpLevel' => 'newcomer;learner;experienced',
@@ -432,10 +485,13 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
}
public function testFilterUserExpLevelRegistrered() {
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->overrideMwServices();
+
$this->assertConditions(
[
# expected
- 'rc_user != 0',
+ 'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
],
[
'userExpLevel' => 'registered',
@@ -445,10 +501,13 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
}
public function testFilterUserExpLevelUnregistrered() {
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->overrideMwServices();
+
$this->assertConditions(
[
# expected
- 'rc_user' => 0,
+ 'COALESCE( actor_rc_user.actor_user, rc_user ) = 0',
],
[
'userExpLevel' => 'unregistered',
@@ -458,10 +517,13 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
}
public function testFilterUserExpLevelRegistreredOrLearner() {
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->overrideMwServices();
+
$this->assertConditions(
[
# expected
- 'rc_user != 0',
+ 'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
],
[
'userExpLevel' => 'registered;learner',
@@ -471,10 +533,14 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
}
public function testFilterUserExpLevelUnregistreredOrExperienced() {
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->overrideMwServices();
+
$conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
$this->assertRegExp(
- '/\(rc_user = 0\) OR \(\(user_editcount >= 500\) AND \(user_registration <= \'[^\']+\'\)\)/',
+ '/\(COALESCE\( actor_rc_user.actor_user, rc_user \) = 0\) OR '
+ . '\(\(user_editcount >= 500\) AND \(user_registration <= \'[^\']+\'\)\)/',
reset( $conds ),
"rc conditions: userExpLevel=unregistered;experienced"
);
@@ -586,8 +652,10 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
]
);
+ // @todo: This is not at all safe or sane. It just blindly assumes
+ // nothing in $conds depends on any other tables.
$result = wfGetDB( DB_MASTER )->select(
- $tables,
+ 'user',
'user_name',
array_filter( $conds ) + [ 'user_email' => 'ut' ]
);
@@ -734,6 +802,7 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
'cssClass' => null,
'conflicts' => [],
'subset' => [],
+ 'defaultHighlightColor' => null
],
[
'name' => 'hidefoo',
@@ -744,6 +813,7 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
'cssClass' => null,
'conflicts' => [],
'subset' => [],
+ 'defaultHighlightColor' => null
],
],
'fullCoverage' => true,
@@ -765,6 +835,7 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
'priority' => -2,
'conflicts' => [],
'subset' => [],
+ 'defaultHighlightColor' => null
],
[
'name' => 'garply',
@@ -774,6 +845,7 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
'priority' => -3,
'conflicts' => [],
'subset' => [],
+ 'defaultHighlightColor' => null
],
],
'conflicts' => [],
@@ -968,15 +1040,33 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
[
[ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 1 ],
true,
- [ 'hideliu' => 1, 'hidebots' => 1, ],
+ [ 'userExpLevel' => 'unregistered', 'hidebots' => 1, ],
],
-
[
[ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 0 ],
true,
[ 'hidebots' => 0, 'hidehumans' => 1 ],
],
-
+ [
+ [ 'hideanons' => 1 ],
+ true,
+ [ 'userExpLevel' => 'registered' ]
+ ],
+ [
+ [ 'hideliu' => 1 ],
+ true,
+ [ 'userExpLevel' => 'unregistered' ]
+ ],
+ [
+ [ 'hideanons' => 1, 'hidebots' => 1 ],
+ true,
+ [ 'userExpLevel' => 'registered', 'hidebots' => 1 ]
+ ],
+ [
+ [ 'hideliu' => 1, 'hidebots' => 0 ],
+ true,
+ [ 'userExpLevel' => 'unregistered', 'hidebots' => 0 ]
+ ],
[
[ 'hidemyself' => 1, 'hidebyothers' => 1 ],
true,
diff --git a/www/wiki/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php b/www/wiki/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php
index f79f6e48..9ac546dc 100644
--- a/www/wiki/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php
+++ b/www/wiki/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php
@@ -83,7 +83,7 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
SpecialPageFactory::resetList();
$page = SpecialPageFactory::getPage( 'testdummy' );
- $this->assertInstanceOf( 'SpecialPage', $page );
+ $this->assertInstanceOf( SpecialPage::class, $page );
$page2 = SpecialPageFactory::getPage( 'testdummy' );
$this->assertEquals( $shouldReuseInstance, $page2 === $page, "Should re-use instance:" );
@@ -93,7 +93,7 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
* @covers SpecialPageFactory::getNames
*/
public function testGetNames() {
- $this->mergeMwGlobalArrayValue( 'wgSpecialPages', [ 'testdummy' => 'SpecialAllPages' ] );
+ $this->mergeMwGlobalArrayValue( 'wgSpecialPages', [ 'testdummy' => SpecialAllPages::class ] );
SpecialPageFactory::resetList();
$names = SpecialPageFactory::getNames();
diff --git a/www/wiki/tests/phpunit/includes/specialpage/SpecialPageTest.php b/www/wiki/tests/phpunit/includes/specialpage/SpecialPageTest.php
index c665f3cf..2ad39729 100644
--- a/www/wiki/tests/phpunit/includes/specialpage/SpecialPageTest.php
+++ b/www/wiki/tests/phpunit/includes/specialpage/SpecialPageTest.php
@@ -67,7 +67,7 @@ class SpecialPageTest extends MediaWikiTestCase {
$specialPage->getContext()->setUser( $user );
$specialPage->getContext()->setLanguage( Language::factory( 'en' ) );
- $this->setExpectedException( 'UserNotLoggedIn', $expected );
+ $this->setExpectedException( UserNotLoggedIn::class, $expected );
// $specialPage->requireLogin( [ $reason [, $title ] ] )
call_user_func_array(
diff --git a/www/wiki/tests/phpunit/includes/specials/ContribsPagerTest.php b/www/wiki/tests/phpunit/includes/specials/ContribsPagerTest.php
index 9366282f..1147805c 100644
--- a/www/wiki/tests/phpunit/includes/specials/ContribsPagerTest.php
+++ b/www/wiki/tests/phpunit/includes/specials/ContribsPagerTest.php
@@ -18,6 +18,7 @@ class ContribsPagerTest extends MediaWikiTestCase {
}
/**
+ * @covers ContribsPager::processDateFilter
* @dataProvider dateFilterOptionProcessingProvider
* @param array $inputOpts Input options
* @param array $expectedOpts Expected options
diff --git a/www/wiki/tests/phpunit/includes/specials/ImageListPagerTest.php b/www/wiki/tests/phpunit/includes/specials/ImageListPagerTest.php
index 22bdefdf..10c6d04c 100644
--- a/www/wiki/tests/phpunit/includes/specials/ImageListPagerTest.php
+++ b/www/wiki/tests/phpunit/includes/specials/ImageListPagerTest.php
@@ -8,7 +8,6 @@
*
* @group Database
*/
-
class ImageListPagerTest extends MediaWikiTestCase {
/**
* @expectedException MWException
diff --git a/www/wiki/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php b/www/wiki/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php
index 1208a20c..d53a9b8f 100644
--- a/www/wiki/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php
+++ b/www/wiki/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php
@@ -5,10 +5,10 @@
* Copyright © 2011, Antoine Musso
*
* @author Antoine Musso
- * @group Database
*/
/**
+ * @group Database
* @covers QueryPage<extended>
*/
class QueryAllSpecialPagesTest extends MediaWikiTestCase {
@@ -20,7 +20,7 @@ class QueryAllSpecialPagesTest extends MediaWikiTestCase {
/** List query pages that can not be tested automatically */
protected $manualTest = [
- 'LinkSearchPage'
+ LinkSearchPage::class
];
/**
@@ -30,7 +30,7 @@ class QueryAllSpecialPagesTest extends MediaWikiTestCase {
* https://bugs.mysql.com/bug.php?id=10327
*/
protected $reopensTempTable = [
- 'BrokenRedirects',
+ BrokenRedirects::class,
];
/**
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialBlankPageTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialBlankPageTest.php
index 7bfb8618..e0d059fb 100644
--- a/www/wiki/tests/phpunit/includes/specials/SpecialBlankPageTest.php
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialBlankPageTest.php
@@ -1,7 +1,7 @@
<?php
/**
- * @licence GNU GPL v2+
+ * @license GNU GPL v2+
* @author Addshore
*
* @covers SpecialBlankpage
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialEditWatchlistTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialEditWatchlistTest.php
index ab3ac553..05a63dbc 100644
--- a/www/wiki/tests/phpunit/includes/specials/SpecialEditWatchlistTest.php
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialEditWatchlistTest.php
@@ -19,7 +19,7 @@ class SpecialEditWatchlistTest extends SpecialPageTestBase {
}
public function testNotLoggedIn_throwsException() {
- $this->setExpectedException( 'UserNotLoggedIn' );
+ $this->setExpectedException( UserNotLoggedIn::class );
$this->executeSpecialPage();
}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialMIMESearchTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialMIMESearchTest.php
index ede27917..4ecb813f 100644
--- a/www/wiki/tests/phpunit/includes/specials/SpecialMIMESearchTest.php
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialMIMESearchTest.php
@@ -1,8 +1,9 @@
<?php
+
/**
* @group Database
+ * @covers MIMEsearchPage
*/
-
class SpecialMIMESearchTest extends MediaWikiTestCase {
/** @var MIMEsearchPage */
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialMyLanguageTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialMyLanguageTest.php
index 89fd1b06..84fa71a2 100644
--- a/www/wiki/tests/phpunit/includes/specials/SpecialMyLanguageTest.php
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialMyLanguageTest.php
@@ -8,7 +8,10 @@ class SpecialMyLanguageTest extends MediaWikiTestCase {
public function addDBDataOnce() {
$titles = [
'Page/Another',
+ 'Page/Another/ar',
+ 'Page/Another/en',
'Page/Another/ru',
+ 'Page/Another/zh-hans',
];
foreach ( $titles as $title ) {
$page = WikiPage::factory( Title::newFromText( $title ) );
@@ -54,12 +57,22 @@ class SpecialMyLanguageTest extends MediaWikiTestCase {
}
public static function provideFindTitle() {
+ // See addDBDataOnce() for page declarations
return [
+ // [ $expected, $subpage, $langCode, $userLang ]
[ null, '::Fail', 'en', 'en' ],
[ 'Page/Another', 'Page/Another/en', 'en', 'en' ],
[ 'Page/Another', 'Page/Another', 'en', 'en' ],
[ 'Page/Another/ru', 'Page/Another', 'en', 'ru' ],
[ 'Page/Another', 'Page/Another', 'en', 'es' ],
+ [ 'Page/Another/zh-hans', 'Page/Another', 'en', 'zh-hans' ],
+ [ 'Page/Another/zh-hans', 'Page/Another', 'en', 'zh-mo' ],
+ [ 'Page/Another/en', 'Page/Another', 'de', 'es' ],
+ [ 'Page/Another/ar', 'Page/Another', 'en', 'ar' ],
+ [ 'Page/Another/ar', 'Page/Another', 'en', 'arz' ],
+ [ 'Page/Another/ar', 'Page/Another/de', 'en', 'arz' ],
+ [ 'Page/Another/ru', 'Page/Another/ru', 'en', 'arz' ],
+ [ 'Page/Another/ar', 'Page/Another/ru', 'en', 'ar' ],
];
}
}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialPageDataTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialPageDataTest.php
index 3d0d3441..40754063 100644
--- a/www/wiki/tests/phpunit/includes/specials/SpecialPageDataTest.php
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialPageDataTest.php
@@ -2,12 +2,9 @@
/**
* @covers SpecialPageData
- *
* @group Database
- *
* @group SpecialPage
*
- * @license GPL-2.0+
* @author Daniel Kinzler
*/
class SpecialPageDataTest extends SpecialPageTestBase {
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialPageTestBase.php b/www/wiki/tests/phpunit/includes/specials/SpecialPageTestBase.php
index 930bbe4d..274a23c4 100644
--- a/www/wiki/tests/phpunit/includes/specials/SpecialPageTestBase.php
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialPageTestBase.php
@@ -5,11 +5,11 @@
*
* @since 1.26
*
- * @licence GNU GPL v2+
+ * @license GNU GPL v2+
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
* @author Daniel Kinzler
* @author Addshore
- * @author Thiemo Mättig
+ * @author Thiemo Kreuz
*/
abstract class SpecialPageTestBase extends MediaWikiTestCase {
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialPreferencesTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialPreferencesTest.php
index ac58d68c..bdfbb62e 100644
--- a/www/wiki/tests/phpunit/includes/specials/SpecialPreferencesTest.php
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialPreferencesTest.php
@@ -7,6 +7,7 @@
*/
/**
+ * @group Preferences
* @group Database
*
* @covers SpecialPreferences
@@ -24,7 +25,7 @@ class SpecialPreferencesTest extends MediaWikiTestCase {
// Set a low limit
$this->setMwGlobals( 'wgMaxSigChars', 2 );
- $user = $this->createMock( 'User' );
+ $user = $this->createMock( User::class );
$user->expects( $this->any() )
->method( 'isAnon' )
->will( $this->returnValue( false ) );
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialSearchTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialSearchTest.php
index 94924ee1..f0a57266 100644
--- a/www/wiki/tests/phpunit/includes/specials/SpecialSearchTest.php
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialSearchTest.php
@@ -194,7 +194,7 @@ class SpecialSearchTest extends MediaWikiTestCase {
);
$mockSearchEngine = $this->mockSearchEngine( $searchResults );
- $search = $this->getMockBuilder( 'SpecialSearch' )
+ $search = $this->getMockBuilder( SpecialSearch::class )
->setMethods( [ 'getSearchEngine' ] )
->getMock();
$search->expects( $this->any() )
@@ -213,7 +213,7 @@ class SpecialSearchTest extends MediaWikiTestCase {
}
protected function mockSearchEngine( $results ) {
- $mock = $this->getMockBuilder( 'SearchEngine' )
+ $mock = $this->getMockBuilder( SearchEngine::class )
->setMethods( [ 'searchText', 'searchTitle' ] )
->getMock();
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialShortpagesTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialShortpagesTest.php
index a5fb50e0..f799b115 100644
--- a/www/wiki/tests/phpunit/includes/specials/SpecialShortpagesTest.php
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialShortpagesTest.php
@@ -5,7 +5,7 @@
*
* @since 1.30
*
- * @licence GNU GPL v2+
+ * @license GNU GPL v2+
*/
class SpecialShortpagesTest extends MediaWikiTestCase {
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialUncategorizedcategoriesTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialUncategorizedcategoriesTest.php
index 64e78f28..80bd365f 100644
--- a/www/wiki/tests/phpunit/includes/specials/SpecialUncategorizedcategoriesTest.php
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialUncategorizedcategoriesTest.php
@@ -5,10 +5,11 @@
class UncategorizedCategoriesPageTest extends MediaWikiTestCase {
/**
* @dataProvider provideTestGetQueryInfoData
+ * @covers UncategorizedCategoriesPage::getQueryInfo
*/
public function testGetQueryInfo( $msgContent, $expected ) {
$msg = new RawMessage( $msgContent );
- $mockContext = $this->getMockBuilder( 'RequestContext' )->getMock();
+ $mockContext = $this->getMockBuilder( RequestContext::class )->getMock();
$mockContext->method( 'msg' )->willReturn( $msg );
$special = new UncategorizedCategoriesPage();
$special->setContext( $mockContext );
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialUploadTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialUploadTest.php
new file mode 100644
index 00000000..95026c18
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialUploadTest.php
@@ -0,0 +1,29 @@
+<?php
+
+class SpecialUploadTest extends MediaWikiTestCase {
+ /**
+ * @covers SpecialUpload::getInitialPageText
+ * @dataProvider provideGetInitialPageText
+ */
+ public function testGetInitialPageText( $expected, $inputParams ) {
+ $result = call_user_func_array( [ 'SpecialUpload', 'getInitialPageText' ], $inputParams );
+ $this->assertEquals( $expected, $result );
+ }
+
+ public function provideGetInitialPageText() {
+ return [
+ [
+ 'expect' => "== Summary ==\nthis is a test\n",
+ 'params' => [
+ 'this is a test'
+ ],
+ ],
+ [
+ 'expect' => "== Summary ==\nthis is a test\n",
+ 'params' => [
+ "== Summary ==\nthis is a test",
+ ],
+ ],
+ ];
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialWatchlistTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialWatchlistTest.php
index 1c439199..5adbed81 100644
--- a/www/wiki/tests/phpunit/includes/specials/SpecialWatchlistTest.php
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialWatchlistTest.php
@@ -56,7 +56,7 @@ class SpecialWatchlistTest extends SpecialPageTestBase {
}
public function testNotLoggedIn_throwsException() {
- $this->setExpectedException( 'UserNotLoggedIn' );
+ $this->setExpectedException( UserNotLoggedIn::class );
$this->executeSpecialPage();
}
@@ -149,6 +149,7 @@ class SpecialWatchlistTest extends SpecialPageTestBase {
// Second two overriden
'hideanons' => false,
'hideliu' => true,
+ 'userExpLevel' => 'registered'
] + $wikiDefaults,
[
'watchlisthideminor' => 1,
@@ -171,12 +172,14 @@ class SpecialWatchlistTest extends SpecialPageTestBase {
'hidebots' => true,
'hideanons' => false,
'hideliu' => true,
+ 'userExpLevel' => 'unregistered'
] + $allFalse,
[
'watchlisthideminor' => 0,
'watchlisthidebots' => 1,
- 'watchlisthideanons' => 1,
- 'watchlisthideliu' => 0,
+
+ 'watchlisthideanons' => 0,
+ 'watchlisthideliu' => 1,
],
[
'hidebots' => 1,
diff --git a/www/wiki/tests/phpunit/includes/tidy/RemexDriverTest.php b/www/wiki/tests/phpunit/includes/tidy/RemexDriverTest.php
index 6b16cbf6..a5ebaa5d 100644
--- a/www/wiki/tests/phpunit/includes/tidy/RemexDriverTest.php
+++ b/www/wiki/tests/phpunit/includes/tidy/RemexDriverTest.php
@@ -200,14 +200,14 @@ class RemexDriverTest extends MediaWikiTestCase {
'a<small><i><div>d</div></i>e</small>',
'<p>a</p><small><i><div>d</div></i></small><p><small>e</small></p>'
],
+ // phpcs:disable Generic.Files.LineLength
[
'Complex pwrap test 6',
'<i>a<div>b</div>c<b>d<div>e</div>f</b>g</i>',
- // @codingStandardsIgnoreStart Generic.Files.LineLength.TooLong
// PHP 5 does not allow concatenation in initialisation of a class static variable
'<p><i>a</i></p><i><div>b</div></i><p><i>c<b>d</b></i></p><i><b><div>e</div></b></i><p><i><b>f</b>g</i></p>'
- // @codingStandardsIgnoreEnd
],
+ // phpcs:enable
/* FIXME the second <b> causes a stack split which clones the <i> even
* though no <p> is actually generated
[
@@ -252,6 +252,16 @@ class RemexDriverTest extends MediaWikiTestCase {
'<table><b>1<p>2</b>3</p>',
'<b>1</b><p><b>2</b>3</p><table></table>'
],
+ [
+ 'AAA causes reparent of p-wrapped text node (T178632)',
+ '<i><blockquote>x</i></blockquote>',
+ '<i></i><blockquote><p><i>x</i></p></blockquote>',
+ ],
+ [
+ 'p-wrap ended by reparenting (T200827)',
+ '<i><blockquote><p></i>',
+ '<i></i><blockquote><p><i></i></p><p><i></i></p></blockquote>',
+ ],
];
public function provider() {
diff --git a/www/wiki/tests/phpunit/includes/title/ForeignTitleTest.php b/www/wiki/tests/phpunit/includes/title/ForeignTitleTest.php
index 25ff186b..f2fccc75 100644
--- a/www/wiki/tests/phpunit/includes/title/ForeignTitleTest.php
+++ b/www/wiki/tests/phpunit/includes/title/ForeignTitleTest.php
@@ -68,7 +68,7 @@ class ForeignTitleTest extends MediaWikiTestCase {
}
public function testUnknownNamespaceError() {
- $this->setExpectedException( 'MWException' );
+ $this->setExpectedException( MWException::class );
$title = new ForeignTitle( null, 'this', 'that' );
$title->getNamespaceId();
}
diff --git a/www/wiki/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php b/www/wiki/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php
deleted file mode 100644
index c79471d5..00000000
--- a/www/wiki/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php
+++ /dev/null
@@ -1,168 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @author Daniel Kinzler
- */
-
-/**
- * @covers MediaWikiPageLinkRenderer
- *
- * @group Title
- * @group Database
- */
-class MediaWikiPageLinkRendererTest extends MediaWikiTestCase {
-
- protected function setUp() {
- parent::setUp();
-
- $this->setMwGlobals( [
- 'wgContLang' => Language::factory( 'en' ),
- ] );
- }
-
- /**
- * Returns a mock GenderCache that will return "female" always.
- *
- * @return GenderCache
- */
- private function getGenderCache() {
- $genderCache = $this->getMockBuilder( 'GenderCache' )
- ->disableOriginalConstructor()
- ->getMock();
-
- $genderCache->expects( $this->any() )
- ->method( 'getGenderOf' )
- ->will( $this->returnValue( 'female' ) );
-
- return $genderCache;
- }
-
- public static function provideGetPageUrl() {
- return [
- [
- new TitleValue( NS_MAIN, 'Foo_Bar' ),
- [],
- '/Foo_Bar'
- ],
- [
- new TitleValue( NS_USER, 'Hansi_Maier', 'stuff' ),
- [ 'foo' => 'bar' ],
- '/User:Hansi_Maier?foo=bar#stuff'
- ],
- ];
- }
-
- /**
- * @dataProvider provideGetPageUrl
- */
- public function testGetPageUrl( TitleValue $title, $params, $url ) {
- // NOTE: was of Feb 2014, MediaWikiPageLinkRenderer *ignores* the
- // WikitextTitleFormatter we pass here, and relies on the Linker
- // class for generating the link! This may break the test e.g.
- // of Linker uses a different language for the namespace names.
-
- $lang = Language::factory( 'en' );
-
- $formatter = new MediaWikiTitleCodec( $lang, $this->getGenderCache() );
- $renderer = new MediaWikiPageLinkRenderer( $formatter, '/' );
- $actual = $renderer->getPageUrl( $title, $params );
-
- $this->assertEquals( $url, $actual );
- }
-
- public static function provideRenderHtmlLink() {
- return [
- [
- new TitleValue( NS_MAIN, 'Foo_Bar' ),
- 'Foo Bar',
- '!<a .*href=".*?Foo_Bar.*?".*?>Foo Bar</a>!'
- ],
- [
- // NOTE: Linker doesn't include fragments in "broken" links
- // NOTE: once this no longer uses Linker, we will get "2" instead of "User" for the namespace.
- new TitleValue( NS_USER, 'Hansi_Maier', 'stuff' ),
- 'Hansi Maier\'s Stuff',
- '!<a .*href=".*?User:Hansi_Maier.*?>Hansi Maier\'s Stuff</a>!'
- ],
- [
- // NOTE: Linker doesn't include fragments in "broken" links
- // NOTE: once this no longer uses Linker, we will get "2" instead of "User" for the namespace.
- new TitleValue( NS_USER, 'Hansi_Maier', 'stuff' ),
- null,
- '!<a .*href=".*?User:Hansi_Maier.*?>User:Hansi Maier#stuff</a>!'
- ],
- ];
- }
-
- /**
- * @dataProvider provideRenderHtmlLink
- */
- public function testRenderHtmlLink( TitleValue $title, $text, $pattern ) {
- // NOTE: was of Feb 2014, MediaWikiPageLinkRenderer *ignores* the
- // WikitextTitleFormatter we pass here, and relies on the Linker
- // class for generating the link! This may break the test e.g.
- // of Linker uses a different language for the namespace names.
-
- $lang = Language::factory( 'en' );
-
- $formatter = new MediaWikiTitleCodec( $lang, $this->getGenderCache() );
- $renderer = new MediaWikiPageLinkRenderer( $formatter );
- $actual = $renderer->renderHtmlLink( $title, $text );
-
- $this->assertRegExp( $pattern, $actual );
- }
-
- public static function provideRenderWikitextLink() {
- return [
- [
- new TitleValue( NS_MAIN, 'Foo_Bar' ),
- 'Foo Bar',
- '[[:0:Foo Bar|Foo Bar]]'
- ],
- [
- new TitleValue( NS_USER, 'Hansi_Maier', 'stuff' ),
- 'Hansi Maier\'s Stuff',
- '[[:2:Hansi Maier#stuff|Hansi Maier&#39;s Stuff]]'
- ],
- [
- new TitleValue( NS_USER, 'Hansi_Maier', 'stuff' ),
- null,
- '[[:2:Hansi Maier#stuff|2:Hansi Maier#stuff]]'
- ],
- ];
- }
-
- /**
- * @dataProvider provideRenderWikitextLink
- */
- public function testRenderWikitextLink( TitleValue $title, $text, $expected ) {
- $formatter = $this->getMock( 'TitleFormatter' );
- $formatter->expects( $this->any() )
- ->method( 'getFullText' )
- ->will( $this->returnCallback(
- function ( TitleValue $title ) {
- return str_replace( '_', ' ', "$title" );
- }
- ) );
-
- $renderer = new MediaWikiPageLinkRenderer( $formatter, '/' );
- $actual = $renderer->renderWikitextLink( $title, $text );
-
- $this->assertEquals( $expected, $actual );
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/title/MediaWikiTitleCodecTest.php b/www/wiki/tests/phpunit/includes/title/MediaWikiTitleCodecTest.php
index e7ac940b..e1b98ec3 100644
--- a/www/wiki/tests/phpunit/includes/title/MediaWikiTitleCodecTest.php
+++ b/www/wiki/tests/phpunit/includes/title/MediaWikiTitleCodecTest.php
@@ -64,7 +64,7 @@ class MediaWikiTitleCodecTest extends MediaWikiTestCase {
* @return GenderCache
*/
private function getGenderCache() {
- $genderCache = $this->getMockBuilder( 'GenderCache' )
+ $genderCache = $this->getMockBuilder( GenderCache::class )
->disableOriginalConstructor()
->getMock();
@@ -385,7 +385,7 @@ class MediaWikiTitleCodecTest extends MediaWikiTestCase {
* @dataProvider provideParseTitle_invalid
*/
public function testParseTitle_invalid( $text ) {
- $this->setExpectedException( 'MalformedTitleException' );
+ $this->setExpectedException( MalformedTitleException::class );
$codec = $this->makeCodec( 'en' );
$codec->parseTitle( $text, NS_MAIN );
diff --git a/www/wiki/tests/phpunit/includes/title/SubpageImportTitleFactoryTest.php b/www/wiki/tests/phpunit/includes/title/SubpageImportTitleFactoryTest.php
index 93ce0800..008cf5d9 100644
--- a/www/wiki/tests/phpunit/includes/title/SubpageImportTitleFactoryTest.php
+++ b/www/wiki/tests/phpunit/includes/title/SubpageImportTitleFactoryTest.php
@@ -80,7 +80,7 @@ class SubpageImportTitleFactoryTest extends MediaWikiTestCase {
* @dataProvider failureProvider
*/
public function testFailures( Title $rootPage ) {
- $this->setExpectedException( 'MWException' );
+ $this->setExpectedException( MWException::class );
new SubpageImportTitleFactory( $rootPage );
}
}
diff --git a/www/wiki/tests/phpunit/includes/title/TitleValueTest.php b/www/wiki/tests/phpunit/includes/title/TitleValueTest.php
index 4dbda74a..d221b431 100644
--- a/www/wiki/tests/phpunit/includes/title/TitleValueTest.php
+++ b/www/wiki/tests/phpunit/includes/title/TitleValueTest.php
@@ -78,7 +78,7 @@ class TitleValueTest extends MediaWikiTestCase {
* @dataProvider badConstructorProvider
*/
public function testConstructionErrors( $ns, $text, $fragment, $interwiki ) {
- $this->setExpectedException( 'InvalidArgumentException' );
+ $this->setExpectedException( InvalidArgumentException::class );
new TitleValue( $ns, $text, $fragment, $interwiki );
}
@@ -116,4 +116,33 @@ class TitleValueTest extends MediaWikiTestCase {
$this->assertEquals( $text, $title->getText() );
}
+
+ public function provideTestToString() {
+ yield [
+ new TitleValue( 0, 'Foo' ),
+ '0:Foo'
+ ];
+ yield [
+ new TitleValue( 1, 'Bar_Baz' ),
+ '1:Bar_Baz'
+ ];
+ yield [
+ new TitleValue( 9, 'JoJo', 'Frag' ),
+ '9:JoJo#Frag'
+ ];
+ yield [
+ new TitleValue( 200, 'tea', 'Fragment', 'wikicode' ),
+ 'wikicode:200:tea#Fragment'
+ ];
+ }
+
+ /**
+ * @dataProvider provideTestToString
+ */
+ public function testToString( TitleValue $value, $expected ) {
+ $this->assertSame(
+ $expected,
+ $value->__toString()
+ );
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/upload/UploadBaseTest.php b/www/wiki/tests/phpunit/includes/upload/UploadBaseTest.php
index dd68cdca..a80262e9 100644
--- a/www/wiki/tests/phpunit/includes/upload/UploadBaseTest.php
+++ b/www/wiki/tests/phpunit/includes/upload/UploadBaseTest.php
@@ -103,6 +103,8 @@ class UploadBaseTest extends MediaWikiTestCase {
}
/**
+ * @covers UploadBase::verifyUpload
+ *
* test uploading a 100 bytes file with $wgMaxUploadSize = 100
*
* This method should be abstracted so we can test different settings.
@@ -126,6 +128,7 @@ class UploadBaseTest extends MediaWikiTestCase {
}
/**
+ * @covers UploadBase::checkSvgScriptCallback
* @dataProvider provideCheckSvgScriptCallback
*/
public function testCheckSvgScriptCallback( $svg, $wellFormed, $filterMatch, $message ) {
@@ -135,7 +138,7 @@ class UploadBaseTest extends MediaWikiTestCase {
}
public static function provideCheckSvgScriptCallback() {
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return [
// html5sec SVG vectors
[
@@ -508,10 +511,11 @@ class UploadBaseTest extends MediaWikiTestCase {
'DTD with aliased entities apos (Should be allowed)'
]
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
/**
+ * @covers UploadBase::detectScriptInSvg
* @dataProvider provideDetectScriptInSvg
*/
public function testDetectScriptInSvg( $svg, $expected, $message ) {
@@ -552,12 +556,13 @@ class UploadBaseTest extends MediaWikiTestCase {
}
/**
+ * @covers UploadBase::checkXMLEncodingMissmatch
* @dataProvider provideCheckXMLEncodingMissmatch
*/
public function testCheckXMLEncodingMissmatch( $fileContents, $evil ) {
$filename = $this->getNewTempFile();
file_put_contents( $filename, $fileContents );
- $this->assertSame( UploadBase::checkXMLEncodingMissmatch( $filename ), $evil );
+ $this->assertSame( $evil, UploadBase::checkXMLEncodingMissmatch( $filename ) );
}
public function provideCheckXMLEncodingMissmatch() {
diff --git a/www/wiki/tests/phpunit/includes/upload/UploadFromUrlTest.php b/www/wiki/tests/phpunit/includes/upload/UploadFromUrlTest.php
index 62081aa3..a69a137b 100644
--- a/www/wiki/tests/phpunit/includes/upload/UploadFromUrlTest.php
+++ b/www/wiki/tests/phpunit/includes/upload/UploadFromUrlTest.php
@@ -22,7 +22,7 @@ class UploadFromUrlTest extends ApiTestCase {
}
protected function doApiRequest( array $params, array $unused = null,
- $appendModule = false, User $user = null
+ $appendModule = false, User $user = null, $tokenType = null
) {
global $wgRequest;
diff --git a/www/wiki/tests/phpunit/includes/upload/UploadStashTest.php b/www/wiki/tests/phpunit/includes/upload/UploadStashTest.php
index 7c40a2df..39acbb07 100644
--- a/www/wiki/tests/phpunit/includes/upload/UploadStashTest.php
+++ b/www/wiki/tests/phpunit/includes/upload/UploadStashTest.php
@@ -14,14 +14,13 @@ class UploadStashTest extends MediaWikiTestCase {
/**
* @var string
*/
- private $bug29408File;
+ private $tmpFile;
protected function setUp() {
parent::setUp();
- // Setup a file for T31408
- $this->bug29408File = wfTempDir() . '/bug29408';
- file_put_contents( $this->bug29408File, "\x00" );
+ $this->tmpFile = $this->getNewTempFile();
+ file_put_contents( $this->tmpFile, "\x00" );
self::$users = [
'sysop' => new TestUser(
@@ -39,18 +38,6 @@ class UploadStashTest extends MediaWikiTestCase {
];
}
- protected function tearDown() {
- if ( file_exists( $this->bug29408File . "." ) ) {
- unlink( $this->bug29408File . "." );
- }
-
- if ( file_exists( $this->bug29408File ) ) {
- unlink( $this->bug29408File );
- }
-
- parent::tearDown();
- }
-
/**
* @todo give this test a real name explaining what is being tested here
*/
@@ -61,7 +48,7 @@ class UploadStashTest extends MediaWikiTestCase {
$stash = new UploadStash( $repo );
// Throws exception caught by PHPUnit on failure
- $file = $stash->stashFile( $this->bug29408File );
+ $file = $stash->stashFile( $this->tmpFile );
// We'll never reach this point if we hit T31408
$this->assertTrue( true, 'Unrecognized file without extension' );
@@ -104,4 +91,23 @@ class UploadStashTest extends MediaWikiTestCase {
$this->assertTrue( UploadFromStash::isValidRequest( $request ) );
}
+ public function testExceptionWhenStoreTempFails() {
+ $mockRepoStoreStatusResult = Status::newFatal( 'TEST_ERROR' );
+ $mockRepo = $this->getMockBuilder( FileRepo::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mockRepo->expects( $this->once() )
+ ->method( 'storeTemp' )
+ ->willReturn( $mockRepoStoreStatusResult );
+
+ $stash = new UploadStash( $mockRepo );
+ try {
+ $stash->stashFile( $this->tmpFile );
+ $this->fail( 'Expected UploadStashFileException not thrown' );
+ } catch ( UploadStashFileException $e ) {
+ $this->assertInstanceOf( ILocalizedException::class, $e );
+ } catch ( Exception $e ) {
+ $this->fail( 'Unexpected exception class ' . get_class( $e ) );
+ }
+ }
}
diff --git a/www/wiki/tests/phpunit/includes/user/BotPasswordTest.php b/www/wiki/tests/phpunit/includes/user/BotPasswordTest.php
index 09cf3507..3bbc2dfa 100644
--- a/www/wiki/tests/phpunit/includes/user/BotPasswordTest.php
+++ b/www/wiki/tests/phpunit/includes/user/BotPasswordTest.php
@@ -32,14 +32,14 @@ class BotPasswordTest extends MediaWikiTestCase {
$this->testUser = $this->getMutableTestUser();
$this->testUserName = $this->testUser->getUser()->getName();
- $mock1 = $this->getMockForAbstractClass( 'CentralIdLookup' );
+ $mock1 = $this->getMockForAbstractClass( CentralIdLookup::class );
$mock1->expects( $this->any() )->method( 'isAttached' )
->will( $this->returnValue( true ) );
$mock1->expects( $this->any() )->method( 'lookupUserNames' )
->will( $this->returnValue( [ $this->testUserName => 42, 'UTDummy' => 43, 'UTInvalid' => 0 ] ) );
$mock1->expects( $this->never() )->method( 'lookupCentralIds' );
- $mock2 = $this->getMockForAbstractClass( 'CentralIdLookup' );
+ $mock2 = $this->getMockForAbstractClass( CentralIdLookup::class );
$mock2->expects( $this->any() )->method( 'isAttached' )
->will( $this->returnValue( false ) );
$mock2->expects( $this->any() )->method( 'lookupUserNames' )
@@ -96,7 +96,7 @@ class BotPasswordTest extends MediaWikiTestCase {
public function testBasics() {
$user = $this->testUser->getUser();
$bp = BotPassword::newFromUser( $user, 'BotPassword' );
- $this->assertInstanceOf( 'BotPassword', $bp );
+ $this->assertInstanceOf( BotPassword::class, $bp );
$this->assertTrue( $bp->isSaved() );
$this->assertSame( 42, $bp->getUserCentralId() );
$this->assertSame( 'BotPassword', $bp->getAppId() );
@@ -124,7 +124,7 @@ class BotPasswordTest extends MediaWikiTestCase {
'user' => $user,
'appId' => 'DoesNotExist'
] );
- $this->assertInstanceOf( 'BotPassword', $bp );
+ $this->assertInstanceOf( BotPassword::class, $bp );
$this->assertFalse( $bp->isSaved() );
$this->assertSame( 42, $bp->getUserCentralId() );
$this->assertSame( 'DoesNotExist', $bp->getAppId() );
@@ -137,7 +137,7 @@ class BotPasswordTest extends MediaWikiTestCase {
'restrictions' => MWRestrictions::newFromJson( '{"IPAddresses":["127.0.0.0/8"]}' ),
'grants' => [ 'test' ],
] );
- $this->assertInstanceOf( 'BotPassword', $bp );
+ $this->assertInstanceOf( BotPassword::class, $bp );
$this->assertFalse( $bp->isSaved() );
$this->assertSame( 43, $bp->getUserCentralId() );
$this->assertSame( 'DoesNotExist2', $bp->getAppId() );
@@ -149,7 +149,7 @@ class BotPasswordTest extends MediaWikiTestCase {
'centralId' => 45,
'appId' => 'DoesNotExist'
] );
- $this->assertInstanceOf( 'BotPassword', $bp );
+ $this->assertInstanceOf( BotPassword::class, $bp );
$this->assertFalse( $bp->isSaved() );
$this->assertSame( 45, $bp->getUserCentralId() );
$this->assertSame( 'DoesNotExist', $bp->getAppId() );
@@ -159,7 +159,7 @@ class BotPasswordTest extends MediaWikiTestCase {
'user' => $user,
'appId' => 'BotPassword'
] );
- $this->assertInstanceOf( 'BotPassword', $bp );
+ $this->assertInstanceOf( BotPassword::class, $bp );
$this->assertFalse( $bp->isSaved() );
$this->assertNull( BotPassword::newUnsaved( [
@@ -187,12 +187,12 @@ class BotPasswordTest extends MediaWikiTestCase {
$bp = TestingAccessWrapper::newFromObject( BotPassword::newFromCentralId( 42, 'BotPassword' ) );
$password = $bp->getPassword();
- $this->assertInstanceOf( 'Password', $password );
+ $this->assertInstanceOf( Password::class, $password );
$this->assertTrue( $password->equals( 'foobaz' ) );
$bp->centralId = 44;
$password = $bp->getPassword();
- $this->assertInstanceOf( 'InvalidPassword', $password );
+ $this->assertInstanceOf( InvalidPassword::class, $password );
$bp = TestingAccessWrapper::newFromObject( BotPassword::newFromCentralId( 42, 'BotPassword' ) );
$dbw = wfGetDB( DB_MASTER );
@@ -203,21 +203,21 @@ class BotPasswordTest extends MediaWikiTestCase {
__METHOD__
);
$password = $bp->getPassword();
- $this->assertInstanceOf( 'InvalidPassword', $password );
+ $this->assertInstanceOf( InvalidPassword::class, $password );
}
public function testInvalidateAllPasswordsForUser() {
$bp1 = TestingAccessWrapper::newFromObject( BotPassword::newFromCentralId( 42, 'BotPassword' ) );
$bp2 = TestingAccessWrapper::newFromObject( BotPassword::newFromCentralId( 43, 'BotPassword' ) );
- $this->assertNotInstanceOf( 'InvalidPassword', $bp1->getPassword(), 'sanity check' );
- $this->assertNotInstanceOf( 'InvalidPassword', $bp2->getPassword(), 'sanity check' );
+ $this->assertNotInstanceOf( InvalidPassword::class, $bp1->getPassword(), 'sanity check' );
+ $this->assertNotInstanceOf( InvalidPassword::class, $bp2->getPassword(), 'sanity check' );
BotPassword::invalidateAllPasswordsForUser( $this->testUserName );
- $this->assertInstanceOf( 'InvalidPassword', $bp1->getPassword() );
- $this->assertNotInstanceOf( 'InvalidPassword', $bp2->getPassword() );
+ $this->assertInstanceOf( InvalidPassword::class, $bp1->getPassword() );
+ $this->assertNotInstanceOf( InvalidPassword::class, $bp2->getPassword() );
$bp = TestingAccessWrapper::newFromObject( BotPassword::newFromCentralId( 42, 'BotPassword' ) );
- $this->assertInstanceOf( 'InvalidPassword', $bp->getPassword() );
+ $this->assertInstanceOf( InvalidPassword::class, $bp->getPassword() );
}
public function testRemoveAllPasswordsForUser() {
@@ -312,7 +312,7 @@ class BotPasswordTest extends MediaWikiTestCase {
);
// Failed restriction
- $request = $this->getMockBuilder( 'FauxRequest' )
+ $request = $this->getMockBuilder( FauxRequest::class )
->setMethods( [ 'getIP' ] )
->getMock();
$request->expects( $this->any() )->method( 'getIP' )
@@ -333,7 +333,7 @@ class BotPasswordTest extends MediaWikiTestCase {
'sanity check'
);
$status = BotPassword::login( "{$this->testUserName}@BotPassword", 'foobaz', $request );
- $this->assertInstanceOf( 'Status', $status );
+ $this->assertInstanceOf( Status::class, $status );
$this->assertTrue( $status->isGood() );
$session = $status->getValue();
$this->assertInstanceOf( MediaWiki\Session\Session::class, $session );
@@ -368,7 +368,7 @@ class BotPasswordTest extends MediaWikiTestCase {
$this->assertFalse( $bp->save( 'update', $passwordHash ) );
$this->assertTrue( $bp->save( 'insert', $passwordHash ) );
$bp2 = BotPassword::newFromCentralId( 42, 'TestSave', BotPassword::READ_LATEST );
- $this->assertInstanceOf( 'BotPassword', $bp2 );
+ $this->assertInstanceOf( BotPassword::class, $bp2 );
$this->assertEquals( $bp->getUserCentralId(), $bp2->getUserCentralId() );
$this->assertEquals( $bp->getAppId(), $bp2->getAppId() );
$this->assertEquals( $bp->getToken(), $bp2->getToken() );
@@ -376,7 +376,7 @@ class BotPasswordTest extends MediaWikiTestCase {
$this->assertEquals( $bp->getGrants(), $bp2->getGrants() );
$pw = TestingAccessWrapper::newFromObject( $bp )->getPassword();
if ( $password === null ) {
- $this->assertInstanceOf( 'InvalidPassword', $pw );
+ $this->assertInstanceOf( InvalidPassword::class, $pw );
} else {
$this->assertTrue( $pw->equals( $password ) );
}
@@ -388,11 +388,11 @@ class BotPasswordTest extends MediaWikiTestCase {
$this->assertTrue( $bp->save( 'update' ) );
$this->assertNotEquals( $token, $bp->getToken() );
$bp2 = BotPassword::newFromCentralId( 42, 'TestSave', BotPassword::READ_LATEST );
- $this->assertInstanceOf( 'BotPassword', $bp2 );
+ $this->assertInstanceOf( BotPassword::class, $bp2 );
$this->assertEquals( $bp->getToken(), $bp2->getToken() );
$pw = TestingAccessWrapper::newFromObject( $bp )->getPassword();
if ( $password === null ) {
- $this->assertInstanceOf( 'InvalidPassword', $pw );
+ $this->assertInstanceOf( InvalidPassword::class, $pw );
} else {
$this->assertTrue( $pw->equals( $password ) );
}
diff --git a/www/wiki/tests/phpunit/includes/user/CentralIdLookupTest.php b/www/wiki/tests/phpunit/includes/user/CentralIdLookupTest.php
index 789cf08f..dc9fe2ad 100644
--- a/www/wiki/tests/phpunit/includes/user/CentralIdLookupTest.php
+++ b/www/wiki/tests/phpunit/includes/user/CentralIdLookupTest.php
@@ -9,16 +9,16 @@ use Wikimedia\TestingAccessWrapper;
class CentralIdLookupTest extends MediaWikiTestCase {
public function testFactory() {
- $mock = $this->getMockForAbstractClass( 'CentralIdLookup' );
+ $mock = $this->getMockForAbstractClass( CentralIdLookup::class );
$this->setMwGlobals( [
'wgCentralIdLookupProviders' => [
- 'local' => [ 'class' => 'LocalIdLookup' ],
- 'local2' => [ 'class' => 'LocalIdLookup' ],
+ 'local' => [ 'class' => LocalIdLookup::class ],
+ 'local2' => [ 'class' => LocalIdLookup::class ],
'mock' => [ 'factory' => function () use ( $mock ) {
return $mock;
} ],
- 'bad' => [ 'class' => 'stdClass' ],
+ 'bad' => [ 'class' => stdClass::class ],
],
'wgCentralIdLookupProvider' => 'mock',
] );
@@ -29,13 +29,13 @@ class CentralIdLookupTest extends MediaWikiTestCase {
$local = CentralIdLookup::factory( 'local' );
$this->assertNotSame( $mock, $local );
- $this->assertInstanceOf( 'LocalIdLookup', $local );
+ $this->assertInstanceOf( LocalIdLookup::class, $local );
$this->assertSame( $local, CentralIdLookup::factory( 'local' ) );
$this->assertSame( 'local', $local->getProviderId() );
$local2 = CentralIdLookup::factory( 'local2' );
$this->assertNotSame( $local, $local2 );
- $this->assertInstanceOf( 'LocalIdLookup', $local2 );
+ $this->assertInstanceOf( LocalIdLookup::class, $local2 );
$this->assertSame( 'local2', $local2->getProviderId() );
$this->assertNull( CentralIdLookup::factory( 'unconfigured' ) );
@@ -44,14 +44,14 @@ class CentralIdLookupTest extends MediaWikiTestCase {
public function testCheckAudience() {
$mock = TestingAccessWrapper::newFromObject(
- $this->getMockForAbstractClass( 'CentralIdLookup' )
+ $this->getMockForAbstractClass( CentralIdLookup::class )
);
$user = static::getTestSysop()->getUser();
$this->assertSame( $user, $mock->checkAudience( $user ) );
$user = $mock->checkAudience( CentralIdLookup::AUDIENCE_PUBLIC );
- $this->assertInstanceOf( 'User', $user );
+ $this->assertInstanceOf( User::class, $user );
$this->assertSame( 0, $user->getId() );
$this->assertNull( $mock->checkAudience( CentralIdLookup::AUDIENCE_RAW ) );
@@ -65,7 +65,7 @@ class CentralIdLookupTest extends MediaWikiTestCase {
}
public function testNameFromCentralId() {
- $mock = $this->getMockForAbstractClass( 'CentralIdLookup' );
+ $mock = $this->getMockForAbstractClass( CentralIdLookup::class );
$mock->expects( $this->once() )->method( 'lookupCentralIds' )
->with(
$this->equalTo( [ 15 => null ] ),
@@ -86,7 +86,7 @@ class CentralIdLookupTest extends MediaWikiTestCase {
* @param bool $succeeds
*/
public function testLocalUserFromCentralId( $name, $succeeds ) {
- $mock = $this->getMockForAbstractClass( 'CentralIdLookup' );
+ $mock = $this->getMockForAbstractClass( CentralIdLookup::class );
$mock->expects( $this->any() )->method( 'isAttached' )
->will( $this->returnValue( true ) );
$mock->expects( $this->once() )->method( 'lookupCentralIds' )
@@ -101,13 +101,13 @@ class CentralIdLookupTest extends MediaWikiTestCase {
42, CentralIdLookup::AUDIENCE_RAW, CentralIdLookup::READ_LATEST
);
if ( $succeeds ) {
- $this->assertInstanceOf( 'User', $user );
+ $this->assertInstanceOf( User::class, $user );
$this->assertSame( $name, $user->getName() );
} else {
$this->assertNull( $user );
}
- $mock = $this->getMockForAbstractClass( 'CentralIdLookup' );
+ $mock = $this->getMockForAbstractClass( CentralIdLookup::class );
$mock->expects( $this->any() )->method( 'isAttached' )
->will( $this->returnValue( false ) );
$mock->expects( $this->once() )->method( 'lookupCentralIds' )
@@ -133,7 +133,7 @@ class CentralIdLookupTest extends MediaWikiTestCase {
}
public function testCentralIdFromName() {
- $mock = $this->getMockForAbstractClass( 'CentralIdLookup' );
+ $mock = $this->getMockForAbstractClass( CentralIdLookup::class );
$mock->expects( $this->once() )->method( 'lookupUserNames' )
->with(
$this->equalTo( [ 'FooBar' => 0 ] ),
@@ -149,7 +149,7 @@ class CentralIdLookupTest extends MediaWikiTestCase {
}
public function testCentralIdFromLocalUser() {
- $mock = $this->getMockForAbstractClass( 'CentralIdLookup' );
+ $mock = $this->getMockForAbstractClass( CentralIdLookup::class );
$mock->expects( $this->any() )->method( 'isAttached' )
->will( $this->returnValue( true ) );
$mock->expects( $this->once() )->method( 'lookupUserNames' )
@@ -167,7 +167,7 @@ class CentralIdLookupTest extends MediaWikiTestCase {
)
);
- $mock = $this->getMockForAbstractClass( 'CentralIdLookup' );
+ $mock = $this->getMockForAbstractClass( CentralIdLookup::class );
$mock->expects( $this->any() )->method( 'isAttached' )
->will( $this->returnValue( false ) );
$mock->expects( $this->never() )->method( 'lookupUserNames' );
diff --git a/www/wiki/tests/phpunit/includes/user/ExternalUserNamesTest.php b/www/wiki/tests/phpunit/includes/user/ExternalUserNamesTest.php
new file mode 100644
index 00000000..429bda46
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/user/ExternalUserNamesTest.php
@@ -0,0 +1,131 @@
+<?php
+
+use MediaWiki\Interwiki\InterwikiLookup;
+
+/**
+ * @covers ExternalUserNames
+ */
+class ExternalUserNamesTest extends MediaWikiTestCase {
+
+ public function provideGetUserLinkTitle() {
+ return [
+ [ 'valid:>User1', Title::makeTitle( NS_MAIN, ':User:User1', '', 'valid' ) ],
+ [
+ 'valid:valid:>User1',
+ Title::makeTitle( NS_MAIN, 'valid::User:User1', '', 'valid' )
+ ],
+ [
+ '127.0.0.1',
+ Title::makeTitle( NS_SPECIAL, 'Contributions/127.0.0.1', '', '' )
+ ],
+ [ 'invalid:>User1', null ]
+ ];
+ }
+
+ /**
+ * @covers ExternalUserNames::getUserLinkTitle
+ * @dataProvider provideGetUserLinkTitle
+ */
+ public function testGetUserLinkTitle( $username, $expected ) {
+ $interwikiLookupMock = $this->getMockBuilder( InterwikiLookup::class )
+ ->getMock();
+
+ $interwikiValueMap = [
+ [ 'valid', true ],
+ [ 'invalid', false ]
+ ];
+ $interwikiLookupMock->expects( $this->any() )
+ ->method( 'isValidInterwiki' )
+ ->will( $this->returnValueMap( $interwikiValueMap ) );
+
+ $this->setService( 'InterwikiLookup', $interwikiLookupMock );
+
+ $this->assertEquals(
+ $expected,
+ ExternalUserNames::getUserLinkTitle( $username )
+ );
+ }
+
+ public function provideApplyPrefix() {
+ return [
+ [ 'User1', 'prefix', 'prefix>User1' ],
+ [ 'User1', 'prefix:>', 'prefix>User1' ],
+ [ 'User1', 'prefix:', 'prefix>User1' ],
+ ];
+ }
+
+ /**
+ * @covers ExternalUserNames::applyPrefix
+ * @dataProvider provideApplyPrefix
+ */
+ public function testApplyPrefix( $username, $prefix, $expected ) {
+ $externalUserNames = new ExternalUserNames( $prefix, true );
+
+ $this->assertSame(
+ $expected,
+ $externalUserNames->applyPrefix( $username )
+ );
+ }
+
+ public function provideAddPrefix() {
+ return [
+ [ 'User1', 'prefix', 'prefix>User1' ],
+ [ 'User2', 'prefix2', 'prefix2>User2' ],
+ [ 'User3', 'prefix3', 'prefix3>User3' ],
+ ];
+ }
+
+ /**
+ * @covers ExternalUserNames::addPrefix
+ * @dataProvider provideAddPrefix
+ */
+ public function testAddPrefix( $username, $prefix, $expected ) {
+ $externalUserNames = new ExternalUserNames( $prefix, true );
+
+ $this->assertSame(
+ $expected,
+ $externalUserNames->addPrefix( $username )
+ );
+ }
+
+ public function provideIsExternal() {
+ return [
+ [ 'User1', false ],
+ [ '>User1', true ],
+ [ 'prefix>User1', true ],
+ [ 'prefix:>User1', true ],
+ ];
+ }
+
+ /**
+ * @covers ExternalUserNames::isExternal
+ * @dataProvider provideIsExternal
+ */
+ public function testIsExternal( $username, $expected ) {
+ $this->assertSame(
+ $expected,
+ ExternalUserNames::isExternal( $username )
+ );
+ }
+
+ public function provideGetLocal() {
+ return [
+ [ 'User1', 'User1' ],
+ [ '>User2', 'User2' ],
+ [ 'prefix>User3', 'User3' ],
+ [ 'prefix:>User4', 'User4' ],
+ ];
+ }
+
+ /**
+ * @covers ExternalUserNames::getLocal
+ * @dataProvider provideGetLocal
+ */
+ public function testGetLocal( $username, $expected ) {
+ $this->assertSame(
+ $expected,
+ ExternalUserNames::getLocal( $username )
+ );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/user/PasswordResetTest.php b/www/wiki/tests/phpunit/includes/user/PasswordResetTest.php
index 53f02df6..1f578ab0 100644
--- a/www/wiki/tests/phpunit/includes/user/PasswordResetTest.php
+++ b/www/wiki/tests/phpunit/includes/user/PasswordResetTest.php
@@ -3,9 +3,10 @@
use MediaWiki\Auth\AuthManager;
/**
+ * @covers PasswordReset
* @group Database
*/
-class PasswordResetTest extends PHPUnit_Framework_TestCase {
+class PasswordResetTest extends MediaWikiTestCase {
/**
* @dataProvider provideIsAllowed
*/
@@ -150,6 +151,12 @@ class PasswordResetTest extends PHPUnit_Framework_TestCase {
'EnableEmail' => true,
] );
+ // Unregister the hooks for proper unit testing
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [
+ 'User::mailPasswordInternal' => [],
+ 'SpecialPasswordResetOnSubmit' => [],
+ ] );
+
$authManager = $this->getMockBuilder( AuthManager::class )->disableOriginalConstructor()
->getMock();
$authManager->expects( $this->any() )->method( 'allowsAuthenticationDataChange' )
diff --git a/www/wiki/tests/phpunit/includes/user/UserArrayFromResultTest.php b/www/wiki/tests/phpunit/includes/user/UserArrayFromResultTest.php
index cf980b12..beaacec8 100644
--- a/www/wiki/tests/phpunit/includes/user/UserArrayFromResultTest.php
+++ b/www/wiki/tests/phpunit/includes/user/UserArrayFromResultTest.php
@@ -7,7 +7,7 @@
class UserArrayFromResultTest extends MediaWikiTestCase {
private function getMockResultWrapper( $row = null, $numRows = 1 ) {
- $resultWrapper = $this->getMockBuilder( 'ResultWrapper' )
+ $resultWrapper = $this->getMockBuilder( Wikimedia\Rdbms\ResultWrapper::class )
->disableOriginalConstructor();
$resultWrapper = $resultWrapper->getMock();
@@ -57,7 +57,7 @@ class UserArrayFromResultTest extends MediaWikiTestCase {
$this->assertEquals( $resultWrapper, $object->res );
$this->assertSame( 0, $object->key );
- $this->assertInstanceOf( 'User', $object->current );
+ $this->assertInstanceOf( User::class, $object->current );
$this->assertEquals( $username, $object->current->mName );
}
@@ -88,7 +88,7 @@ class UserArrayFromResultTest extends MediaWikiTestCase {
$username = 'addshore';
$userRow = $this->getRowWithUsername( $username );
$object = $this->getUserArrayFromResult( $this->getMockResultWrapper( $userRow ) );
- $this->assertInstanceOf( 'User', $object->current() );
+ $this->assertInstanceOf( User::class, $object->current() );
$this->assertEquals( $username, $object->current()->mName );
}
diff --git a/www/wiki/tests/phpunit/includes/user/UserGroupMembershipTest.php b/www/wiki/tests/phpunit/includes/user/UserGroupMembershipTest.php
index f95e3871..4862747b 100644
--- a/www/wiki/tests/phpunit/includes/user/UserGroupMembershipTest.php
+++ b/www/wiki/tests/phpunit/includes/user/UserGroupMembershipTest.php
@@ -4,6 +4,9 @@
* @group Database
*/
class UserGroupMembershipTest extends MediaWikiTestCase {
+
+ protected $tablesUsed = [ 'user', 'user_groups' ];
+
/**
* @var User Belongs to no groups
*/
diff --git a/www/wiki/tests/phpunit/includes/user/UserTest.php b/www/wiki/tests/phpunit/includes/user/UserTest.php
index aa368de7..ebfecbca 100644
--- a/www/wiki/tests/phpunit/includes/user/UserTest.php
+++ b/www/wiki/tests/phpunit/includes/user/UserTest.php
@@ -21,7 +21,9 @@ class UserTest extends MediaWikiTestCase {
$this->setMwGlobals( [
'wgGroupPermissions' => [],
'wgRevokePermissions' => [],
+ 'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
] );
+ $this->overrideMwServices();
$this->setUpPermissionGlobals();
@@ -121,7 +123,7 @@ class UserTest extends MediaWikiTestCase {
$this->assertContains( 'nukeworld', $rights );
// Add a Session that limits rights
- $mock = $this->getMockBuilder( stdclass::class )
+ $mock = $this->getMockBuilder( stdClass::class )
->setMethods( [ 'getAllowedUserRights', 'deregisterSession', 'getSessionId' ] )
->getMock();
$mock->method( 'getAllowedUserRights' )->willReturn( [ 'test', 'writetest' ] );
@@ -236,6 +238,8 @@ class UserTest extends MediaWikiTestCase {
* Test, if for all rights a right- message exist,
* which is used on Special:ListGroupRights as help text
* Extensions and core
+ *
+ * @coversNothing
*/
public function testAllRightsWithMessage() {
// Getting all user rights, for core: User::$mCoreRights, for extensions: $wgAvailableRights
@@ -346,6 +350,7 @@ class UserTest extends MediaWikiTestCase {
$user->setOption( 'userjs-someoption', 'test' );
$user->setOption( 'rclimit', 200 );
+ $user->setOption( 'wpwatchlistdays', '0' );
$user->saveSettings();
$user = User::newFromName( $user->getName() );
@@ -357,6 +362,11 @@ class UserTest extends MediaWikiTestCase {
MediaWikiServices::getInstance()->getMainWANObjectCache()->clearProcessCache();
$this->assertEquals( 'test', $user->getOption( 'userjs-someoption' ) );
$this->assertEquals( 200, $user->getOption( 'rclimit' ) );
+
+ // Check that an option saved as a string '0' is returned as an integer.
+ $user = User::newFromName( $user->getName() );
+ $user->load( User::READ_LATEST );
+ $this->assertSame( 0, $user->getOption( 'wpwatchlistdays' ) );
}
/**
@@ -600,7 +610,13 @@ class UserTest extends MediaWikiTestCase {
'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
] );
+ // Unregister the hooks for proper unit testing
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [
+ 'PerformRetroactiveAutoblock' => []
+ ] );
+
// 1. Log in a test user, and block them.
+ $userBlocker = $this->getTestSysop()->getUser();
$user1tmp = $this->getTestUser()->getUser();
$request1 = new FauxRequest();
$request1->getSession()->setUser( $user1tmp );
@@ -609,8 +625,11 @@ class UserTest extends MediaWikiTestCase {
'enableAutoblock' => true,
'expiry' => wfTimestamp( TS_MW, $expiryFiveHours ),
] );
+ $block->setBlocker( $this->getTestSysop()->getUser() );
$block->setTarget( $user1tmp );
- $block->insert();
+ $block->setBlocker( $userBlocker );
+ $res = $block->insert();
+ $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
$user1 = User::newFromSession( $request1 );
$user1->mBlock = $block;
$user1->load();
@@ -639,7 +658,8 @@ class UserTest extends MediaWikiTestCase {
$this->assertTrue( $user2->isAnon() );
$this->assertFalse( $user2->isLoggedIn() );
$this->assertTrue( $user2->isBlocked() );
- $this->assertEquals( true, $user2->getBlock()->isAutoblocking() ); // Non-strict type-check.
+ // Non-strict type-check.
+ $this->assertEquals( true, $user2->getBlock()->isAutoblocking(), 'Autoblock does not work' );
// Can't directly compare the objects becuase of member type differences.
// One day this will work: $this->assertEquals( $block, $user2->getBlock() );
$this->assertEquals( $block->getId(), $user2->getBlock()->getId() );
@@ -672,13 +692,22 @@ class UserTest extends MediaWikiTestCase {
'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
] );
+ // Unregister the hooks for proper unit testing
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [
+ 'PerformRetroactiveAutoblock' => []
+ ] );
+
// 1. Log in a test user, and block them.
+ $userBlocker = $this->getTestSysop()->getUser();
$testUser = $this->getTestUser()->getUser();
$request1 = new FauxRequest();
$request1->getSession()->setUser( $testUser );
$block = new Block( [ 'enableAutoblock' => true ] );
+ $block->setBlocker( $this->getTestSysop()->getUser() );
$block->setTarget( $testUser );
- $block->insert();
+ $block->setBlocker( $userBlocker );
+ $res = $block->insert();
+ $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
$user = User::newFromSession( $request1 );
$user->mBlock = $block;
$user->load();
@@ -708,13 +737,23 @@ class UserTest extends MediaWikiTestCase {
'wgCookiePrefix' => 'wm_infinite_block',
'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
] );
+
+ // Unregister the hooks for proper unit testing
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [
+ 'PerformRetroactiveAutoblock' => []
+ ] );
+
// 1. Log in a test user, and block them indefinitely.
+ $userBlocker = $this->getTestSysop()->getUser();
$user1Tmp = $this->getTestUser()->getUser();
$request1 = new FauxRequest();
$request1->getSession()->setUser( $user1Tmp );
$block = new Block( [ 'enableAutoblock' => true, 'expiry' => 'infinity' ] );
+ $block->setBlocker( $this->getTestSysop()->getUser() );
$block->setTarget( $user1Tmp );
- $block->insert();
+ $block->setBlocker( $userBlocker );
+ $res = $block->insert();
+ $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
$user1 = User::newFromSession( $request1 );
$user1->mBlock = $block;
$user1->load();
@@ -756,30 +795,36 @@ class UserTest extends MediaWikiTestCase {
}
public function testSoftBlockRanges() {
- global $wgUser;
-
- $this->setMwGlobals( [
- 'wgSoftBlockRanges' => [ '10.0.0.0/8' ],
- 'wgUser' => null,
- ] );
+ $setSessionUser = function ( User $user, WebRequest $request ) {
+ $this->setMwGlobals( 'wgUser', $user );
+ RequestContext::getMain()->setUser( $user );
+ RequestContext::getMain()->setRequest( $request );
+ TestingAccessWrapper::newFromObject( $user )->mRequest = $request;
+ $request->getSession()->setUser( $user );
+ };
+ $this->setMwGlobals( 'wgSoftBlockRanges', [ '10.0.0.0/8' ] );
// IP isn't in $wgSoftBlockRanges
+ $wgUser = new User();
$request = new FauxRequest();
$request->setIP( '192.168.0.1' );
- $wgUser = User::newFromSession( $request );
+ $setSessionUser( $wgUser, $request );
$this->assertNull( $wgUser->getBlock() );
// IP is in $wgSoftBlockRanges
+ $wgUser = new User();
$request = new FauxRequest();
$request->setIP( '10.20.30.40' );
- $wgUser = User::newFromSession( $request );
+ $setSessionUser( $wgUser, $request );
$block = $wgUser->getBlock();
$this->assertInstanceOf( Block::class, $block );
$this->assertSame( 'wgSoftBlockRanges', $block->getSystemBlockType() );
// Make sure the block is really soft
- $request->getSession()->setUser( $this->getTestUser()->getUser() );
- $wgUser = User::newFromSession( $request );
+ $wgUser = $this->getTestUser()->getUser();
+ $request = new FauxRequest();
+ $request->setIP( '10.20.30.40' );
+ $setSessionUser( $wgUser, $request );
$this->assertFalse( $wgUser->isAnon(), 'sanity check' );
$this->assertNull( $wgUser->getBlock() );
}
@@ -795,13 +840,22 @@ class UserTest extends MediaWikiTestCase {
'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
] );
+ // Unregister the hooks for proper unit testing
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [
+ 'PerformRetroactiveAutoblock' => []
+ ] );
+
// 1. Log in a blocked test user.
+ $userBlocker = $this->getTestSysop()->getUser();
$user1tmp = $this->getTestUser()->getUser();
$request1 = new FauxRequest();
$request1->getSession()->setUser( $user1tmp );
$block = new Block( [ 'enableAutoblock' => true ] );
+ $block->setBlocker( $this->getTestSysop()->getUser() );
$block->setTarget( $user1tmp );
- $block->insert();
+ $block->setBlocker( $userBlocker );
+ $res = $block->insert();
+ $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
$user1 = User::newFromSession( $request1 );
$user1->mBlock = $block;
$user1->load();
@@ -832,13 +886,22 @@ class UserTest extends MediaWikiTestCase {
'wgSecretKey' => null,
] );
+ // Unregister the hooks for proper unit testing
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [
+ 'PerformRetroactiveAutoblock' => []
+ ] );
+
// 1. Log in a blocked test user.
+ $userBlocker = $this->getTestSysop()->getUser();
$user1tmp = $this->getTestUser()->getUser();
$request1 = new FauxRequest();
$request1->getSession()->setUser( $user1tmp );
$block = new Block( [ 'enableAutoblock' => true ] );
+ $block->setBlocker( $this->getTestSysop()->getUser() );
$block->setTarget( $user1tmp );
- $block->insert();
+ $block->setBlocker( $userBlocker );
+ $res = $block->insert();
+ $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
$user1 = User::newFromSession( $request1 );
$user1->mBlock = $block;
$user1->load();
@@ -861,6 +924,9 @@ class UserTest extends MediaWikiTestCase {
$block->delete();
}
+ /**
+ * @covers User::isPingLimitable
+ */
public function testIsPingLimitable() {
$request = new FauxRequest();
$request->setIP( '1.2.3.4' );
@@ -897,6 +963,7 @@ class UserTest extends MediaWikiTestCase {
}
/**
+ * @covers User::getExperienceLevel
* @dataProvider provideExperienceLevel
*/
public function testExperienceLevel( $editCount, $memberSince, $expLevel ) {
@@ -908,24 +975,25 @@ class UserTest extends MediaWikiTestCase {
] );
$db = wfGetDB( DB_MASTER );
-
- $data = new stdClass();
- $data->user_id = 1;
- $data->user_name = 'name';
- $data->user_real_name = 'Real Name';
- $data->user_touched = 1;
- $data->user_token = 'token';
- $data->user_email = 'a@a.a';
- $data->user_email_authenticated = null;
- $data->user_email_token = 'token';
- $data->user_email_token_expires = null;
- $data->user_editcount = $editCount;
- $data->user_registration = $db->timestamp( time() - $memberSince * 86400 );
- $user = User::newFromRow( $data );
+ $userQuery = User::getQueryInfo();
+ $row = $db->selectRow(
+ $userQuery['tables'],
+ $userQuery['fields'],
+ [ 'user_id' => $this->getTestUser()->getUser()->getId() ],
+ __METHOD__,
+ [],
+ $userQuery['joins']
+ );
+ $row->user_editcount = $editCount;
+ $row->user_registration = $db->timestamp( time() - $memberSince * 86400 );
+ $user = User::newFromRow( $row );
$this->assertEquals( $expLevel, $user->getExperienceLevel() );
}
+ /**
+ * @covers User::getExperienceLevel
+ */
public function testExperienceLevelAnon() {
$user = User::newFromName( '10.11.12.13', false );
@@ -977,4 +1045,164 @@ class UserTest extends MediaWikiTestCase {
);
$this->assertTrue( User::isLocallyBlockedProxy( $ip ) );
}
+
+ public function testActorId() {
+ $this->hideDeprecated( 'User::selectFields' );
+
+ // Newly-created user has an actor ID
+ $user = User::createNew( 'UserTestActorId1' );
+ $id = $user->getId();
+ $this->assertTrue( $user->getActorId() > 0, 'User::createNew sets an actor ID' );
+
+ $user = User::newFromName( 'UserTestActorId2' );
+ $user->addToDatabase();
+ $this->assertTrue( $user->getActorId() > 0, 'User::addToDatabase sets an actor ID' );
+
+ $user = User::newFromName( 'UserTestActorId1' );
+ $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by name' );
+
+ $user = User::newFromId( $id );
+ $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by ID' );
+
+ $user2 = User::newFromActorId( $user->getActorId() );
+ $this->assertEquals( $user->getId(), $user2->getId(),
+ 'User::newFromActorId works for an existing user' );
+
+ $row = $this->db->selectRow( 'user', User::selectFields(), [ 'user_id' => $id ], __METHOD__ );
+ $user = User::newFromRow( $row );
+ $this->assertTrue( $user->getActorId() > 0,
+ 'Actor ID can be retrieved for user loaded with User::selectFields()' );
+
+ $this->db->delete( 'actor', [ 'actor_user' => $id ], __METHOD__ );
+ User::purge( wfWikiId(), $id );
+ // Because WANObjectCache->delete() stupidly doesn't delete from the process cache.
+ ObjectCache::getMainWANInstance()->clearProcessCache();
+
+ $user = User::newFromId( $id );
+ $this->assertFalse( $user->getActorId() > 0, 'No Actor ID by default if none in database' );
+ $this->assertTrue( $user->getActorId( $this->db ) > 0, 'Actor ID can be created if none in db' );
+
+ $user->setName( 'UserTestActorId4-renamed' );
+ $user->saveSettings();
+ $this->assertEquals(
+ $user->getName(),
+ $this->db->selectField(
+ 'actor', 'actor_name', [ 'actor_id' => $user->getActorId() ], __METHOD__
+ ),
+ 'User::saveSettings updates actor table for name change'
+ );
+
+ // For sanity
+ $ip = '192.168.12.34';
+ $this->db->delete( 'actor', [ 'actor_name' => $ip ], __METHOD__ );
+
+ $user = User::newFromName( $ip, false );
+ $this->assertFalse( $user->getActorId() > 0, 'Anonymous user has no actor ID by default' );
+ $this->assertTrue( $user->getActorId( $this->db ) > 0,
+ 'Actor ID can be created for an anonymous user' );
+
+ $user = User::newFromName( $ip, false );
+ $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be loaded for an anonymous user' );
+ $user2 = User::newFromActorId( $user->getActorId() );
+ $this->assertEquals( $user->getName(), $user2->getName(),
+ 'User::newFromActorId works for an anonymous user' );
+ }
+
+ public function testNewFromAnyId() {
+ // Registered user
+ $user = $this->getTestUser()->getUser();
+ for ( $i = 1; $i <= 7; $i++ ) {
+ $test = User::newFromAnyId(
+ ( $i & 1 ) ? $user->getId() : null,
+ ( $i & 2 ) ? $user->getName() : null,
+ ( $i & 4 ) ? $user->getActorId() : null
+ );
+ $this->assertSame( $user->getId(), $test->getId() );
+ $this->assertSame( $user->getName(), $test->getName() );
+ $this->assertSame( $user->getActorId(), $test->getActorId() );
+ }
+
+ // Anon user. Can't load by only user ID when that's 0.
+ $user = User::newFromName( '192.168.12.34', false );
+ $user->getActorId( $this->db ); // Make sure an actor ID exists
+
+ $test = User::newFromAnyId( null, '192.168.12.34', null );
+ $this->assertSame( $user->getId(), $test->getId() );
+ $this->assertSame( $user->getName(), $test->getName() );
+ $this->assertSame( $user->getActorId(), $test->getActorId() );
+ $test = User::newFromAnyId( null, null, $user->getActorId() );
+ $this->assertSame( $user->getId(), $test->getId() );
+ $this->assertSame( $user->getName(), $test->getName() );
+ $this->assertSame( $user->getActorId(), $test->getActorId() );
+
+ // Bogus data should still "work" as long as nothing triggers a ->load(),
+ // and accessing the specified data shouldn't do that.
+ $test = User::newFromAnyId( 123456, 'Bogus', 654321 );
+ $this->assertSame( 123456, $test->getId() );
+ $this->assertSame( 'Bogus', $test->getName() );
+ $this->assertSame( 654321, $test->getActorId() );
+
+ // Exceptional cases
+ try {
+ User::newFromAnyId( null, null, null );
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( InvalidArgumentException $ex ) {
+ }
+ try {
+ User::newFromAnyId( 0, null, 0 );
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( InvalidArgumentException $ex ) {
+ }
+ }
+
+ /**
+ * @covers User::getBlockedStatus
+ * @covers User::getBlock
+ * @covers User::blockedBy
+ * @covers User::blockedFor
+ * @covers User::isHidden
+ * @covers User::isBlockedFrom
+ */
+ public function testBlockInstanceCache() {
+ // First, check the user isn't blocked
+ $user = $this->getMutableTestUser()->getUser();
+ $ut = Title::makeTitle( NS_USER_TALK, $user->getName() );
+ $this->assertNull( $user->getBlock( false ), 'sanity check' );
+ $this->assertSame( '', $user->blockedBy(), 'sanity check' );
+ $this->assertSame( '', $user->blockedFor(), 'sanity check' );
+ $this->assertFalse( (bool)$user->isHidden(), 'sanity check' );
+ $this->assertFalse( $user->isBlockedFrom( $ut ), 'sanity check' );
+
+ // Block the user
+ $blocker = $this->getTestSysop()->getUser();
+ $block = new Block( [
+ 'hideName' => true,
+ 'allowUsertalk' => false,
+ 'reason' => 'Because',
+ ] );
+ $block->setTarget( $user );
+ $block->setBlocker( $blocker );
+ $res = $block->insert();
+ $this->assertTrue( (bool)$res['id'], 'sanity check: Failed to insert block' );
+
+ // Clear cache and confirm it loaded the block properly
+ $user->clearInstanceCache();
+ $this->assertInstanceOf( Block::class, $user->getBlock( false ) );
+ $this->assertSame( $blocker->getName(), $user->blockedBy() );
+ $this->assertSame( 'Because', $user->blockedFor() );
+ $this->assertTrue( (bool)$user->isHidden() );
+ $this->assertTrue( $user->isBlockedFrom( $ut ) );
+
+ // Unblock
+ $block->delete();
+
+ // Clear cache and confirm it loaded the not-blocked properly
+ $user->clearInstanceCache();
+ $this->assertNull( $user->getBlock( false ) );
+ $this->assertSame( '', $user->blockedBy() );
+ $this->assertSame( '', $user->blockedFor() );
+ $this->assertFalse( (bool)$user->isHidden() );
+ $this->assertFalse( $user->isBlockedFrom( $ut ) );
+ }
+
}
diff --git a/www/wiki/tests/phpunit/includes/utils/AvroValidatorTest.php b/www/wiki/tests/phpunit/includes/utils/AvroValidatorTest.php
index 7559e224..cf45f9fd 100644
--- a/www/wiki/tests/phpunit/includes/utils/AvroValidatorTest.php
+++ b/www/wiki/tests/phpunit/includes/utils/AvroValidatorTest.php
@@ -4,12 +4,18 @@
*
* Ported from /t/inc/IP.t by avar.
*
- * @group IP
* @todo Test methods in this call should be split into a method and a
* dataprovider.
*/
-class AvroValidatorTest extends PHPUnit_Framework_TestCase {
+/**
+ * @group IP
+ * @covers AvroValidator
+ */
+class AvroValidatorTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
public function setUp() {
if ( !class_exists( 'AvroSchema' ) ) {
$this->markTestSkipped( 'Avro is required to run the AvroValidatorTest' );
diff --git a/www/wiki/tests/phpunit/includes/utils/BatchRowUpdateTest.php b/www/wiki/tests/phpunit/includes/utils/BatchRowUpdateTest.php
index 017e97d3..52b14339 100644
--- a/www/wiki/tests/phpunit/includes/utils/BatchRowUpdateTest.php
+++ b/www/wiki/tests/phpunit/includes/utils/BatchRowUpdateTest.php
@@ -4,11 +4,15 @@
* Tests for BatchRowUpdate and its components
*
* @group db
+ *
+ * @covers BatchRowUpdate
+ * @covers BatchRowIterator
+ * @covers BatchRowWriter
*/
class BatchRowUpdateTest extends MediaWikiTestCase {
public function testWriterBasicFunctionality() {
- $db = $this->mockDb();
+ $db = $this->mockDb( [ 'update' ] );
$writer = new BatchRowWriter( $db, 'echo_event' );
$updates = [
@@ -32,17 +36,13 @@ class BatchRowUpdateTest extends MediaWikiTestCase {
}
public function testReaderBasicIterate() {
- $db = $this->mockDb();
$batchSize = 2;
- $reader = new BatchRowIterator( $db, 'some_table', 'id_field', $batchSize );
-
$response = $this->genSelectResult( $batchSize, /*numRows*/ 5, function () {
static $i = 0;
return [ 'id_field' => ++$i ];
} );
- $db->expects( $this->exactly( count( $response ) ) )
- ->method( 'select' )
- ->will( $this->consecutivelyReturnFromSelect( $response ) );
+ $db = $this->mockDbConsecutiveSelect( $response );
+ $reader = new BatchRowIterator( $db, 'some_table', 'id_field', $batchSize );
$pos = 0;
foreach ( $reader as $rows ) {
@@ -126,7 +126,7 @@ class BatchRowUpdateTest extends MediaWikiTestCase {
public function testReaderSetFetchColumns(
$message, array $columns, array $primaryKeys, array $fetchColumns
) {
- $db = $this->mockDb();
+ $db = $this->mockDb( [ 'select' ] );
$db->expects( $this->once() )
->method( 'select' )
// only testing second parameter of Database::select
@@ -198,7 +198,7 @@ class BatchRowUpdateTest extends MediaWikiTestCase {
}
protected function mockDbConsecutiveSelect( array $retvals ) {
- $db = $this->mockDb();
+ $db = $this->mockDb( [ 'select', 'addQuotes' ] );
$db->expects( $this->any() )
->method( 'select' )
->will( $this->consecutivelyReturnFromSelect( $retvals ) );
@@ -234,11 +234,12 @@ class BatchRowUpdateTest extends MediaWikiTestCase {
return $res;
}
- protected function mockDb() {
+ protected function mockDb( $methods = [] ) {
// @TODO: mock from Database
// FIXME: the constructor normally sets mAtomicLevels and mSrvCache
- $databaseMysql = $this->getMockBuilder( 'DatabaseMysqli' )
+ $databaseMysql = $this->getMockBuilder( Wikimedia\Rdbms\DatabaseMysqli::class )
->disableOriginalConstructor()
+ ->setMethods( array_merge( [ 'isOpen', 'getApproximateLagStatus' ], $methods ) )
->getMock();
$databaseMysql->expects( $this->any() )
->method( 'isOpen' )
diff --git a/www/wiki/tests/phpunit/includes/utils/ClassCollectorTest.php b/www/wiki/tests/phpunit/includes/utils/ClassCollectorTest.php
index e8a228e3..9e5163f9 100644
--- a/www/wiki/tests/phpunit/includes/utils/ClassCollectorTest.php
+++ b/www/wiki/tests/phpunit/includes/utils/ClassCollectorTest.php
@@ -3,7 +3,9 @@
/**
* @covers ClassCollector
*/
-class ClassCollectorTest extends PHPUnit_Framework_TestCase {
+class ClassCollectorTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
public static function provideCases() {
return [
@@ -32,6 +34,12 @@ class ClassCollectorTest extends PHPUnit_Framework_TestCase {
[ 'Bar' ],
],
[
+ // Namespaced class is not currently supported. Must use namespace declaration
+ // earlier in the file.
+ "class_alias( Example\Foo::class, 'Bar' );",
+ [],
+ ],
+ [
"namespace Example;\nclass Foo {}\nclass_alias( Foo::class, 'Bar' );",
[ 'Example\Foo', 'Bar' ],
],
diff --git a/www/wiki/tests/phpunit/includes/utils/FileContentsHasherTest.php b/www/wiki/tests/phpunit/includes/utils/FileContentsHasherTest.php
index 0ee4c134..316d9f42 100644
--- a/www/wiki/tests/phpunit/includes/utils/FileContentsHasherTest.php
+++ b/www/wiki/tests/phpunit/includes/utils/FileContentsHasherTest.php
@@ -3,7 +3,9 @@
/**
* @covers FileContentsHasherTest
*/
-class FileContentsHasherTest extends PHPUnit_Framework_TestCase {
+class FileContentsHasherTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
public function provideSingleFile() {
return array_map( function ( $file ) {
diff --git a/www/wiki/tests/phpunit/includes/utils/IPTest.php b/www/wiki/tests/phpunit/includes/utils/IPTest.php
deleted file mode 100644
index 5e0626b6..00000000
--- a/www/wiki/tests/phpunit/includes/utils/IPTest.php
+++ /dev/null
@@ -1,670 +0,0 @@
-<?php
-/**
- * Tests for IP validity functions.
- *
- * Ported from /t/inc/IP.t by avar.
- *
- * @group IP
- * @todo Test methods in this call should be split into a method and a
- * dataprovider.
- */
-
-class IPTest extends PHPUnit_Framework_TestCase {
- /**
- * @covers IP::isIPAddress
- * @dataProvider provideInvalidIPs
- */
- public function isNotIPAddress( $val, $desc ) {
- $this->assertFalse( IP::isIPAddress( $val ), $desc );
- }
-
- /**
- * Provide a list of things that aren't IP addresses
- */
- public function provideInvalidIPs() {
- return [
- [ false, 'Boolean false is not an IP' ],
- [ true, 'Boolean true is not an IP' ],
- [ '', 'Empty string is not an IP' ],
- [ 'abc', 'Garbage IP string' ],
- [ ':', 'Single ":" is not an IP' ],
- [ '2001:0DB8::A:1::1', 'IPv6 with a double :: occurrence' ],
- [ '2001:0DB8::A:1::', 'IPv6 with a double :: occurrence, last at end' ],
- [ '::2001:0DB8::5:1', 'IPv6 with a double :: occurrence, firt at beginning' ],
- [ '124.24.52', 'IPv4 not enough quads' ],
- [ '24.324.52.13', 'IPv4 out of range' ],
- [ '.24.52.13', 'IPv4 starts with period' ],
- [ 'fc:100:300', 'IPv6 with only 3 words' ],
- ];
- }
-
- /**
- * @covers IP::isIPAddress
- */
- public function testisIPAddress() {
- $this->assertTrue( IP::isIPAddress( '::' ), 'RFC 4291 IPv6 Unspecified Address' );
- $this->assertTrue( IP::isIPAddress( '::1' ), 'RFC 4291 IPv6 Loopback Address' );
- $this->assertTrue( IP::isIPAddress( '74.24.52.13/20', 'IPv4 range' ) );
- $this->assertTrue( IP::isIPAddress( 'fc:100:a:d:1:e:ac:0/24' ), 'IPv6 range' );
- $this->assertTrue( IP::isIPAddress( 'fc::100:a:d:1:e:ac/96' ), 'IPv6 range with "::"' );
-
- $validIPs = [ 'fc:100::', 'fc:100:a:d:1:e:ac::', 'fc::100', '::fc:100:a:d:1:e:ac',
- '::fc', 'fc::100:a:d:1:e:ac', 'fc:100:a:d:1:e:ac:0', '124.24.52.13', '1.24.52.13' ];
- foreach ( $validIPs as $ip ) {
- $this->assertTrue( IP::isIPAddress( $ip ), "$ip is a valid IP address" );
- }
- }
-
- /**
- * @covers IP::isIPv6
- */
- public function testisIPv6() {
- $this->assertFalse( IP::isIPv6( ':fc:100::' ), 'IPv6 starting with lone ":"' );
- $this->assertFalse( IP::isIPv6( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
- $this->assertFalse( IP::isIPv6( 'fc:300' ), 'IPv6 with only 2 words' );
- $this->assertFalse( IP::isIPv6( 'fc:100:300' ), 'IPv6 with only 3 words' );
-
- $this->assertTrue( IP::isIPv6( 'fc:100::' ) );
- $this->assertTrue( IP::isIPv6( 'fc:100:a::' ) );
- $this->assertTrue( IP::isIPv6( 'fc:100:a:d::' ) );
- $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1::' ) );
- $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e::' ) );
- $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac::' ) );
-
- $this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 with 8 words ending with "::"' );
- $this->assertFalse(
- IP::isIPv6( 'fc:100:a:d:1:e:ac:0:1::' ),
- 'IPv6 with 9 words ending with "::"'
- );
-
- $this->assertFalse( IP::isIPv6( ':::' ) );
- $this->assertFalse( IP::isIPv6( '::0:' ), 'IPv6 ending in a lone ":"' );
-
- $this->assertTrue( IP::isIPv6( '::' ), 'IPv6 zero address' );
- $this->assertTrue( IP::isIPv6( '::0' ) );
- $this->assertTrue( IP::isIPv6( '::fc' ) );
- $this->assertTrue( IP::isIPv6( '::fc:100' ) );
- $this->assertTrue( IP::isIPv6( '::fc:100:a' ) );
- $this->assertTrue( IP::isIPv6( '::fc:100:a:d' ) );
- $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1' ) );
- $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e' ) );
- $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e:ac' ) );
-
- $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' );
- $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' );
-
- $this->assertFalse( IP::isIPv6( ':fc::100' ), 'IPv6 starting with lone ":"' );
- $this->assertFalse( IP::isIPv6( 'fc::100:' ), 'IPv6 ending with lone ":"' );
- $this->assertFalse( IP::isIPv6( 'fc:::100' ), 'IPv6 with ":::" in the middle' );
-
- $this->assertTrue( IP::isIPv6( 'fc::100' ), 'IPv6 with "::" and 2 words' );
- $this->assertTrue( IP::isIPv6( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
- $this->assertTrue( IP::isIPv6( 'fc::100:a:d', 'IPv6 with "::" and 4 words' ) );
- $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' );
- $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e' ), 'IPv6 with "::" and 6 words' );
- $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' );
- $this->assertTrue( IP::isIPv6( '2001::df' ), 'IPv6 with "::" and 2 words' );
- $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' );
- $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' );
-
- $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' );
- $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' );
-
- $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac:0' ) );
- }
-
- /**
- * @covers IP::isIPv4
- * @dataProvider provideInvalidIPv4Addresses
- */
- public function testisNotIPv4( $bogusIP, $desc ) {
- $this->assertFalse( IP::isIPv4( $bogusIP ), $desc );
- }
-
- public function provideInvalidIPv4Addresses() {
- return [
- [ false, 'Boolean false is not an IP' ],
- [ true, 'Boolean true is not an IP' ],
- [ '', 'Empty string is not an IP' ],
- [ 'abc', 'Letters are not an IP' ],
- [ ':', 'A colon is not an IP' ],
- [ '124.24.52', 'IPv4 not enough quads' ],
- [ '24.324.52.13', 'IPv4 out of range' ],
- [ '.24.52.13', 'IPv4 starts with period' ],
- ];
- }
-
- /**
- * @covers IP::isIPv4
- * @dataProvider provideValidIPv4Address
- */
- public function testIsIPv4( $ip, $desc ) {
- $this->assertTrue( IP::isIPv4( $ip ), $desc );
- }
-
- /**
- * Provide some IPv4 addresses and ranges
- */
- public function provideValidIPv4Address() {
- return [
- [ '124.24.52.13', 'Valid IPv4 address' ],
- [ '1.24.52.13', 'Another valid IPv4 address' ],
- [ '74.24.52.13/20', 'An IPv4 range' ],
- ];
- }
-
- /**
- * @covers IP::isValid
- */
- public function testValidIPs() {
- foreach ( range( 0, 255 ) as $i ) {
- $a = sprintf( "%03d", $i );
- $b = sprintf( "%02d", $i );
- $c = sprintf( "%01d", $i );
- foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
- $ip = "$f.$f.$f.$f";
- $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv4 address" );
- }
- }
- foreach ( range( 0x0, 0xFFFF, 0xF ) as $i ) {
- $a = sprintf( "%04x", $i );
- $b = sprintf( "%03x", $i );
- $c = sprintf( "%02x", $i );
- foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
- $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
- $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv6 address" );
- }
- }
- // test with some abbreviations
- $this->assertFalse( IP::isValid( ':fc:100::' ), 'IPv6 starting with lone ":"' );
- $this->assertFalse( IP::isValid( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
- $this->assertFalse( IP::isValid( 'fc:300' ), 'IPv6 with only 2 words' );
- $this->assertFalse( IP::isValid( 'fc:100:300' ), 'IPv6 with only 3 words' );
-
- $this->assertTrue( IP::isValid( 'fc:100::' ) );
- $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e::' ) );
- $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e:ac::' ) );
-
- $this->assertTrue( IP::isValid( 'fc::100' ), 'IPv6 with "::" and 2 words' );
- $this->assertTrue( IP::isValid( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
- $this->assertTrue( IP::isValid( '2001::df' ), 'IPv6 with "::" and 2 words' );
- $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' );
- $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' );
- $this->assertTrue( IP::isValid( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' );
- $this->assertTrue( IP::isValid( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' );
-
- $this->assertFalse(
- IP::isValid( 'fc:100:a:d:1:e:ac:0::' ),
- 'IPv6 with 8 words ending with "::"'
- );
- $this->assertFalse(
- IP::isValid( 'fc:100:a:d:1:e:ac:0:1::' ),
- 'IPv6 with 9 words ending with "::"'
- );
- }
-
- /**
- * @covers IP::isValid
- */
- public function testInvalidIPs() {
- // Out of range...
- foreach ( range( 256, 999 ) as $i ) {
- $a = sprintf( "%03d", $i );
- $b = sprintf( "%02d", $i );
- $c = sprintf( "%01d", $i );
- foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
- $ip = "$f.$f.$f.$f";
- $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv4 address" );
- }
- }
- foreach ( range( 'g', 'z' ) as $i ) {
- $a = sprintf( "%04s", $i );
- $b = sprintf( "%03s", $i );
- $c = sprintf( "%02s", $i );
- foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
- $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
- $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv6 address" );
- }
- }
- // Have CIDR
- $ipCIDRs = [
- '212.35.31.121/32',
- '212.35.31.121/18',
- '212.35.31.121/24',
- '::ff:d:321:5/96',
- 'ff::d3:321:5/116',
- 'c:ff:12:1:ea:d:321:5/120',
- ];
- foreach ( $ipCIDRs as $i ) {
- $this->assertFalse( IP::isValid( $i ),
- "$i is an invalid IP address because it is a block" );
- }
- // Incomplete/garbage
- $invalid = [
- 'www.xn--var-xla.net',
- '216.17.184.G',
- '216.17.184.1.',
- '216.17.184',
- '216.17.184.',
- '256.17.184.1'
- ];
- foreach ( $invalid as $i ) {
- $this->assertFalse( IP::isValid( $i ), "$i is an invalid IP address" );
- }
- }
-
- /**
- * Provide some valid IP blocks
- */
- public function provideValidBlocks() {
- return [
- [ '116.17.184.5/32' ],
- [ '0.17.184.5/30' ],
- [ '16.17.184.1/24' ],
- [ '30.242.52.14/1' ],
- [ '10.232.52.13/8' ],
- [ '30.242.52.14/0' ],
- [ '::e:f:2001/96' ],
- [ '::c:f:2001/128' ],
- [ '::10:f:2001/70' ],
- [ '::fe:f:2001/1' ],
- [ '::6d:f:2001/8' ],
- [ '::fe:f:2001/0' ],
- ];
- }
-
- /**
- * @covers IP::isValidBlock
- * @dataProvider provideValidBlocks
- */
- public function testValidBlocks( $block ) {
- $this->assertTrue( IP::isValidBlock( $block ), "$block is a valid IP block" );
- }
-
- /**
- * @covers IP::isValidBlock
- * @dataProvider provideInvalidBlocks
- */
- public function testInvalidBlocks( $invalid ) {
- $this->assertFalse( IP::isValidBlock( $invalid ), "$invalid is not a valid IP block" );
- }
-
- public function provideInvalidBlocks() {
- return [
- [ '116.17.184.5/33' ],
- [ '0.17.184.5/130' ],
- [ '16.17.184.1/-1' ],
- [ '10.232.52.13/*' ],
- [ '7.232.52.13/ab' ],
- [ '11.232.52.13/' ],
- [ '::e:f:2001/129' ],
- [ '::c:f:2001/228' ],
- [ '::10:f:2001/-1' ],
- [ '::6d:f:2001/*' ],
- [ '::86:f:2001/ab' ],
- [ '::23:f:2001/' ],
- ];
- }
-
- /**
- * @covers IP::sanitizeIP
- * @dataProvider provideSanitizeIP
- */
- public function testSanitizeIP( $expected, $input ) {
- $result = IP::sanitizeIP( $input );
- $this->assertEquals( $expected, $result );
- }
-
- /**
- * Provider for IP::testSanitizeIP()
- */
- public static function provideSanitizeIP() {
- return [
- [ '0.0.0.0', '0.0.0.0' ],
- [ '0.0.0.0', '00.00.00.00' ],
- [ '0.0.0.0', '000.000.000.000' ],
- [ '141.0.11.253', '141.000.011.253' ],
- [ '1.2.4.5', '1.2.4.5' ],
- [ '1.2.4.5', '01.02.04.05' ],
- [ '1.2.4.5', '001.002.004.005' ],
- [ '10.0.0.1', '010.0.000.1' ],
- [ '80.72.250.4', '080.072.250.04' ],
- [ 'Foo.1000.00', 'Foo.1000.00' ],
- [ 'Bar.01', 'Bar.01' ],
- [ 'Bar.010', 'Bar.010' ],
- [ null, '' ],
- [ null, ' ' ]
- ];
- }
-
- /**
- * @covers IP::toHex
- * @dataProvider provideToHex
- */
- public function testToHex( $expected, $input ) {
- $result = IP::toHex( $input );
- $this->assertTrue( $result === false || is_string( $result ) );
- $this->assertEquals( $expected, $result );
- }
-
- /**
- * Provider for IP::testToHex()
- */
- public static function provideToHex() {
- return [
- [ '00000001', '0.0.0.1' ],
- [ '01020304', '1.2.3.4' ],
- [ '7F000001', '127.0.0.1' ],
- [ '80000000', '128.0.0.0' ],
- [ 'DEADCAFE', '222.173.202.254' ],
- [ 'FFFFFFFF', '255.255.255.255' ],
- [ '8D000BFD', '141.000.11.253' ],
- [ false, 'IN.VA.LI.D' ],
- [ 'v6-00000000000000000000000000000001', '::1' ],
- [ 'v6-20010DB885A3000000008A2E03707334', '2001:0db8:85a3:0000:0000:8a2e:0370:7334' ],
- [ 'v6-20010DB885A3000000008A2E03707334', '2001:db8:85a3::8a2e:0370:7334' ],
- [ false, 'IN:VA::LI:D' ],
- [ false, ':::1' ]
- ];
- }
-
- /**
- * @covers IP::isPublic
- * @dataProvider provideIsPublic
- */
- public function testIsPublic( $expected, $input ) {
- $result = IP::isPublic( $input );
- $this->assertEquals( $expected, $result );
- }
-
- /**
- * Provider for IP::testIsPublic()
- */
- public static function provideIsPublic() {
- return [
- [ false, 'fc00::3' ], # RFC 4193 (local)
- [ false, 'fc00::ff' ], # RFC 4193 (local)
- [ false, '127.1.2.3' ], # loopback
- [ false, '::1' ], # loopback
- [ false, 'fe80::1' ], # link-local
- [ false, '169.254.1.1' ], # link-local
- [ false, '10.0.0.1' ], # RFC 1918 (private)
- [ false, '172.16.0.1' ], # RFC 1918 (private)
- [ false, '192.168.0.1' ], # RFC 1918 (private)
- [ true, '2001:5c0:1000:a::133' ], # public
- [ true, 'fc::3' ], # public
- [ true, '00FC::' ] # public
- ];
- }
-
- // Private wrapper used to test CIDR Parsing.
- private function assertFalseCIDR( $CIDR, $msg = '' ) {
- $ff = [ false, false ];
- $this->assertEquals( $ff, IP::parseCIDR( $CIDR ), $msg );
- }
-
- // Private wrapper to test network shifting using only dot notation
- private function assertNet( $expected, $CIDR ) {
- $parse = IP::parseCIDR( $CIDR );
- $this->assertEquals( $expected, long2ip( $parse[0] ), "network shifting $CIDR" );
- }
-
- /**
- * @covers IP::hexToQuad
- * @dataProvider provideIPsAndHexes
- */
- public function testHexToQuad( $ip, $hex ) {
- $this->assertEquals( $ip, IP::hexToQuad( $hex ) );
- }
-
- /**
- * Provide some IP addresses and their equivalent hex representations
- */
- public function provideIPsandHexes() {
- return [
- [ '0.0.0.1', '00000001' ],
- [ '255.0.0.0', 'FF000000' ],
- [ '255.255.255.255', 'FFFFFFFF' ],
- [ '10.188.222.255', '0ABCDEFF' ],
- // hex not left-padded...
- [ '0.0.0.0', '0' ],
- [ '0.0.0.1', '1' ],
- [ '0.0.0.255', 'FF' ],
- [ '0.0.255.0', 'FF00' ],
- ];
- }
-
- /**
- * @covers IP::hexToOctet
- * @dataProvider provideOctetsAndHexes
- */
- public function testHexToOctet( $octet, $hex ) {
- $this->assertEquals( $octet, IP::hexToOctet( $hex ) );
- }
-
- /**
- * Provide some hex and octet representations of the same IPs
- */
- public function provideOctetsAndHexes() {
- return [
- [ '0:0:0:0:0:0:0:1', '00000000000000000000000000000001' ],
- [ '0:0:0:0:0:0:FF:3', '00000000000000000000000000FF0003' ],
- [ '0:0:0:0:0:0:FF00:6', '000000000000000000000000FF000006' ],
- [ '0:0:0:0:0:0:FCCF:FAFF', '000000000000000000000000FCCFFAFF' ],
- [ 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ],
- // hex not left-padded...
- [ '0:0:0:0:0:0:0:0', '0' ],
- [ '0:0:0:0:0:0:0:1', '1' ],
- [ '0:0:0:0:0:0:0:FF', 'FF' ],
- [ '0:0:0:0:0:0:0:FFD0', 'FFD0' ],
- [ '0:0:0:0:0:0:FA00:0', 'FA000000' ],
- [ '0:0:0:0:0:0:FCCF:FAFF', 'FCCFFAFF' ],
- ];
- }
-
- /**
- * IP::parseCIDR() returns an array containing a signed IP address
- * representing the network mask and the bit mask.
- * @covers IP::parseCIDR
- */
- public function testCIDRParsing() {
- $this->assertFalseCIDR( '192.0.2.0', "missing mask" );
- $this->assertFalseCIDR( '192.0.2.0/', "missing bitmask" );
-
- // Verify if statement
- $this->assertFalseCIDR( '256.0.0.0/32', "invalid net" );
- $this->assertFalseCIDR( '192.0.2.0/AA', "mask not numeric" );
- $this->assertFalseCIDR( '192.0.2.0/-1', "mask < 0" );
- $this->assertFalseCIDR( '192.0.2.0/33', "mask > 32" );
-
- // Check internal logic
- # 0 mask always result in array(0,0)
- $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '192.0.0.2/0' ) );
- $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '0.0.0.0/0' ) );
- $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '255.255.255.255/0' ) );
-
- // @todo FIXME: Add more tests.
-
- # This part test network shifting
- $this->assertNet( '192.0.0.0', '192.0.0.2/24' );
- $this->assertNet( '192.168.5.0', '192.168.5.13/24' );
- $this->assertNet( '10.0.0.160', '10.0.0.161/28' );
- $this->assertNet( '10.0.0.0', '10.0.0.3/28' );
- $this->assertNet( '10.0.0.0', '10.0.0.3/30' );
- $this->assertNet( '10.0.0.4', '10.0.0.4/30' );
- $this->assertNet( '172.17.32.0', '172.17.35.48/21' );
- $this->assertNet( '10.128.0.0', '10.135.0.0/9' );
- $this->assertNet( '134.0.0.0', '134.0.5.1/8' );
- }
-
- /**
- * @covers IP::canonicalize
- */
- public function testIPCanonicalizeOnValidIp() {
- $this->assertEquals( '192.0.2.152', IP::canonicalize( '192.0.2.152' ),
- 'Canonicalization of a valid IP returns it unchanged' );
- }
-
- /**
- * @covers IP::canonicalize
- */
- public function testIPCanonicalizeMappedAddress() {
- $this->assertEquals(
- '192.0.2.152',
- IP::canonicalize( '::ffff:192.0.2.152' )
- );
- $this->assertEquals(
- '192.0.2.152',
- IP::canonicalize( '::192.0.2.152' )
- );
- }
-
- /**
- * Issues there are most probably from IP::toHex() or IP::parseRange()
- * @covers IP::isInRange
- * @dataProvider provideIPsAndRanges
- */
- public function testIPIsInRange( $expected, $addr, $range, $message = '' ) {
- $this->assertEquals(
- $expected,
- IP::isInRange( $addr, $range ),
- $message
- );
- }
-
- /** Provider for testIPIsInRange() */
- public static function provideIPsAndRanges() {
- # Format: (expected boolean, address, range, optional message)
- return [
- # IPv4
- [ true, '192.0.2.0', '192.0.2.0/24', 'Network address' ],
- [ true, '192.0.2.77', '192.0.2.0/24', 'Simple address' ],
- [ true, '192.0.2.255', '192.0.2.0/24', 'Broadcast address' ],
-
- [ false, '0.0.0.0', '192.0.2.0/24' ],
- [ false, '255.255.255', '192.0.2.0/24' ],
-
- # IPv6
- [ false, '::1', '2001:DB8::/32' ],
- [ false, '::', '2001:DB8::/32' ],
- [ false, 'FE80::1', '2001:DB8::/32' ],
-
- [ true, '2001:DB8::', '2001:DB8::/32' ],
- [ true, '2001:0DB8::', '2001:DB8::/32' ],
- [ true, '2001:DB8::1', '2001:DB8::/32' ],
- [ true, '2001:0DB8::1', '2001:DB8::/32' ],
- [ true, '2001:0DB8:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF',
- '2001:DB8::/32' ],
-
- [ false, '2001:0DB8:F::', '2001:DB8::/96' ],
- ];
- }
-
- /**
- * Test for IP::splitHostAndPort().
- * @dataProvider provideSplitHostAndPort
- */
- public function testSplitHostAndPort( $expected, $input, $description ) {
- $this->assertEquals( $expected, IP::splitHostAndPort( $input ), $description );
- }
-
- /**
- * Provider for IP::splitHostAndPort()
- */
- public static function provideSplitHostAndPort() {
- return [
- [ false, '[', 'Unclosed square bracket' ],
- [ false, '[::', 'Unclosed square bracket 2' ],
- [ [ '::', false ], '::', 'Bare IPv6 0' ],
- [ [ '::1', false ], '::1', 'Bare IPv6 1' ],
- [ [ '::', false ], '[::]', 'Bracketed IPv6 0' ],
- [ [ '::1', false ], '[::1]', 'Bracketed IPv6 1' ],
- [ [ '::1', 80 ], '[::1]:80', 'Bracketed IPv6 with port' ],
- [ false, '::x', 'Double colon but no IPv6' ],
- [ [ 'x', 80 ], 'x:80', 'Hostname and port' ],
- [ false, 'x:x', 'Hostname and invalid port' ],
- [ [ 'x', false ], 'x', 'Plain hostname' ]
- ];
- }
-
- /**
- * Test for IP::combineHostAndPort()
- * @dataProvider provideCombineHostAndPort
- */
- public function testCombineHostAndPort( $expected, $input, $description ) {
- list( $host, $port, $defaultPort ) = $input;
- $this->assertEquals(
- $expected,
- IP::combineHostAndPort( $host, $port, $defaultPort ),
- $description );
- }
-
- /**
- * Provider for IP::combineHostAndPort()
- */
- public static function provideCombineHostAndPort() {
- return [
- [ '[::1]', [ '::1', 2, 2 ], 'IPv6 default port' ],
- [ '[::1]:2', [ '::1', 2, 3 ], 'IPv6 non-default port' ],
- [ 'x', [ 'x', 2, 2 ], 'Normal default port' ],
- [ 'x:2', [ 'x', 2, 3 ], 'Normal non-default port' ],
- ];
- }
-
- /**
- * Test for IP::sanitizeRange()
- * @dataProvider provideIPCIDRs
- */
- public function testSanitizeRange( $input, $expected, $description ) {
- $this->assertEquals( $expected, IP::sanitizeRange( $input ), $description );
- }
-
- /**
- * Provider for IP::testSanitizeRange()
- */
- public static function provideIPCIDRs() {
- return [
- [ '35.56.31.252/16', '35.56.0.0/16', 'IPv4 range' ],
- [ '135.16.21.252/24', '135.16.21.0/24', 'IPv4 range' ],
- [ '5.36.71.252/32', '5.36.71.252/32', 'IPv4 silly range' ],
- [ '5.36.71.252', '5.36.71.252', 'IPv4 non-range' ],
- [ '0:1:2:3:4:c5:f6:7/96', '0:1:2:3:4:C5:0:0/96', 'IPv6 range' ],
- [ '0:1:2:3:4:5:6:7/120', '0:1:2:3:4:5:6:0/120', 'IPv6 range' ],
- [ '0:e1:2:3:4:5:e6:7/128', '0:E1:2:3:4:5:E6:7/128', 'IPv6 silly range' ],
- [ '0:c1:A2:3:4:5:c6:7', '0:C1:A2:3:4:5:C6:7', 'IPv6 non range' ],
- ];
- }
-
- /**
- * Test for IP::prettifyIP()
- * @dataProvider provideIPsToPrettify
- */
- public function testPrettifyIP( $ip, $prettified ) {
- $this->assertEquals( $prettified, IP::prettifyIP( $ip ), "Prettify of $ip" );
- }
-
- /**
- * Provider for IP::testPrettifyIP()
- */
- public static function provideIPsToPrettify() {
- return [
- [ '0:0:0:0:0:0:0:0', '::' ],
- [ '0:0:0::0:0:0', '::' ],
- [ '0:0:0:1:0:0:0:0', '0:0:0:1::' ],
- [ '0:0::f', '::f' ],
- [ '0::0:0:0:33:fef:b', '::33:fef:b' ],
- [ '3f:535:0:0:0:0:e:fbb', '3f:535::e:fbb' ],
- [ '0:0:fef:0:0:0:e:fbb', '0:0:fef::e:fbb' ],
- [ 'abbc:2004::0:0:0:0', 'abbc:2004::' ],
- [ 'cebc:2004:f:0:0:0:0:0', 'cebc:2004:f::' ],
- [ '0:0:0:0:0:0:0:0/16', '::/16' ],
- [ '0:0:0::0:0:0/64', '::/64' ],
- [ '0:0::f/52', '::f/52' ],
- [ '::0:0:33:fef:b/52', '::33:fef:b/52' ],
- [ '3f:535:0:0:0:0:e:fbb/48', '3f:535::e:fbb/48' ],
- [ '0:0:fef:0:0:0:e:fbb/96', '0:0:fef::e:fbb/96' ],
- [ 'abbc:2004:0:0::0:0/40', 'abbc:2004::/40' ],
- [ 'aebc:2004:f:0:0:0:0:0/80', 'aebc:2004:f::/80' ],
- ];
- }
-}
diff --git a/www/wiki/tests/phpunit/includes/utils/MWCryptHKDFTest.php b/www/wiki/tests/phpunit/includes/utils/MWCryptHKDFTest.php
index 86c19ae4..05a33c5a 100644
--- a/www/wiki/tests/phpunit/includes/utils/MWCryptHKDFTest.php
+++ b/www/wiki/tests/phpunit/includes/utils/MWCryptHKDFTest.php
@@ -1,9 +1,9 @@
<?php
/**
- *
* @group HKDF
+ * @covers CryptHKDF
+ * @covers MWCryptHKDF
*/
-
class MWCryptHKDFTest extends MediaWikiTestCase {
protected function setUp() {
@@ -40,7 +40,7 @@ class MWCryptHKDFTest extends MediaWikiTestCase {
* Test vectors from Appendix A on https://tools.ietf.org/html/rfc5869
*/
public static function providerRfc5869() {
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:disable Generic.Files.LineLength
return [
// A.1
[
@@ -93,6 +93,6 @@ class MWCryptHKDFTest extends MediaWikiTestCase {
'0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e927336d0441f4c4300e2cff0d0900b52d3b4' // okm
],
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
}
diff --git a/www/wiki/tests/phpunit/includes/utils/MWCryptHashTest.php b/www/wiki/tests/phpunit/includes/utils/MWCryptHashTest.php
index 905d14ca..94705bff 100644
--- a/www/wiki/tests/phpunit/includes/utils/MWCryptHashTest.php
+++ b/www/wiki/tests/phpunit/includes/utils/MWCryptHashTest.php
@@ -1,10 +1,13 @@
<?php
+
/**
- *
* @group Hash
+ *
+ * @covers MWCryptHash
*/
+class MWCryptHashTest extends PHPUnit\Framework\TestCase {
-class MWCryptHashTest extends PHPUnit_Framework_TestCase {
+ use MediaWikiCoversValidator;
public function testHashLength() {
if ( MWCryptHash::hashAlgo() !== 'whirlpool' ) {
@@ -21,9 +24,8 @@ class MWCryptHashTest extends PHPUnit_Framework_TestCase {
}
$data = 'foobar';
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:ignore Generic.Files.LineLength
$hash = '9923afaec3a86f865bb231a588f453f84e8151a2deb4109aebc6de4284be5bebcff4fab82a7e51d920237340a043736e9d13bab196006dcca0fe65314d68eab9';
- // @codingStandardsIgnoreEnd
$this->assertEquals(
hex2bin( $hash ),
@@ -44,9 +46,8 @@ class MWCryptHashTest extends PHPUnit_Framework_TestCase {
$data = 'foobar';
$key = 'secret';
- // @codingStandardsIgnoreStart Generic.Files.LineLength
+ // phpcs:ignore Generic.Files.LineLength
$hash = 'ddc94177b2020e55ce2049199fd9cc6327f416ff6dc621cc34cb43d9bec61d73372b4790c0e24957f565ecaf2d42821e6303619093e99cbe14a3b9250bda5f81';
- // @codingStandardsIgnoreEnd
$this->assertEquals(
hex2bin( $hash ),
diff --git a/www/wiki/tests/phpunit/includes/utils/MWRestrictionsTest.php b/www/wiki/tests/phpunit/includes/utils/MWRestrictionsTest.php
index f2ea489b..abdfbb14 100644
--- a/www/wiki/tests/phpunit/includes/utils/MWRestrictionsTest.php
+++ b/www/wiki/tests/phpunit/includes/utils/MWRestrictionsTest.php
@@ -1,5 +1,7 @@
<?php
-class MWRestrictionsTest extends PHPUnit_Framework_TestCase {
+class MWRestrictionsTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
protected static $restrictionsForChecks;
@@ -19,7 +21,7 @@ class MWRestrictionsTest extends PHPUnit_Framework_TestCase {
*/
public function testNewDefault() {
$ret = MWRestrictions::newDefault();
- $this->assertInstanceOf( 'MWRestrictions', $ret );
+ $this->assertInstanceOf( MWRestrictions::class, $ret );
$this->assertSame(
'{"IPAddresses":["0.0.0.0/0","::/0"]}',
$ret->toJson()
@@ -39,7 +41,7 @@ class MWRestrictionsTest extends PHPUnit_Framework_TestCase {
public function testArray( $data, $expect ) {
if ( $expect === true ) {
$ret = MWRestrictions::newFromArray( $data );
- $this->assertInstanceOf( 'MWRestrictions', $ret );
+ $this->assertInstanceOf( MWRestrictions::class, $ret );
$this->assertSame( $data, $ret->toArray() );
} else {
try {
@@ -87,7 +89,7 @@ class MWRestrictionsTest extends PHPUnit_Framework_TestCase {
public function testJson( $json, $expect ) {
if ( is_array( $expect ) ) {
$ret = MWRestrictions::newFromJson( $json );
- $this->assertInstanceOf( 'MWRestrictions', $ret );
+ $this->assertInstanceOf( MWRestrictions::class, $ret );
$this->assertSame( $expect, $ret->toArray() );
$this->assertSame( $json, $ret->toJson( false ) );
@@ -178,7 +180,7 @@ class MWRestrictionsTest extends PHPUnit_Framework_TestCase {
public function provideCheck() {
$ret = [];
- $mockBuilder = $this->getMockBuilder( 'FauxRequest' )
+ $mockBuilder = $this->getMockBuilder( FauxRequest::class )
->setMethods( [ 'getIP' ] );
foreach ( self::provideCheckIP() as $checkIP ) {
diff --git a/www/wiki/tests/phpunit/includes/utils/UIDGeneratorTest.php b/www/wiki/tests/phpunit/includes/utils/UIDGeneratorTest.php
index 8b54b722..d335a93a 100644
--- a/www/wiki/tests/phpunit/includes/utils/UIDGeneratorTest.php
+++ b/www/wiki/tests/phpunit/includes/utils/UIDGeneratorTest.php
@@ -1,6 +1,8 @@
<?php
-class UIDGeneratorTest extends PHPUnit_Framework_TestCase {
+class UIDGeneratorTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
protected function tearDown() {
// Bug: 44850
@@ -16,7 +18,7 @@ class UIDGeneratorTest extends PHPUnit_Framework_TestCase {
* @covers UIDGenerator::newTimestampedUID88
*/
public function testTimestampedUID( $method, $digitlen, $bits, $tbits, $hostbits ) {
- $id = call_user_func( [ 'UIDGenerator', $method ] );
+ $id = call_user_func( [ UIDGenerator::class, $method ] );
$this->assertEquals( true, ctype_digit( $id ), "UID made of digit characters" );
$this->assertLessThanOrEqual( $digitlen, strlen( $id ),
"UID has the right number of digits" );
@@ -25,7 +27,7 @@ class UIDGeneratorTest extends PHPUnit_Framework_TestCase {
$ids = [];
for ( $i = 0; $i < 300; $i++ ) {
- $ids[] = call_user_func( [ 'UIDGenerator', $method ] );
+ $ids[] = call_user_func( [ UIDGenerator::class, $method ] );
}
$lastId = array_shift( $ids );
diff --git a/www/wiki/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php b/www/wiki/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php
index 7e74d960..9f18e5af 100644
--- a/www/wiki/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php
+++ b/www/wiki/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php
@@ -4,7 +4,10 @@
* @covers ZipDirectoryReader
* NOTE: this test is more like an integration test than a unit test
*/
-class ZipDirectoryReaderTest extends PHPUnit_Framework_TestCase {
+class ZipDirectoryReaderTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
protected $zipDir;
protected $entries;
diff --git a/www/wiki/tests/phpunit/includes/watcheditem/NoWriteWatchedItemStoreUnitTest.php b/www/wiki/tests/phpunit/includes/watcheditem/NoWriteWatchedItemStoreUnitTest.php
new file mode 100644
index 00000000..a8761e39
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/watcheditem/NoWriteWatchedItemStoreUnitTest.php
@@ -0,0 +1,246 @@
+<?php
+
+/**
+ * @author Addshore
+ *
+ * @covers NoWriteWatchedItemStore
+ */
+class NoWriteWatchedItemStoreUnitTest extends MediaWikiTestCase {
+
+ public function testAddWatch() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->never() )->method( 'addWatch' );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $this->setExpectedException( DBReadOnlyError::class );
+ $noWriteService->addWatch( $this->getTestSysop()->getUser(), new TitleValue( 0, 'Foo' ) );
+ }
+
+ public function testAddWatchBatchForUser() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->never() )->method( 'addWatchBatchForUser' );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $this->setExpectedException( DBReadOnlyError::class );
+ $noWriteService->addWatchBatchForUser( $this->getTestSysop()->getUser(), [] );
+ }
+
+ public function testRemoveWatch() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->never() )->method( 'removeWatch' );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $this->setExpectedException( DBReadOnlyError::class );
+ $noWriteService->removeWatch( $this->getTestSysop()->getUser(), new TitleValue( 0, 'Foo' ) );
+ }
+
+ public function testSetNotificationTimestampsForUser() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->never() )->method( 'setNotificationTimestampsForUser' );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $this->setExpectedException( DBReadOnlyError::class );
+ $noWriteService->setNotificationTimestampsForUser(
+ $this->getTestSysop()->getUser(),
+ 'timestamp',
+ []
+ );
+ }
+
+ public function testUpdateNotificationTimestamp() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->never() )->method( 'updateNotificationTimestamp' );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $this->setExpectedException( DBReadOnlyError::class );
+ $noWriteService->updateNotificationTimestamp(
+ $this->getTestSysop()->getUser(),
+ new TitleValue( 0, 'Foo' ),
+ 'timestamp'
+ );
+ }
+
+ public function testResetNotificationTimestamp() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->never() )->method( 'resetNotificationTimestamp' );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $this->setExpectedException( DBReadOnlyError::class );
+ $noWriteService->resetNotificationTimestamp(
+ $this->getTestSysop()->getUser(),
+ Title::newFromText( 'Foo' )
+ );
+ }
+
+ public function testCountWatchedItems() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )->method( 'countWatchedItems' )->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->countWatchedItems(
+ $this->getTestSysop()->getUser()
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testCountWatchers() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )->method( 'countWatchers' )->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->countWatchers(
+ new TitleValue( 0, 'Foo' )
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testCountVisitingWatchers() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )
+ ->method( 'countVisitingWatchers' )
+ ->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->countVisitingWatchers(
+ new TitleValue( 0, 'Foo' ),
+ 9
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testCountWatchersMultiple() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )
+ ->method( 'countVisitingWatchersMultiple' )
+ ->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->countWatchersMultiple(
+ [ new TitleValue( 0, 'Foo' ) ],
+ []
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testCountVisitingWatchersMultiple() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )
+ ->method( 'countVisitingWatchersMultiple' )
+ ->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->countVisitingWatchersMultiple(
+ [ [ new TitleValue( 0, 'Foo' ), 99 ] ],
+ 11
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testGetWatchedItem() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )->method( 'getWatchedItem' )->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->getWatchedItem(
+ $this->getTestSysop()->getUser(),
+ new TitleValue( 0, 'Foo' )
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testLoadWatchedItem() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )->method( 'loadWatchedItem' )->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->loadWatchedItem(
+ $this->getTestSysop()->getUser(),
+ new TitleValue( 0, 'Foo' )
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testGetWatchedItemsForUser() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )
+ ->method( 'getWatchedItemsForUser' )
+ ->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->getWatchedItemsForUser(
+ $this->getTestSysop()->getUser(),
+ []
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testIsWatched() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )->method( 'isWatched' )->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->isWatched(
+ $this->getTestSysop()->getUser(),
+ new TitleValue( 0, 'Foo' )
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testGetNotificationTimestampsBatch() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )
+ ->method( 'getNotificationTimestampsBatch' )
+ ->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->getNotificationTimestampsBatch(
+ $this->getTestSysop()->getUser(),
+ [ new TitleValue( 0, 'Foo' ) ]
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testCountUnreadNotifications() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )
+ ->method( 'countUnreadNotifications' )
+ ->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->countUnreadNotifications(
+ $this->getTestSysop()->getUser(),
+ 88
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testDuplicateAllAssociatedEntries() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $this->setExpectedException( DBReadOnlyError::class );
+ $noWriteService->duplicateAllAssociatedEntries(
+ new TitleValue( 0, 'Foo' ),
+ new TitleValue( 0, 'Bar' )
+ );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/WatchedItemQueryServiceUnitTest.php b/www/wiki/tests/phpunit/includes/watcheditem/WatchedItemQueryServiceUnitTest.php
index 62ba5f68..50e6c202 100644
--- a/www/wiki/tests/phpunit/includes/WatchedItemQueryServiceUnitTest.php
+++ b/www/wiki/tests/phpunit/includes/watcheditem/WatchedItemQueryServiceUnitTest.php
@@ -1,12 +1,80 @@
<?php
-use Wikimedia\ScopedCallback;
+use Wikimedia\Rdbms\LoadBalancer;
use Wikimedia\TestingAccessWrapper;
/**
* @covers WatchedItemQueryService
*/
-class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
+class WatchedItemQueryServiceUnitTest extends MediaWikiTestCase {
+
+ use MediaWikiCoversValidator;
+
+ /**
+ * @return PHPUnit_Framework_MockObject_MockObject|CommentStore
+ */
+ private function getMockCommentStore() {
+ $mockStore = $this->getMockBuilder( CommentStore::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mockStore->expects( $this->any() )
+ ->method( 'getFields' )
+ ->willReturn( [ 'commentstore' => 'fields' ] );
+ $mockStore->expects( $this->any() )
+ ->method( 'getJoin' )
+ ->willReturn( [
+ 'tables' => [ 'commentstore' => 'table' ],
+ 'fields' => [ 'commentstore' => 'field' ],
+ 'joins' => [ 'commentstore' => 'join' ],
+ ] );
+ return $mockStore;
+ }
+
+ /**
+ * @return PHPUnit_Framework_MockObject_MockObject|ActorMigration
+ */
+ private function getMockActorMigration() {
+ $mockStore = $this->getMockBuilder( ActorMigration::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mockStore->expects( $this->any() )
+ ->method( 'getJoin' )
+ ->willReturn( [
+ 'tables' => [ 'actormigration' => 'table' ],
+ 'fields' => [
+ 'rc_user' => 'actormigration_user',
+ 'rc_user_text' => 'actormigration_user_text',
+ 'rc_actor' => 'actormigration_actor',
+ ],
+ 'joins' => [ 'actormigration' => 'join' ],
+ ] );
+ $mockStore->expects( $this->any() )
+ ->method( 'getWhere' )
+ ->willReturn( [
+ 'tables' => [ 'actormigration' => 'table' ],
+ 'conds' => 'actormigration_conds',
+ 'joins' => [ 'actormigration' => 'join' ],
+ ] );
+ $mockStore->expects( $this->any() )
+ ->method( 'isAnon' )
+ ->willReturn( 'actormigration is anon' );
+ $mockStore->expects( $this->any() )
+ ->method( 'isNotAnon' )
+ ->willReturn( 'actormigration is not anon' );
+ return $mockStore;
+ }
+
+ /**
+ * @param PHPUnit_Framework_MockObject_MockObject|Database $mockDb
+ * @return WatchedItemQueryService
+ */
+ private function newService( $mockDb ) {
+ return new WatchedItemQueryService(
+ $this->getMockLoadBalancer( $mockDb ),
+ $this->getMockCommentStore(),
+ $this->getMockActorMigration()
+ );
+ }
/**
* @return PHPUnit_Framework_MockObject_MockObject|Database
@@ -24,10 +92,17 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
)
->will( $this->returnCallback( function ( $a, $conj ) {
$sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
- return join( $sqlConj, array_map( function ( $s ) {
- return '(' . $s . ')';
- }, $a
- ) );
+ $conds = [];
+ foreach ( $a as $k => $v ) {
+ if ( is_int( $k ) ) {
+ $conds[] = "($v)";
+ } elseif ( is_array( $v ) ) {
+ $conds[] = "($k IN ('" . implode( "','", $v ) . "'))";
+ } else {
+ $conds[] = "($k = '$v')";
+ }
+ }
+ return implode( $sqlConj, $conds );
} ) );
$mock->expects( $this->any() )
@@ -230,7 +305,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
] ),
] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
$startFrom = null;
@@ -390,7 +465,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
$startFrom = [ '20160203123456', 42 ];
} ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
TestingAccessWrapper::newFromObject( $queryService )->extensions = [ $mockExtension ];
$startFrom = null;
@@ -457,76 +532,29 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
[
[ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER ] ],
null,
- [],
- [ 'rc_user_text' ],
- [],
+ [ 'actormigration' => 'table' ],
+ [ 'rc_user_text' => 'actormigration_user_text' ],
[],
[],
+ [ 'actormigration' => 'join' ],
],
[
[ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER_ID ] ],
null,
- [],
- [ 'rc_user' ],
- [],
- [],
- [],
- ],
- [
- [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_COMMENT ] ],
- null,
- [],
- [
- 'rc_comment_text' => 'rc_comment',
- 'rc_comment_data' => 'NULL',
- 'rc_comment_cid' => 'NULL',
- ],
- [],
- [],
- [],
- [ 'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD ],
- ],
- [
- [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_COMMENT ] ],
- null,
- [ 'comment_rc_comment' => 'comment' ],
- [
- 'rc_comment_text' => 'COALESCE( comment_rc_comment.comment_text, rc_comment )',
- 'rc_comment_data' => 'comment_rc_comment.comment_data',
- 'rc_comment_cid' => 'comment_rc_comment.comment_id',
- ],
- [],
- [],
- [ 'comment_rc_comment' => [ 'LEFT JOIN', 'comment_rc_comment.comment_id = rc_comment_id' ] ],
- [ 'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH ],
- ],
- [
- [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_COMMENT ] ],
- null,
- [ 'comment_rc_comment' => 'comment' ],
- [
- 'rc_comment_text' => 'COALESCE( comment_rc_comment.comment_text, rc_comment )',
- 'rc_comment_data' => 'comment_rc_comment.comment_data',
- 'rc_comment_cid' => 'comment_rc_comment.comment_id',
- ],
+ [ 'actormigration' => 'table' ],
+ [ 'rc_user' => 'actormigration_user' ],
[],
[],
- [ 'comment_rc_comment' => [ 'LEFT JOIN', 'comment_rc_comment.comment_id = rc_comment_id' ] ],
- [ 'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_NEW ],
+ [ 'actormigration' => 'join' ],
],
[
[ 'includeFields' => [ WatchedItemQueryService::INCLUDE_COMMENT ] ],
null,
- [ 'comment_rc_comment' => 'comment' ],
- [
- 'rc_comment_text' => 'comment_rc_comment.comment_text',
- 'rc_comment_data' => 'comment_rc_comment.comment_data',
- 'rc_comment_cid' => 'comment_rc_comment.comment_id',
- ],
+ [ 'commentstore' => 'table' ],
+ [ 'commentstore' => 'field' ],
[],
[],
- [ 'comment_rc_comment' => [ 'JOIN', 'comment_rc_comment.comment_id = rc_comment_id' ] ],
- [ 'wgCommentTableSchemaMigrationStage' => MIGRATION_NEW ],
+ [ 'commentstore' => 'join' ],
],
[
[ 'includeFields' => [ WatchedItemQueryService::INCLUDE_PATROL_INFO ] ],
@@ -719,20 +747,20 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
[
[ 'filters' => [ WatchedItemQueryService::FILTER_ANON ] ],
null,
+ [ 'actormigration' => 'table' ],
[],
+ [ 'actormigration is anon' ],
[],
- [ 'rc_user = 0' ],
- [],
- [],
+ [ 'actormigration' => 'join' ],
],
[
[ 'filters' => [ WatchedItemQueryService::FILTER_NOT_ANON ] ],
null,
+ [ 'actormigration' => 'table' ],
[],
+ [ 'actormigration is not anon' ],
[],
- [ 'rc_user != 0' ],
- [],
- [],
+ [ 'actormigration' => 'join' ],
],
[
[ 'filters' => [ WatchedItemQueryService::FILTER_PATROLLED ] ],
@@ -748,7 +776,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
null,
[],
[],
- [ 'rc_patrolled = 0' ],
+ [ 'rc_patrolled' => 0 ],
[],
[],
],
@@ -773,20 +801,20 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
[
[ 'onlyByUser' => 'SomeOtherUser' ],
null,
+ [ 'actormigration' => 'table' ],
[],
+ [ 'actormigration_conds' ],
[],
- [ 'rc_user_text' => 'SomeOtherUser' ],
- [],
- [],
+ [ 'actormigration' => 'join' ],
],
[
[ 'notByUser' => 'SomeOtherUser' ],
null,
+ [ 'actormigration' => 'table' ],
[],
+ [ 'NOT(actormigration_conds)' ],
[],
- [ "rc_user_text != 'SomeOtherUser'" ],
- [],
- [],
+ [ 'actormigration' => 'join' ],
],
[
[ 'dir' => WatchedItemQueryService::DIR_OLDER ],
@@ -834,23 +862,8 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
array $expectedExtraFields,
array $expectedExtraConds,
array $expectedDbOptions,
- array $expectedExtraJoinConds,
- array $globals = []
+ array $expectedExtraJoinConds
) {
- // Sigh. This test class doesn't extend MediaWikiTestCase, so we have to reinvent setMwGlobals().
- if ( $globals ) {
- $resetGlobals = [];
- foreach ( $globals as $k => $v ) {
- $resetGlobals[$k] = $GLOBALS[$k];
- $GLOBALS[$k] = $v;
- }
- $reset = new ScopedCallback( function () use ( $resetGlobals ) {
- foreach ( $resetGlobals as $k => $v ) {
- $GLOBALS[$k] = $v;
- }
- } );
- }
-
$expectedTables = array_merge( [ 'recentchanges', 'watchlist', 'page' ], $expectedExtraTables );
$expectedFields = array_merge(
[
@@ -902,7 +915,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
)
->will( $this->returnValue( [] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
$items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options, $startFrom );
@@ -939,7 +952,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
$user = $this->getMockNonAnonUserWithIdAndNoPatrolRights( 1 );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$items = $queryService->getWatchedItemsWithRecentChangeInfo(
$user,
[ 'filters' => [ $filtersOption ] ]
@@ -1000,7 +1013,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
->method( 'getType' )
->will( $this->returnValue( $dbType ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
$items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
@@ -1013,62 +1026,74 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
[
[],
'deletedhistory',
+ [],
[
'(rc_type != ' . RC_LOG . ') OR ((rc_deleted & ' . LogPage::DELETED_ACTION . ') != ' .
LogPage::DELETED_ACTION . ')'
],
+ [],
],
[
[],
'suppressrevision',
+ [],
[
'(rc_type != ' . RC_LOG . ') OR (' .
'(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
],
+ [],
],
[
[],
'viewsuppressed',
+ [],
[
'(rc_type != ' . RC_LOG . ') OR (' .
'(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
],
+ [],
],
[
[ 'onlyByUser' => 'SomeOtherUser' ],
'deletedhistory',
+ [ 'actormigration' => 'table' ],
[
- 'rc_user_text' => 'SomeOtherUser',
+ 'actormigration_conds',
'(rc_deleted & ' . Revision::DELETED_USER . ') != ' . Revision::DELETED_USER,
'(rc_type != ' . RC_LOG . ') OR ((rc_deleted & ' . LogPage::DELETED_ACTION . ') != ' .
LogPage::DELETED_ACTION . ')'
],
+ [ 'actormigration' => 'join' ],
],
[
[ 'onlyByUser' => 'SomeOtherUser' ],
'suppressrevision',
+ [ 'actormigration' => 'table' ],
[
- 'rc_user_text' => 'SomeOtherUser',
+ 'actormigration_conds',
'(rc_deleted & ' . ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ) . ') != ' .
( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ),
'(rc_type != ' . RC_LOG . ') OR (' .
'(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
],
+ [ 'actormigration' => 'join' ],
],
[
[ 'onlyByUser' => 'SomeOtherUser' ],
'viewsuppressed',
+ [ 'actormigration' => 'table' ],
[
- 'rc_user_text' => 'SomeOtherUser',
+ 'actormigration_conds',
'(rc_deleted & ' . ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ) . ') != ' .
( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ),
'(rc_type != ' . RC_LOG . ') OR (' .
'(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
],
+ [ 'actormigration' => 'join' ],
],
];
}
@@ -1079,7 +1104,9 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
public function testGetWatchedItemsWithRecentChangeInfo_userPermissionRelatedExtraChecks(
array $options,
$notAllowedAction,
- array $expectedExtraConds
+ array $expectedExtraTables,
+ array $expectedExtraConds,
+ array $expectedExtraJoins
) {
$commonConds = [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ];
$conds = array_merge( $commonConds, $expectedExtraConds );
@@ -1088,18 +1115,21 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
$mockDb->expects( $this->once() )
->method( 'select' )
->with(
- [ 'recentchanges', 'watchlist', 'page' ],
+ array_merge( [ 'recentchanges', 'watchlist', 'page' ], $expectedExtraTables ),
$this->isType( 'array' ),
$conds,
$this->isType( 'string' ),
$this->isType( 'array' ),
- $this->isType( 'array' )
+ array_merge( [
+ 'watchlist' => [ 'INNER JOIN', [ 'wl_namespace=rc_namespace', 'wl_title=rc_title' ] ],
+ 'page' => [ 'LEFT JOIN', 'rc_cur_id=page_id' ],
+ ], $expectedExtraJoins )
)
->will( $this->returnValue( [] ) );
$user = $this->getMockNonAnonUserWithIdAndRestrictedPermissions( 1, $notAllowedAction );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
$this->assertEmpty( $items );
@@ -1139,7 +1169,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
)
->will( $this->returnValue( [] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
$items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, [ 'allRevisions' => true ] );
@@ -1224,7 +1254,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
$mockDb->expects( $this->never() )
->method( $this->anything() );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
$this->setExpectedException( InvalidArgumentException::class, $expectedInExceptionMessage );
@@ -1266,7 +1296,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
)
->will( $this->returnValue( [] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
$items = $queryService->getWatchedItemsWithRecentChangeInfo(
@@ -1308,7 +1338,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
)
->will( $this->returnValue( [] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
$items = $queryService->getWatchedItemsWithRecentChangeInfo(
@@ -1336,7 +1366,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
)
->will( $this->returnValue( [] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
$otherUser = $this->getMockUnrestrictedNonAnonUserWithId( 2 );
$otherUser->expects( $this->once() )
@@ -1367,7 +1397,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
$mockDb->expects( $this->never() )
->method( $this->anything() );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
$otherUser = $this->getMockUnrestrictedNonAnonUserWithId( 2 );
$otherUser->expects( $this->once() )
@@ -1404,7 +1434,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
] ),
] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockNonAnonUserWithId( 1 );
$items = $queryService->getWatchedItemsForUser( $user );
@@ -1504,7 +1534,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
)
->will( $this->returnValue( [] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$items = $queryService->getWatchedItemsForUser( $user, $options );
$this->assertEmpty( $items );
@@ -1601,7 +1631,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
)
->will( $this->returnCallback( function ( $a, $conj ) {
$sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
- return join( $sqlConj, array_map( function ( $s ) {
+ return implode( $sqlConj, array_map( function ( $s ) {
return '(' . $s . ')';
}, $a
) );
@@ -1617,7 +1647,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
)
->will( $this->returnValue( [] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$items = $queryService->getWatchedItemsForUser( $user, $options );
$this->assertEmpty( $items );
@@ -1655,7 +1685,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
array $options,
$expectedInExceptionMessage
) {
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $this->getMockDb() ) );
+ $queryService = $this->newService( $this->getMockDb() );
$this->setExpectedException( InvalidArgumentException::class, $expectedInExceptionMessage );
$queryService->getWatchedItemsForUser( $this->getMockNonAnonUserWithId( 1 ), $options );
@@ -1667,7 +1697,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
$mockDb->expects( $this->never() )
->method( $this->anything() );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$items = $queryService->getWatchedItemsForUser( $this->getMockAnonUser() );
$this->assertEmpty( $items );
diff --git a/www/wiki/tests/phpunit/includes/WatchedItemStoreIntegrationTest.php b/www/wiki/tests/phpunit/includes/watcheditem/WatchedItemStoreIntegrationTest.php
index 61b62aa6..3102929e 100644
--- a/www/wiki/tests/phpunit/includes/WatchedItemStoreIntegrationTest.php
+++ b/www/wiki/tests/phpunit/includes/watcheditem/WatchedItemStoreIntegrationTest.php
@@ -106,6 +106,23 @@ class WatchedItemStoreIntegrationTest extends MediaWikiTestCase {
);
}
+ public function testWatchBatchAndClearItems() {
+ $user = $this->getUser();
+ $title1 = Title::newFromText( 'WatchedItemStoreIntegrationTestPage1' );
+ $title2 = Title::newFromText( 'WatchedItemStoreIntegrationTestPage2' );
+ $store = MediaWikiServices::getInstance()->getWatchedItemStore();
+
+ $store->addWatchBatchForUser( $user, [ $title1, $title2 ] );
+
+ $this->assertTrue( $store->isWatched( $user, $title1 ) );
+ $this->assertTrue( $store->isWatched( $user, $title2 ) );
+
+ $store->clearUserWatchedItems( $user );
+
+ $this->assertFalse( $store->isWatched( $user, $title1 ) );
+ $this->assertFalse( $store->isWatched( $user, $title2 ) );
+ }
+
public function testUpdateResetAndSetNotificationTimestamp() {
$user = $this->getUser();
$otherUser = ( new TestUser( 'WatchedItemStoreIntegrationTestUser_otherUser' ) )->getUser();
diff --git a/www/wiki/tests/phpunit/includes/WatchedItemStoreUnitTest.php b/www/wiki/tests/phpunit/includes/watcheditem/WatchedItemStoreUnitTest.php
index 950e2208..26f69088 100644
--- a/www/wiki/tests/phpunit/includes/WatchedItemStoreUnitTest.php
+++ b/www/wiki/tests/phpunit/includes/watcheditem/WatchedItemStoreUnitTest.php
@@ -1,6 +1,8 @@
<?php
use MediaWiki\Linker\LinkTarget;
+use Wikimedia\Rdbms\LoadBalancer;
use Wikimedia\ScopedCallback;
+use Wikimedia\TestingAccessWrapper;
/**
* @author Addshore
@@ -45,6 +47,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
private function getMockCache() {
$mock = $this->getMockBuilder( HashBagOStuff::class )
->disableOriginalConstructor()
+ ->setMethods( [ 'get', 'set', 'delete', 'makeKey' ] )
->getMock();
$mock->expects( $this->any() )
->method( 'makeKey' )
@@ -103,10 +106,82 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
return new WatchedItemStore(
$loadBalancer,
$cache,
- $readOnlyMode
+ $readOnlyMode,
+ 1000
);
}
+ public function testClearWatchedItems() {
+ $user = $this->getMockNonAnonUserWithId( 7 );
+
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->once() )
+ ->method( 'selectField' )
+ ->with(
+ 'watchlist',
+ 'COUNT(*)',
+ [
+ 'wl_user' => $user->getId(),
+ ],
+ $this->isType( 'string' )
+ )
+ ->will( $this->returnValue( 12 ) );
+ $mockDb->expects( $this->once() )
+ ->method( 'delete' )
+ ->with(
+ 'watchlist',
+ [ 'wl_user' => 7 ],
+ $this->isType( 'string' )
+ );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )->method( 'get' );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( 'RM-KEY' );
+
+ $store = $this->newWatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache,
+ $this->getMockReadOnlyMode()
+ );
+ TestingAccessWrapper::newFromObject( $store )
+ ->cacheIndex = [ 0 => [ 'F' => [ 7 => 'RM-KEY', 9 => 'KEEP-KEY' ] ] ];
+
+ $this->assertTrue( $store->clearUserWatchedItems( $user ) );
+ }
+
+ public function testClearWatchedItems_tooManyItemsWatched() {
+ $user = $this->getMockNonAnonUserWithId( 7 );
+
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->once() )
+ ->method( 'selectField' )
+ ->with(
+ 'watchlist',
+ 'COUNT(*)',
+ [
+ 'wl_user' => $user->getId(),
+ ],
+ $this->isType( 'string' )
+ )
+ ->will( $this->returnValue( 99999 ) );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )->method( 'get' );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->never() )->method( 'delete' );
+
+ $store = $this->newWatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache,
+ $this->getMockReadOnlyMode()
+ );
+
+ $this->assertFalse( $store->clearUserWatchedItems( $user ) );
+ }
+
public function testCountWatchedItems() {
$user = $this->getMockNonAnonUserWithId( 1 );
@@ -121,7 +196,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
],
$this->isType( 'string' )
)
- ->will( $this->returnValue( 12 ) );
+ ->will( $this->returnValue( '12' ) );
$mockCache = $this->getMockCache();
$mockCache->expects( $this->never() )->method( 'get' );
@@ -152,7 +227,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
],
$this->isType( 'string' )
)
- ->will( $this->returnValue( 7 ) );
+ ->will( $this->returnValue( '7' ) );
$mockCache = $this->getMockCache();
$mockCache->expects( $this->never() )->method( 'get' );
@@ -178,9 +253,9 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
$mockDb = $this->getMockDb();
$dbResult = [
- $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
- $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
- $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
+ $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
+ $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
+ $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
),
];
$mockDb->expects( $this->once() )
@@ -244,9 +319,9 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
$mockDb = $this->getMockDb();
$dbResult = [
- $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
- $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
- $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
+ $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
+ $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
+ $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
),
];
$mockDb->expects( $this->once() )
@@ -310,7 +385,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
],
$this->isType( 'string' )
)
- ->will( $this->returnValue( 7 ) );
+ ->will( $this->returnValue( '7' ) );
$mockDb->expects( $this->exactly( 1 ) )
->method( 'addQuotes' )
->will( $this->returnCallback( function ( $value ) {
@@ -344,9 +419,11 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
];
$dbResult = [
- $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
- $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
- $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
+ $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
+ $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
+ $this->getFakeRow(
+ [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
+ ),
];
$mockDb = $this->getMockDb();
$mockDb->expects( $this->exactly( 2 * 3 ) )
@@ -367,7 +444,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
)
->will( $this->returnCallback( function ( $a, $conj ) {
$sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
- return join( $sqlConj, array_map( function ( $s ) {
+ return implode( $sqlConj, array_map( function ( $s ) {
return '(' . $s . ')';
}, $a
) );
@@ -433,14 +510,16 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
];
$dbResult = [
- $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
- $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
- $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
+ $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
+ $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
$this->getFakeRow(
- [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 100 ]
+ [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
),
$this->getFakeRow(
- [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 200 ]
+ [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '100' ]
+ ),
+ $this->getFakeRow(
+ [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '200' ]
),
];
$mockDb = $this->getMockDb();
@@ -462,7 +541,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
)
->will( $this->returnCallback( function ( $a, $conj ) {
$sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
- return join( $sqlConj, array_map( function ( $s ) {
+ return implode( $sqlConj, array_map( function ( $s ) {
return '(' . $s . ')';
}, $a
) );
@@ -595,7 +674,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
],
$this->isType( 'string' )
)
- ->will( $this->returnValue( 9 ) );
+ ->will( $this->returnValue( '9' ) );
$mockCache = $this->getMockCache();
$mockCache->expects( $this->never() )->method( 'set' );
@@ -630,7 +709,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
$this->isType( 'string' ),
[ 'LIMIT' => 50 ]
)
- ->will( $this->returnValue( 50 ) );
+ ->will( $this->returnValue( '50' ) );
$mockCache = $this->getMockCache();
$mockCache->expects( $this->never() )->method( 'set' );
@@ -668,7 +747,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
$this->isType( 'string' ),
[ 'LIMIT' => 50 ]
)
- ->will( $this->returnValue( 9 ) );
+ ->will( $this->returnValue( '9' ) );
$mockCache = $this->getMockCache();
$mockCache->expects( $this->never() )->method( 'set' );
@@ -720,8 +799,8 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
public function testDuplicateEntry_somethingToDuplicate() {
$fakeRows = [
- $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
- $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
+ $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
+ $this->getFakeRow( [ 'wl_user' => '2', 'wl_notificationtimestamp' => null ] ),
];
$mockDb = $this->getMockDb();
@@ -839,7 +918,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
LinkTarget $newTarget
) {
$fakeRows = [
- $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
+ $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
];
$mockDb = $this->getMockDb();
@@ -1113,7 +1192,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
$this->getMockNonAnonUserWithId( 1 ),
new TitleValue( 0, 'SomeDbKey' )
);
- $this->assertInstanceOf( 'WatchedItem', $watchedItem );
+ $this->assertInstanceOf( WatchedItem::class, $watchedItem );
$this->assertEquals( 1, $watchedItem->getUser()->getId() );
$this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
$this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
@@ -1312,7 +1391,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
$this->getMockNonAnonUserWithId( 1 ),
new TitleValue( 0, 'SomeDbKey' )
);
- $this->assertInstanceOf( 'WatchedItem', $watchedItem );
+ $this->assertInstanceOf( WatchedItem::class, $watchedItem );
$this->assertEquals( 1, $watchedItem->getUser()->getId() );
$this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
$this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
@@ -1452,7 +1531,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
$this->assertInternalType( 'array', $watchedItems );
$this->assertCount( 2, $watchedItems );
foreach ( $watchedItems as $watchedItem ) {
- $this->assertInstanceOf( 'WatchedItem', $watchedItem );
+ $this->assertInstanceOf( WatchedItem::class, $watchedItem );
}
$this->assertEquals(
new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
@@ -1511,7 +1590,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
$this->getMockReadOnlyMode()
);
- $this->setExpectedException( 'InvalidArgumentException' );
+ $this->setExpectedException( InvalidArgumentException::class );
$store->getWatchedItemsForUser(
$this->getMockNonAnonUserWithId( 1 ),
[ 'sort' => 'foo' ]
@@ -1631,13 +1710,13 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
$mockDb = $this->getMockDb();
$dbResult = [
$this->getFakeRow( [
- 'wl_namespace' => 0,
+ 'wl_namespace' => '0',
'wl_title' => 'SomeDbKey',
'wl_notificationtimestamp' => '20151212010101',
] ),
$this->getFakeRow(
[
- 'wl_namespace' => 1,
+ 'wl_namespace' => '1',
'wl_title' => 'AnotherDbKey',
'wl_notificationtimestamp' => null,
]
@@ -1773,7 +1852,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
)
->will( $this->returnValue( [
$this->getFakeRow(
- [ 'wl_namespace' => 1, 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
+ [ 'wl_namespace' => '1', 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
)
] ) );
@@ -1996,12 +2075,11 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
->method( 'selectRow' );
$mockCache = $this->getMockCache();
- $mockDb->expects( $this->never() )
- ->method( 'get' );
- $mockDb->expects( $this->never() )
- ->method( 'set' );
- $mockDb->expects( $this->never() )
- ->method( 'delete' );
+ $mockCache->expects( $this->never() )->method( 'get' );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( '0:SomeDbKey:1' );
$store = $this->newWatchedItemStore(
$this->getMockLoadBalancer( $mockDb ),
@@ -2090,12 +2168,11 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
->method( 'selectRow' );
$mockCache = $this->getMockCache();
- $mockDb->expects( $this->never() )
- ->method( 'get' );
- $mockDb->expects( $this->never() )
- ->method( 'set' );
- $mockDb->expects( $this->never() )
- ->method( 'delete' );
+ $mockCache->expects( $this->never() )->method( 'get' );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( '0:SomeTitle:1' );
$store = $this->newWatchedItemStore(
$this->getMockLoadBalancer( $mockDb ),
@@ -2157,12 +2234,13 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
) );
$mockCache = $this->getMockCache();
- $mockDb->expects( $this->never() )
- ->method( 'get' );
- $mockDb->expects( $this->never() )
- ->method( 'set' );
- $mockDb->expects( $this->never() )
- ->method( 'delete' );
+ $mockCache->expects( $this->never() )->method( 'get' );
+ $mockCache->expects( $this->once() )
+ ->method( 'set' )
+ ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( '0:SomeDbKey:1' );
$store = $this->newWatchedItemStore(
$this->getMockLoadBalancer( $mockDb ),
@@ -2233,12 +2311,11 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
->will( $this->returnValue( false ) );
$mockCache = $this->getMockCache();
- $mockDb->expects( $this->never() )
- ->method( 'get' );
- $mockDb->expects( $this->never() )
- ->method( 'set' );
- $mockDb->expects( $this->never() )
- ->method( 'delete' );
+ $mockCache->expects( $this->never() )->method( 'get' );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( '0:SomeDbKey:1' );
$store = $this->newWatchedItemStore(
$this->getMockLoadBalancer( $mockDb ),
@@ -2300,12 +2377,13 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
) );
$mockCache = $this->getMockCache();
- $mockDb->expects( $this->never() )
- ->method( 'get' );
- $mockDb->expects( $this->never() )
- ->method( 'set' );
- $mockDb->expects( $this->never() )
- ->method( 'delete' );
+ $mockCache->expects( $this->never() )->method( 'get' );
+ $mockCache->expects( $this->once() )
+ ->method( 'set' )
+ ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( '0:SomeDbKey:1' );
$store = $this->newWatchedItemStore(
$this->getMockLoadBalancer( $mockDb ),
@@ -2378,12 +2456,13 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
) );
$mockCache = $this->getMockCache();
- $mockDb->expects( $this->never() )
- ->method( 'get' );
- $mockDb->expects( $this->never() )
- ->method( 'set' );
- $mockDb->expects( $this->never() )
- ->method( 'delete' );
+ $mockCache->expects( $this->never() )->method( 'get' );
+ $mockCache->expects( $this->once() )
+ ->method( 'set' )
+ ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( '0:SomeDbKey:1' );
$store = $this->newWatchedItemStore(
$this->getMockLoadBalancer( $mockDb ),
diff --git a/www/wiki/tests/phpunit/languages/LanguageCodeTest.php b/www/wiki/tests/phpunit/languages/LanguageCodeTest.php
index 7689ef1d..544a0635 100644
--- a/www/wiki/tests/phpunit/languages/LanguageCodeTest.php
+++ b/www/wiki/tests/phpunit/languages/LanguageCodeTest.php
@@ -2,13 +2,13 @@
/**
* @covers LanguageCode
- *
* @group Language
*
- * @license GPL-2.0+
- * @author Thiemo Mättig
+ * @author Thiemo Kreuz
*/
-class LanguageCodeTest extends PHPUnit_Framework_TestCase {
+class LanguageCodeTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
public function testConstructor() {
$instance = new LanguageCode();
@@ -43,4 +43,119 @@ class LanguageCodeTest extends PHPUnit_Framework_TestCase {
$this->assertEquals( null, LanguageCode::replaceDeprecatedCodes( null ) );
}
+ /**
+ * test @see LanguageCode::bcp47().
+ * Please note the BCP 47 explicitly state that language codes are case
+ * insensitive, there are some exceptions to the rule :)
+ * This test is used to verify our formatting against all lower and
+ * all upper cases language code.
+ *
+ * @see https://tools.ietf.org/html/bcp47
+ * @dataProvider provideLanguageCodes()
+ */
+ public function testBcp47( $code, $expected ) {
+ $code = strtolower( $code );
+ $this->assertEquals( $expected, LanguageCode::bcp47( $code ),
+ "Applying BCP47 standard to lower case '$code'"
+ );
+
+ $code = strtoupper( $code );
+ $this->assertEquals( $expected, LanguageCode::bcp47( $code ),
+ "Applying BCP47 standard to upper case '$code'"
+ );
+ }
+
+ /**
+ * Array format is ($code, $expected)
+ */
+ public static function provideLanguageCodes() {
+ return [
+ // Extracted from BCP 47 (list not exhaustive)
+ # 2.1.1
+ [ 'en-ca-x-ca', 'en-CA-x-ca' ],
+ [ 'sgn-be-fr', 'sgn-BE-FR' ],
+ [ 'az-latn-x-latn', 'az-Latn-x-latn' ],
+ # 2.2
+ [ 'sr-Latn-RS', 'sr-Latn-RS' ],
+ [ 'az-arab-ir', 'az-Arab-IR' ],
+
+ # 2.2.5
+ [ 'sl-nedis', 'sl-nedis' ],
+ [ 'de-ch-1996', 'de-CH-1996' ],
+
+ # 2.2.6
+ [
+ 'en-latn-gb-boont-r-extended-sequence-x-private',
+ 'en-Latn-GB-boont-r-extended-sequence-x-private'
+ ],
+
+ // Examples from BCP 47 Appendix A
+ # Simple language subtag:
+ [ 'DE', 'de' ],
+ [ 'fR', 'fr' ],
+ [ 'ja', 'ja' ],
+
+ # Language subtag plus script subtag:
+ [ 'zh-hans', 'zh-Hans' ],
+ [ 'sr-cyrl', 'sr-Cyrl' ],
+ [ 'sr-latn', 'sr-Latn' ],
+
+ # Extended language subtags and their primary language subtag
+ # counterparts:
+ [ 'zh-cmn-hans-cn', 'zh-cmn-Hans-CN' ],
+ [ 'cmn-hans-cn', 'cmn-Hans-CN' ],
+ [ 'zh-yue-hk', 'zh-yue-HK' ],
+ [ 'yue-hk', 'yue-HK' ],
+
+ # Language-Script-Region:
+ [ 'zh-hans-cn', 'zh-Hans-CN' ],
+ [ 'sr-latn-RS', 'sr-Latn-RS' ],
+
+ # Language-Variant:
+ [ 'sl-rozaj', 'sl-rozaj' ],
+ [ 'sl-rozaj-biske', 'sl-rozaj-biske' ],
+ [ 'sl-nedis', 'sl-nedis' ],
+
+ # Language-Region-Variant:
+ [ 'de-ch-1901', 'de-CH-1901' ],
+ [ 'sl-it-nedis', 'sl-IT-nedis' ],
+
+ # Language-Script-Region-Variant:
+ [ 'hy-latn-it-arevela', 'hy-Latn-IT-arevela' ],
+
+ # Language-Region:
+ [ 'de-de', 'de-DE' ],
+ [ 'en-us', 'en-US' ],
+ [ 'es-419', 'es-419' ],
+
+ # Private use subtags:
+ [ 'de-ch-x-phonebk', 'de-CH-x-phonebk' ],
+ [ 'az-arab-x-aze-derbend', 'az-Arab-x-aze-derbend' ],
+ /**
+ * Previous test does not reflect the BCP 47 which states:
+ * az-Arab-x-AZE-derbend
+ * AZE being private, it should be lower case, hence the test above
+ * should probably be:
+ * [ 'az-arab-x-aze-derbend', 'az-Arab-x-AZE-derbend' ],
+ */
+
+ # Private use registry values:
+ [ 'x-whatever', 'x-whatever' ],
+ [ 'qaa-qaaa-qm-x-southern', 'qaa-Qaaa-QM-x-southern' ],
+ [ 'de-qaaa', 'de-Qaaa' ],
+ [ 'sr-latn-qm', 'sr-Latn-QM' ],
+ [ 'sr-qaaa-rs', 'sr-Qaaa-RS' ],
+
+ # Tags that use extensions
+ [ 'en-us-u-islamcal', 'en-US-u-islamcal' ],
+ [ 'zh-cn-a-myext-x-private', 'zh-CN-a-myext-x-private' ],
+ [ 'en-a-myext-b-another', 'en-a-myext-b-another' ],
+
+ # Invalid:
+ // de-419-DE
+ // a-DE
+ // ar-a-aaa-b-bbb-a-ccc
+ ];
+ }
+
}
diff --git a/www/wiki/tests/phpunit/languages/LanguageConverterTest.php b/www/wiki/tests/phpunit/languages/LanguageConverterTest.php
index fc2ed33b..82ab7def 100644
--- a/www/wiki/tests/phpunit/languages/LanguageConverterTest.php
+++ b/www/wiki/tests/phpunit/languages/LanguageConverterTest.php
@@ -160,6 +160,8 @@ class LanguageConverterTest extends MediaWikiLangTestCase {
/**
* Test exhausting pcre.backtrack_limit
+ *
+ * @covers LanguageConverter::autoConvert
*/
public function testAutoConvertT124404() {
$testString = '';
diff --git a/www/wiki/tests/phpunit/languages/LanguageTest.php b/www/wiki/tests/phpunit/languages/LanguageTest.php
index cd52366f..66bd76df 100644
--- a/www/wiki/tests/phpunit/languages/LanguageTest.php
+++ b/www/wiki/tests/phpunit/languages/LanguageTest.php
@@ -209,71 +209,105 @@ class LanguageTest extends LanguageClassesTestCase {
}
/**
- * @covers Language::truncate
+ * @covers Language::truncateForDatabase
+ * @covers Language::truncateInternal
*/
- public function testTruncate() {
+ public function testTruncateForDatabase() {
$this->assertEquals(
"XXX",
- $this->getLang()->truncate( "1234567890", 0, 'XXX' ),
+ $this->getLang()->truncateForDatabase( "1234567890", 0, 'XXX' ),
'truncate prefix, len 0, small ellipsis'
);
$this->assertEquals(
"12345XXX",
- $this->getLang()->truncate( "1234567890", 8, 'XXX' ),
+ $this->getLang()->truncateForDatabase( "1234567890", 8, 'XXX' ),
'truncate prefix, small ellipsis'
);
$this->assertEquals(
"123456789",
- $this->getLang()->truncate( "123456789", 5, 'XXXXXXXXXXXXXXX' ),
+ $this->getLang()->truncateForDatabase( "123456789", 5, 'XXXXXXXXXXXXXXX' ),
'truncate prefix, large ellipsis'
);
$this->assertEquals(
"XXX67890",
- $this->getLang()->truncate( "1234567890", -8, 'XXX' ),
+ $this->getLang()->truncateForDatabase( "1234567890", -8, 'XXX' ),
'truncate suffix, small ellipsis'
);
$this->assertEquals(
"123456789",
- $this->getLang()->truncate( "123456789", -5, 'XXXXXXXXXXXXXXX' ),
+ $this->getLang()->truncateForDatabase( "123456789", -5, 'XXXXXXXXXXXXXXX' ),
'truncate suffix, large ellipsis'
);
$this->assertEquals(
"123XXX",
- $this->getLang()->truncate( "123 ", 9, 'XXX' ),
+ $this->getLang()->truncateForDatabase( "123 ", 9, 'XXX' ),
'truncate prefix, with spaces'
);
$this->assertEquals(
"12345XXX",
- $this->getLang()->truncate( "12345 8", 11, 'XXX' ),
+ $this->getLang()->truncateForDatabase( "12345 8", 11, 'XXX' ),
'truncate prefix, with spaces and non-space ending'
);
$this->assertEquals(
"XXX234",
- $this->getLang()->truncate( "1 234", -8, 'XXX' ),
+ $this->getLang()->truncateForDatabase( "1 234", -8, 'XXX' ),
'truncate suffix, with spaces'
);
$this->assertEquals(
"12345XXX",
- $this->getLang()->truncate( "1234567890", 5, 'XXX', false ),
+ $this->getLang()->truncateForDatabase( "1234567890", 5, 'XXX', false ),
'truncate without adjustment'
);
$this->assertEquals(
"泰乐菌...",
- $this->getLang()->truncate( "泰乐菌素123456789", 11, '...', false ),
+ $this->getLang()->truncateForDatabase( "泰乐菌素123456789", 11, '...', false ),
'truncate does not chop Unicode characters in half'
);
$this->assertEquals(
"\n泰乐菌...",
- $this->getLang()->truncate( "\n泰乐菌素123456789", 12, '...', false ),
+ $this->getLang()->truncateForDatabase( "\n泰乐菌素123456789", 12, '...', false ),
'truncate does not chop Unicode characters in half if there is a preceding newline'
);
}
/**
+ * @dataProvider provideTruncateData
+ * @covers Language::truncateForVisual
+ * @covers Language::truncateInternal
+ */
+ public function testTruncateForVisual(
+ $expected, $string, $length, $ellipsis = '...', $adjustLength = true
+ ) {
+ $this->assertEquals(
+ $expected,
+ $this->getLang()->truncateForVisual( $string, $length, $ellipsis, $adjustLength )
+ );
+ }
+
+ /**
+ * @return array Format is ($expected, $string, $length, $ellipsis, $adjustLength)
+ */
+ public static function provideTruncateData() {
+ return [
+ [ "XXX", "тестирам да ли ради", 0, "XXX" ],
+ [ "testnXXX", "testni scenarij", 8, "XXX" ],
+ [ "حالة اختبار", "حالة اختبار", 5, "XXXXXXXXXXXXXXX" ],
+ [ "XXXедент", "прецедент", -8, "XXX" ],
+ [ "XXപിൾ", "ആപ്പിൾ", -5, "XX" ],
+ [ "神秘XXX", "神秘 ", 9, "XXX" ],
+ [ "ΔημιουργXXX", "Δημιουργία Σύμπαντος", 11, "XXX" ],
+ [ "XXXの家です", "地球は私たちの唯 の家です", -8, "XXX" ],
+ [ "زندگیXXX", "زندگی زیباست", 6, "XXX", false ],
+ [ "ცხოვრება...", "ცხოვრება არის საოცარი", 8, "...", false ],
+ [ "\nທ່ານ...", "\nທ່ານບໍ່ຮູ້ຫນັງສື", 5, "...", false ],
+ ];
+ }
+
+ /**
* @dataProvider provideHTMLTruncateData
* @covers Language::truncateHTML
*/
@@ -1010,6 +1044,27 @@ class LanguageTest extends LanguageClassesTestCase {
'nengo'
],
[
+ 'xtY',
+ '20190430235959',
+ '平成31',
+ '平成31',
+ 'nengo - last day of heisei'
+ ],
+ [
+ 'xtY',
+ '20190501000000',
+ '令和元',
+ '令和元',
+ 'nengo - first day of reiwa'
+ ],
+ [
+ 'xtY',
+ '20200501000000',
+ '令和2',
+ '令和2',
+ 'nengo - second year of reiwa'
+ ],
+ [
'xrxkYY',
'20120102090705',
'MMDLV2012',
@@ -1326,7 +1381,7 @@ class LanguageTest extends LanguageClassesTestCase {
}
public static function provideCheckTitleEncodingData() {
- // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ // phpcs:disable Generic.Files.LineLength
return [
[ "" ],
[ "United States of America" ], // 7bit ASCII
@@ -1377,7 +1432,7 @@ class LanguageTest extends LanguageClassesTestCase {
)
]
];
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
}
/**
@@ -1630,6 +1685,34 @@ class LanguageTest extends LanguageClassesTestCase {
}
/**
+ * @dataProvider provideFormatNum
+ * @covers Language::formatNum
+ */
+ public function testFormatNum(
+ $translateNumerals, $langCode, $number, $nocommafy, $expected
+ ) {
+ $this->setMwGlobals( [ 'wgTranslateNumerals' => $translateNumerals ] );
+ $lang = Language::factory( $langCode );
+ $formattedNum = $lang->formatNum( $number, $nocommafy );
+ $this->assertType( 'string', $formattedNum );
+ $this->assertEquals( $expected, $formattedNum );
+ }
+
+ public function provideFormatNum() {
+ return [
+ [ true, 'en', 100, false, '100' ],
+ [ true, 'en', 101, true, '101' ],
+ [ false, 'en', 103, false, '103' ],
+ [ false, 'en', 104, true, '104' ],
+ [ true, 'en', '105', false, '105' ],
+ [ true, 'en', '106', true, '106' ],
+ [ false, 'en', '107', false, '107' ],
+ [ false, 'en', '108', true, '108' ],
+ ];
+ }
+
+ /**
+ * @covers Language::parseFormattedNumber
* @dataProvider parseFormattedNumberProvider
*/
public function testParseFormattedNumber( $langCode, $number ) {
@@ -1768,6 +1851,9 @@ class LanguageTest extends LanguageClassesTestCase {
];
}
+ /**
+ * @covers Language::equals
+ */
public function testEquals() {
$en1 = new Language();
$en1->setCode( 'en' );
diff --git a/www/wiki/tests/phpunit/languages/SpecialPageAliasTest.php b/www/wiki/tests/phpunit/languages/SpecialPageAliasTest.php
index 4a7fed2a..0bb6a4d2 100644
--- a/www/wiki/tests/phpunit/languages/SpecialPageAliasTest.php
+++ b/www/wiki/tests/phpunit/languages/SpecialPageAliasTest.php
@@ -25,7 +25,7 @@ class SpecialPageAliasTest extends MediaWikiTestCase {
}
public function validSpecialPageAliasesProvider() {
- $codes = array_keys( Language::fetchLanguageNames( 'mwfile' ) );
+ $codes = array_keys( Language::fetchLanguageNames( null, 'mwfile' ) );
$data = [];
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageArTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageArTest.php
index 5a667598..f3f5a3f1 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageArTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageArTest.php
@@ -4,7 +4,9 @@
* @file
*/
-/** Tests for MediaWiki languages/LanguageAr.php */
+/**
+ * @covers LanguageAr
+ */
class LanguageArTest extends LanguageClassesTestCase {
/**
* @covers Language::formatNum
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageBe_taraskTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageBe_taraskTest.php
index 26db1062..4f049cd1 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageBe_taraskTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageBe_taraskTest.php
@@ -1,8 +1,11 @@
<?php
-// @codingStandardsIgnoreStart Ignore Squiz.Classes.ValidClassName.NotCamelCaps
+// phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps
+/**
+ * @covers LanguageBe_tarask
+ */
class LanguageBe_taraskTest extends LanguageClassesTestCase {
- // @codingStandardsIgnoreEnd
+ // phpcs:enable
/**
* Make sure the language code we are given is indeed
* be-tarask. This is to ensure LanguageClassesTestCase
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageBsTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageBsTest.php
index 207f5054..29b2ccf3 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageBsTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageBsTest.php
@@ -5,7 +5,11 @@
* @file
*/
-/** Tests for Croatian (hrvatski) */
+/**
+ * Tests for Croatian (hrvatski)
+ *
+ * @covers LanguageBs
+ */
class LanguageBsTest extends LanguageClassesTestCase {
/**
* @dataProvider providePlural
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageCrhTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageCrhTest.php
new file mode 100644
index 00000000..7c99614e
--- /dev/null
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageCrhTest.php
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * @covers LanguageCrh
+ * @covers CrhConverter
+ */
+class LanguageCrhTest extends LanguageClassesTestCase {
+ /**
+ * @dataProvider provideAutoConvertToAllVariants
+ * @covers Language::autoConvertToAllVariants
+ */
+ public function testAutoConvertToAllVariants( $result, $value ) {
+ $this->assertEquals( $result, $this->getLang()->autoConvertToAllVariants( $value ) );
+ }
+
+ public static function provideAutoConvertToAllVariants() {
+ return [
+ [ // general words, covering more of the alphabet
+ [
+ 'crh' => 'рузгярнынъ ruzgârnıñ Париж Parij',
+ 'crh-cyrl' => 'рузгярнынъ рузгярнынъ Париж Париж',
+ 'crh-latn' => 'ruzgârnıñ ruzgârnıñ Parij Parij',
+ ],
+ 'рузгярнынъ ruzgârnıñ Париж Parij'
+ ],
+ [ // general words, covering more of the alphabet
+ [
+ 'crh' => 'чёкюч çöküç элифбени elifbeni полициясы politsiyası',
+ 'crh-cyrl' => 'чёкюч чёкюч элифбени элифбени полициясы полициясы',
+ 'crh-latn' => 'çöküç çöküç elifbeni elifbeni politsiyası politsiyası',
+ ],
+ 'чёкюч çöküç элифбени elifbeni полициясы politsiyası'
+ ],
+ [ // general words, covering more of the alphabet
+ [
+ 'crh' => 'хусусында hususında акъшамларны aqşamlarnı опькеленюв öpkelenüv',
+ 'crh-cyrl' => 'хусусында хусусында акъшамларны акъшамларны опькеленюв опькеленюв',
+ 'crh-latn' => 'hususında hususında aqşamlarnı aqşamlarnı öpkelenüv öpkelenüv',
+ ],
+ 'хусусында hususında акъшамларны aqşamlarnı опькеленюв öpkelenüv'
+ ],
+ [ // general words, covering more of the alphabet
+ [
+ 'crh' => 'кулюмсиреди külümsiredi айтмайджагъым aytmaycağım козьяшсыз közyaşsız',
+ 'crh-cyrl' => 'кулюмсиреди кулюмсиреди айтмайджагъым айтмайджагъым козьяшсыз козьяшсыз',
+ 'crh-latn' => 'külümsiredi külümsiredi aytmaycağım aytmaycağım közyaşsız közyaşsız',
+ ],
+ 'кулюмсиреди külümsiredi айтмайджагъым aytmaycağım козьяшсыз közyaşsız'
+ ],
+ [ // exception words
+ [
+ 'crh' => 'инструменталь instrumental гургуль gürgül тюшюнмемек tüşünmemek',
+ 'crh-cyrl' => 'инструменталь инструменталь гургуль гургуль тюшюнмемек тюшюнмемек',
+ 'crh-latn' => 'instrumental instrumental gürgül gürgül tüşünmemek tüşünmemek',
+ ],
+ 'инструменталь instrumental гургуль gürgül тюшюнмемек tüşünmemek'
+ ],
+ [ // recent problem words, part 1
+ [
+ 'crh' => 'künü куню sürgünligi сюргюнлиги özü озю etti этти',
+ 'crh-cyrl' => 'куню куню сюргюнлиги сюргюнлиги озю озю этти этти',
+ 'crh-latn' => 'künü künü sürgünligi sürgünligi özü özü etti etti',
+ ],
+ 'künü куню sürgünligi сюргюнлиги özü озю etti этти'
+ ],
+ [ // recent problem words, part 2
+ [
+ 'crh' => 'esas эсас dört дёрт keldi кельди',
+ 'crh-cyrl' => 'эсас эсас дёрт дёрт кельди кельди',
+ 'crh-latn' => 'esas esas dört dört keldi keldi',
+ ],
+ 'esas эсас dört дёрт keldi кельди'
+ ],
+ [ // multi part words
+ [
+ 'crh' => 'эки юз eki yüz',
+ 'crh-cyrl' => 'эки юз эки юз',
+ 'crh-latn' => 'eki yüz eki yüz',
+ ],
+ 'эки юз eki yüz'
+ ],
+ [ // ALL CAPS, made up acronyms (not 100% sure these are correct)
+ [
+ 'crh' => 'ÑAB QIC ĞUK COT НЪАБ КЪЫДж ГЪУК ДЖОТ CA ДЖА',
+ 'crh-cyrl' => 'НЪАБ КЪЫДж ГЪУК ДЖОТ НЪАБ КЪЫДж ГЪУК ДЖОТ ДЖА ДЖА',
+ 'crh-latn' => 'ÑAB QIC ĞUK COT ÑAB QIC ĞUK COT CA CA',
+ ],
+ 'ÑAB QIC ĞUK COT НЪАБ КЪЫДж ГЪУК ДЖОТ CA ДЖА'
+ ],
+ ];
+ }
+}
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageCuTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageCuTest.php
index de65d162..565a8856 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageCuTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageCuTest.php
@@ -5,7 +5,9 @@
* @file
*/
-/** Tests for MediaWiki languages/LanguageCu.php */
+/**
+ * @covers LanguageCu
+ */
class LanguageCuTest extends LanguageClassesTestCase {
/**
* @dataProvider providePlural
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageDsbTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageDsbTest.php
index 949d5dbe..877a70cd 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageDsbTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageDsbTest.php
@@ -5,7 +5,9 @@
* @file
*/
-/** Tests for MediaWiki languages/classes/LanguageDsb.php */
+/**
+ * @covers LanguageDsb
+ */
class LanguageDsbTest extends LanguageClassesTestCase {
/**
* @dataProvider providePlural
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageGanTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageGanTest.php
index 43eb93e3..c5d9e5e6 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageGanTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageGanTest.php
@@ -1,5 +1,9 @@
<?php
+/**
+ * @covers LanguageGan
+ * @covers GanConverter
+ */
class LanguageGanTest extends LanguageClassesTestCase {
/**
* @dataProvider provideAutoConvertToAllVariants
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageHsbTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageHsbTest.php
index 133cc1c3..0841f6f9 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageHsbTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageHsbTest.php
@@ -5,7 +5,9 @@
* @file
*/
-/** Tests for MediaWiki languages/classes/LanguageHsb.php */
+/**
+ * @covers LanguageHsb
+ */
class LanguageHsbTest extends LanguageClassesTestCase {
/**
* @dataProvider providePlural
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageHuTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageHuTest.php
index 7ea63e10..a1925bdf 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageHuTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageHuTest.php
@@ -5,7 +5,9 @@
* @file
*/
-/** Tests for MediaWiki languages/LanguageHu.php */
+/**
+ * @covers LanguageHu
+ */
class LanguageHuTest extends LanguageClassesTestCase {
/**
* @dataProvider providePlural
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageHyTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageHyTest.php
index 64530912..b4936154 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageHyTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageHyTest.php
@@ -5,7 +5,11 @@
* @file
*/
-/** Tests for Armenian (Հայերեն) */
+/**
+ * Tests for Armenian (Հայերեն)
+ *
+ * @covers LanguageHy
+ */
class LanguageHyTest extends LanguageClassesTestCase {
/**
* @dataProvider providePlural
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageIuTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageIuTest.php
index ff9c4d0d..01d97fc0 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageIuTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageIuTest.php
@@ -1,5 +1,9 @@
<?php
+/**
+ * @covers LanguageIu
+ * @covers IuConverter
+ */
class LanguageIuTest extends LanguageClassesTestCase {
/**
* @dataProvider provideAutoConvertToAllVariants
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageKkTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageKkTest.php
index a03eac22..f21950e0 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageKkTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageKkTest.php
@@ -1,5 +1,10 @@
<?php
+/**
+ * @covers LanguageKk
+ * @covers LanguageKk_cyrl
+ * @covers KkConverter
+ */
class LanguageKkTest extends LanguageClassesTestCase {
/**
* @dataProvider provideAutoConvertToAllVariants
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageKshTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageKshTest.php
index f77c5b62..6419e281 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageKshTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageKshTest.php
@@ -5,7 +5,9 @@
* @file
*/
-/** Tests for MediaWiki languages/classes/LanguageKsh.php */
+/**
+ * @covers LanguageKsh
+ */
class LanguageKshTest extends LanguageClassesTestCase {
/**
* @dataProvider providePlural
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageKuTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageKuTest.php
index 797ab4a4..db693088 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageKuTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageKuTest.php
@@ -1,5 +1,9 @@
<?php
+/**
+ * @covers LanguageKu
+ * @covers KuConverter
+ */
class LanguageKuTest extends LanguageClassesTestCase {
/**
* @dataProvider provideAutoConvertToAllVariants
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageMlTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageMlTest.php
index 6bac031d..673b5c77 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageMlTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageMlTest.php
@@ -5,7 +5,9 @@
* @file
*/
-/** Tests for MediaWiki languages/LanguageMl.php */
+/**
+ * @covers LanguageMl
+ */
class LanguageMlTest extends LanguageClassesTestCase {
/**
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguagePlTest.php b/www/wiki/tests/phpunit/languages/classes/LanguagePlTest.php
index a6d0f2e1..14877290 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguagePlTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguagePlTest.php
@@ -74,4 +74,31 @@ class LanguagePlTest extends LanguageClassesTestCase {
[ 'other', 201 ],
];
}
+
+ /**
+ * @covers Language::commafy()
+ * @dataProvider provideCommafyData
+ */
+ public function testCommafy( $number, $numbersWithCommas ) {
+ $this->assertEquals(
+ $numbersWithCommas,
+ $this->getLang()->commafy( $number ),
+ "commafy('$number')"
+ );
+ }
+
+ public static function provideCommafyData() {
+ // Note that commafy() always uses English separators (',' and '.') instead of
+ // Polish (' ' and ','). There is another function that converts them later.
+ return [
+ [ 1000, '1000' ],
+ [ 10000, '10,000' ],
+ [ 1000.0001, '1000.0001' ],
+ [ 10000.0001, '10,000.0001' ],
+ [ -1000, '-1000' ],
+ [ -10000, '-10,000' ],
+ [ -1000.0001, '-1000.0001' ],
+ [ -10000.0001, '-10,000.0001' ],
+ ];
+ }
}
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageRuTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageRuTest.php
index a76293c1..a34c03fd 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageRuTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageRuTest.php
@@ -6,7 +6,6 @@
* @file
*/
-/** Tests for MediaWiki languages/classes/LanguageRu.php */
class LanguageRuTest extends LanguageClassesTestCase {
/**
* @dataProvider providePlural
@@ -101,6 +100,11 @@ class LanguageRuTest extends LanguageClassesTestCase {
'genitive',
],
[
+ 'Викиверситета',
+ 'Викиверситет',
+ 'genitive',
+ ],
+ [
'Викискладе',
'Викисклад',
'prepositional',
@@ -111,6 +115,11 @@ class LanguageRuTest extends LanguageClassesTestCase {
'prepositional',
],
[
+ 'Викиверситете',
+ 'Викиверситет',
+ 'prepositional',
+ ],
+ [
'русского',
'русский',
'languagegen',
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageShiTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageShiTest.php
index 207a1b0b..1d0f8635 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageShiTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageShiTest.php
@@ -1,5 +1,9 @@
<?php
+/**
+ * @covers LanguageShi
+ * @covers ShiConverter
+ */
class LanguageShiTest extends LanguageClassesTestCase {
/**
* @dataProvider provideAutoConvertToAllVariants
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageSlTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageSlTest.php
index ed138c50..50100ce7 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageSlTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageSlTest.php
@@ -6,7 +6,9 @@
* @file
*/
-/** Tests for MediaWiki languages/classes/LanguageSl.php */
+/**
+ * @covers LanguageSl
+ */
class LanguageSlTest extends LanguageClassesTestCase {
/**
* @dataProvider providerPlural
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageSrTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageSrTest.php
index b64fd679..e81d5370 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageSrTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageSrTest.php
@@ -16,7 +16,10 @@
* - Tests for LanguageConverter and Language should probably be separate..
*/
-/** Tests for MediaWiki languages/LanguageSr.php */
+/**
+ * @covers LanguageSr
+ * @covers SrConverter
+ */
class LanguageSrTest extends LanguageClassesTestCase {
/**
* @covers LanguageConverter::convertTo
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageTgTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageTgTest.php
index 0ed24ff1..89697675 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageTgTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageTgTest.php
@@ -1,5 +1,9 @@
<?php
+/**
+ * @covers LanguageTg
+ * @covers TgConverter
+ */
class LanguageTgTest extends LanguageClassesTestCase {
/**
* @dataProvider provideAutoConvertToAllVariants
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageTrTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageTrTest.php
index 28d71df7..3ddf2d03 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageTrTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageTrTest.php
@@ -5,7 +5,9 @@
* @file
*/
-/** Tests for MediaWiki languages/LanguageTr.php */
+/**
+ * @covers LanguageTr
+ */
class LanguageTrTest extends LanguageClassesTestCase {
/**
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageUkTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageUkTest.php
index 6b259823..0ccebbe2 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageUkTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageUkTest.php
@@ -6,7 +6,6 @@
* @file
*/
-/** Tests for Ukrainian */
class LanguageUkTest extends LanguageClassesTestCase {
/**
* @dataProvider providePlural
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageUzTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageUzTest.php
index 7ef87bf4..367226d7 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageUzTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageUzTest.php
@@ -16,7 +16,10 @@
* - Tests for LanguageConverter and Language should probably be separate..
*/
-/** Tests for MediaWiki languages/LanguageUz.php */
+/**
+ * @covers LanguageUz
+ * @covers UzConverter
+ */
class LanguageUzTest extends LanguageClassesTestCase {
/**
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageWaTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageWaTest.php
index 27e57f64..80c98603 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageWaTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageWaTest.php
@@ -5,7 +5,9 @@
* @file
*/
-/** Tests for MediaWiki languages/classes/LanguageWa.php */
+/**
+ * @covers LanguageWa
+ */
class LanguageWaTest extends LanguageClassesTestCase {
/**
* @dataProvider providePlural
diff --git a/www/wiki/tests/phpunit/languages/classes/LanguageZhTest.php b/www/wiki/tests/phpunit/languages/classes/LanguageZhTest.php
index 26edc90a..2e73ac51 100644
--- a/www/wiki/tests/phpunit/languages/classes/LanguageZhTest.php
+++ b/www/wiki/tests/phpunit/languages/classes/LanguageZhTest.php
@@ -1,5 +1,10 @@
<?php
+/**
+ * @covers LanguageZh
+ * @covers LanguageZh_hans
+ * @covers ZhConverter
+ */
class LanguageZhTest extends LanguageClassesTestCase {
/**
* @dataProvider provideAutoConvertToAllVariants
diff --git a/www/wiki/tests/phpunit/maintenance/BenchmarkerTest.php b/www/wiki/tests/phpunit/maintenance/BenchmarkerTest.php
new file mode 100644
index 00000000..c15d789d
--- /dev/null
+++ b/www/wiki/tests/phpunit/maintenance/BenchmarkerTest.php
@@ -0,0 +1,142 @@
+<?php
+
+namespace MediaWiki\Tests\Maintenance;
+
+use Benchmarker;
+use MediaWikiCoversValidator;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @covers Benchmarker
+ */
+class BenchmarkerTest extends \PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
+ public function testBenchSimple() {
+ $bench = $this->getMockBuilder( Benchmarker::class )
+ ->setMethods( [ 'execute', 'output' ] )
+ ->getMock();
+ $benchProxy = TestingAccessWrapper::newFromObject( $bench );
+ $benchProxy->defaultCount = 3;
+
+ $count = 0;
+ $bench->bench( [
+ 'test' => function () use ( &$count ) {
+ $count++;
+ }
+ ] );
+
+ $this->assertSame( 3, $count );
+ }
+
+ public function testBenchSetup() {
+ $bench = $this->getMockBuilder( Benchmarker::class )
+ ->setMethods( [ 'execute', 'output' ] )
+ ->getMock();
+ $benchProxy = TestingAccessWrapper::newFromObject( $bench );
+ $benchProxy->defaultCount = 2;
+
+ $buffer = [];
+ $bench->bench( [
+ 'test' => [
+ 'setup' => function () use ( &$buffer ) {
+ $buffer[] = 'setup';
+ },
+ 'function' => function () use ( &$buffer ) {
+ $buffer[] = 'run';
+ }
+ ]
+ ] );
+
+ $this->assertSame( [ 'setup', 'run', 'run' ], $buffer );
+ }
+
+ public function testBenchVerbose() {
+ $bench = $this->getMockBuilder( Benchmarker::class )
+ ->setMethods( [ 'execute', 'output', 'hasOption', 'verboseRun' ] )
+ ->getMock();
+ $benchProxy = TestingAccessWrapper::newFromObject( $bench );
+ $benchProxy->defaultCount = 1;
+
+ $bench->expects( $this->exactly( 2 ) )->method( 'hasOption' )
+ ->will( $this->returnValueMap( [
+ [ 'verbose', true ],
+ [ 'count', false ],
+ ] ) );
+
+ $bench->expects( $this->once() )->method( 'verboseRun' )
+ ->with( 0 )
+ ->willReturn( null );
+
+ $bench->bench( [
+ 'test' => function () {
+ }
+ ] );
+ }
+
+ public function noop() {
+ }
+
+ public function testBenchName_method() {
+ $bench = $this->getMockBuilder( Benchmarker::class )
+ ->setMethods( [ 'execute', 'output', 'addResult' ] )
+ ->getMock();
+ $benchProxy = TestingAccessWrapper::newFromObject( $bench );
+ $benchProxy->defaultCount = 1;
+
+ $bench->expects( $this->once() )->method( 'addResult' )
+ ->with( $this->callback( function ( $res ) {
+ return isset( $res['name'] ) && $res['name'] === __CLASS__ . '::noop()';
+ } ) );
+
+ $bench->bench( [
+ [ 'function' => [ $this, 'noop' ] ]
+ ] );
+ }
+
+ public function testBenchName_string() {
+ $bench = $this->getMockBuilder( Benchmarker::class )
+ ->setMethods( [ 'execute', 'output', 'addResult' ] )
+ ->getMock();
+ $benchProxy = TestingAccessWrapper::newFromObject( $bench );
+ $benchProxy->defaultCount = 1;
+
+ $bench->expects( $this->once() )->method( 'addResult' )
+ ->with( $this->callback( function ( $res ) {
+ return 'strtolower(A)';
+ } ) );
+
+ $bench->bench( [ [
+ 'function' => 'strtolower',
+ 'args' => [ 'A' ],
+ ] ] );
+ }
+
+ /**
+ * @covers Benchmarker::verboseRun
+ */
+ public function testVerboseRun() {
+ $bench = $this->getMockBuilder( Benchmarker::class )
+ ->setMethods( [ 'execute', 'output', 'hasOption', 'startBench', 'addResult' ] )
+ ->getMock();
+ $benchProxy = TestingAccessWrapper::newFromObject( $bench );
+ $benchProxy->defaultCount = 1;
+
+ $bench->expects( $this->exactly( 2 ) )->method( 'hasOption' )
+ ->will( $this->returnValueMap( [
+ [ 'verbose', true ],
+ [ 'count', false ],
+ ] ) );
+
+ $bench->expects( $this->once() )->method( 'output' )
+ ->with( $this->callback( function ( $out ) {
+ return preg_match( '/memory.+ peak/', $out ) === 1;
+ } ) );
+
+ $bench->bench( [
+ 'test' => function () {
+ }
+ ] );
+ }
+}
diff --git a/www/wiki/tests/phpunit/maintenance/DumpTestCase.php b/www/wiki/tests/phpunit/maintenance/DumpTestCase.php
index 99bd4277..9b90bfe6 100644
--- a/www/wiki/tests/phpunit/maintenance/DumpTestCase.php
+++ b/www/wiki/tests/phpunit/maintenance/DumpTestCase.php
@@ -1,5 +1,15 @@
<?php
+namespace MediaWiki\Tests\Maintenance;
+
+use ContentHandler;
+use ExecutableFinder;
+use MediaWikiLangTestCase;
+use Page;
+use User;
+use XMLReader;
+use MWException;
+
/**
* Base TestCase for dumps
*/
@@ -35,7 +45,7 @@ abstract class DumpTestCase extends MediaWikiLangTestCase {
*/
protected function checkHasGzip() {
if ( self::$hasGzip === null ) {
- self::$hasGzip = ( Installer::locateExecutableInDefaultPaths( 'gzip' ) !== false );
+ self::$hasGzip = ( ExecutableFinder::findInDefaultPaths( 'gzip' ) !== false );
}
if ( !self::$hasGzip ) {
diff --git a/www/wiki/tests/phpunit/maintenance/MaintenanceBaseTestCase.php b/www/wiki/tests/phpunit/maintenance/MaintenanceBaseTestCase.php
new file mode 100644
index 00000000..bdcf7e5f
--- /dev/null
+++ b/www/wiki/tests/phpunit/maintenance/MaintenanceBaseTestCase.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace MediaWiki\Tests\Maintenance;
+
+use Maintenance;
+use MediaWikiTestCase;
+use Wikimedia\TestingAccessWrapper;
+
+abstract class MaintenanceBaseTestCase extends MediaWikiTestCase {
+
+ /**
+ * The main Maintenance instance that is used for testing, wrapped and mockable.
+ *
+ * @var Maintenance
+ */
+ protected $maintenance;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->maintenance = $this->createMaintenance();
+ }
+
+ /**
+ * Do a little stream cleanup to prevent output in case the child class
+ * hasn't tested the capture buffer.
+ */
+ protected function tearDown() {
+ if ( $this->maintenance ) {
+ $this->maintenance->cleanupChanneled();
+ }
+
+ // This is smelly, but maintenance scripts usually produce output, so
+ // we anticipate and ignore with a regex that will catch everything.
+ //
+ // If you call $this->expectOutputRegex in your subclass, this guard
+ // won't be triggered, and your specific pattern will be respected.
+ if ( !$this->hasExpectationOnOutput() ) {
+ $this->expectOutputRegex( '/.*/' );
+ }
+
+ parent::tearDown();
+ }
+
+ /**
+ * @return string Class name
+ *
+ * Subclasses must implement this in order to use the $this->maintenance
+ * variable. Normally, it will be set like:
+ * return PopulateDatabaseMaintenance::class;
+ *
+ * If you need to change the way your maintenance class is constructed,
+ * override createMaintenance.
+ */
+ abstract protected function getMaintenanceClass();
+
+ /**
+ * Called by setUp to initialize $this->maintenance.
+ *
+ * @return object The Maintenance instance to test.
+ */
+ protected function createMaintenance() {
+ $className = $this->getMaintenanceClass();
+ $obj = new $className();
+
+ // We use TestingAccessWrapper in order to access protected internals
+ // such as `output()`.
+ return TestingAccessWrapper::newFromObject( $obj );
+ }
+
+ /**
+ * Asserts the output before and after simulating shutdown
+ *
+ * This function simulates shutdown of self::maintenance.
+ *
+ * @param string $preShutdownOutput Expected output before simulating shutdown
+ * @param bool $expectNLAppending Whether or not shutdown simulation is expected
+ * to add a newline to the output. If false, $preShutdownOutput is the
+ * expected output after shutdown simulation. Otherwise,
+ * $preShutdownOutput with an appended newline is the expected output
+ * after shutdown simulation.
+ */
+ protected function assertOutputPrePostShutdown( $preShutdownOutput, $expectNLAppending ) {
+ $this->assertEquals( $preShutdownOutput, $this->getActualOutput(),
+ "Output before shutdown simulation" );
+
+ $this->maintenance->cleanupChanneled();
+
+ $postShutdownOutput = $preShutdownOutput . ( $expectNLAppending ? "\n" : "" );
+ $this->expectOutputString( $postShutdownOutput );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/maintenance/MaintenanceTest.php b/www/wiki/tests/phpunit/maintenance/MaintenanceTest.php
index e21e7269..141561f0 100644
--- a/www/wiki/tests/phpunit/maintenance/MaintenanceTest.php
+++ b/www/wiki/tests/phpunit/maintenance/MaintenanceTest.php
@@ -1,187 +1,35 @@
<?php
-// It would be great if we were able to use PHPUnit's getMockForAbstractClass
-// instead of the MaintenanceFixup hack below. However, we cannot do
-// without changing the visibility and without working around hacks in
-// Maintenance.php
-// For the same reason, we cannot just use FakeMaintenance.
-use MediaWiki\MediaWikiServices;
-
-/**
- * makes parts of the API of Maintenance that is hidden by protected visibily
- * visible for testing, and makes up for a stream closing hack in Maintenance.php.
- *
- * This class is solely used for being able to test Maintenance right now
- * without having to apply major refactorings to fix some design issues in
- * Maintenance.php. Before adding more functions here, please consider whether
- * this approach is correct, or a refactoring Maintenance to separate concers
- * is more appropriate.
- *
- * Upon refactoring, keep in mind that besides the maintenance scrits themselves
- * and tests right here, also at least Extension:Maintenance make use of
- * Maintenance.
- *
- * Due to a hack in Maintenance.php using register_shutdown_function, be sure to
- * finally call simulateShutdown on MaintenanceFixup instance before a test
- * ends.
- */
-class MaintenanceFixup extends Maintenance {
-
- // --- Making up for the register_shutdown_function hack in Maintenance.php
-
- /**
- * The test case that generated this instance.
- *
- * This member is motivated by allowing the destructor to check whether or not
- * the test failed, in order to avoid unnecessary nags about omitted shutdown
- * simulation.
- * But as it is already available, we also usi it to flagging tests as failed
- *
- * @var MediaWikiTestCase
- */
- private $testCase;
-
- /**
- * shutdownSimulated === true if simulateShutdown has done it's work
- *
- * @var bool
- */
- private $shutdownSimulated = false;
-
- /**
- * Simulates what Maintenance wants to happen at script's end.
- */
- public function simulateShutdown() {
- if ( $this->shutdownSimulated ) {
- $this->testCase->fail( __METHOD__ . " called more than once" );
- }
-
- // The cleanup action.
- $this->outputChanneled( false );
-
- // Bookkeeping that we simulated the clean up.
- $this->shutdownSimulated = true;
- }
-
- // Note that the "public" here does not change visibility
- public function outputChanneled( $msg, $channel = null ) {
- if ( $this->shutdownSimulated ) {
- if ( $msg !== false ) {
- $this->testCase->fail( "Already past simulated shutdown, but msg is "
- . "not false. Did the hack in Maintenance.php change? Please "
- . "adapt the test case or Maintenance.php" );
- }
+namespace MediaWiki\Tests\Maintenance;
- // The current call is the one registered via register_shutdown_function.
- // We can safely ignore it, as we simulated this one via simulateShutdown
- // before (if we did not, the destructor of this instance will warn about
- // it)
- return;
- }
-
- call_user_func_array( [ "parent", __FUNCTION__ ], func_get_args() );
- }
-
- /**
- * Safety net around register_shutdown_function of Maintenance.php
- */
- public function __destruct() {
- if ( !$this->shutdownSimulated ) {
- // Someone generated a MaintenanceFixup instance without calling
- // simulateShutdown. We'd have to raise a PHPUnit exception to correctly
- // flag this illegal usage. However, we are already in a destruktor, which
- // would trigger undefined behavior. Hence, we can only report to the
- // error output :( Hopefully people read the PHPUnit output.
- $name = $this->testCase->getName();
- fwrite( STDERR, "ERROR! Instance of " . __CLASS__ . " for test $name "
- . "destructed without calling simulateShutdown method. Call "
- . "simulateShutdown on the instance before it gets destructed." );
- }
-
- // The following guard is required, as PHP does not offer default destructors :(
- if ( is_callable( "parent::__destruct" ) ) {
- parent::__destruct();
- }
- }
-
- public function __construct( MediaWikiTestCase $testCase ) {
- parent::__construct();
- $this->testCase = $testCase;
- }
-
- // --- Making protected functions visible for test
-
- public function output( $out, $channel = null ) {
- // Just to make PHP not nag about signature mismatches, we copied
- // Maintenance::output signature. However, we do not use (or rely on)
- // those variables. Instead we pass to Maintenance::output whatever we
- // receive at runtime.
- return call_user_func_array( [ "parent", __FUNCTION__ ], func_get_args() );
- }
-
- public function addOption( $name, $description, $required = false,
- $withArg = false, $shortName = false, $multiOccurance = false
- ) {
- return call_user_func_array( [ "parent", __FUNCTION__ ], func_get_args() );
- }
-
- public function getOption( $name, $default = null ) {
- return call_user_func_array( [ "parent", __FUNCTION__ ], func_get_args() );
- }
-
- // --- Requirements for getting instance of abstract class
-
- public function execute() {
- $this->testCase->fail( __METHOD__ . " called unexpectedly" );
- }
-}
+use Maintenance;
+use MediaWiki\MediaWikiServices;
+use Wikimedia\TestingAccessWrapper;
/**
* @covers Maintenance
*/
-class MaintenanceTest extends MediaWikiTestCase {
+class MaintenanceTest extends MaintenanceBaseTestCase {
/**
- * The main Maintenance instance that is used for testing.
- *
- * @var MaintenanceFixup
+ * @see MaintenanceBaseTestCase::getMaintenanceClass
*/
- private $m;
-
- protected function setUp() {
- parent::setUp();
- $this->m = new MaintenanceFixup( $this );
- }
-
- protected function tearDown() {
- if ( $this->m ) {
- $this->m->simulateShutdown();
- $this->m = null;
- }
- parent::tearDown();
+ protected function getMaintenanceClass() {
+ return Maintenance::class;
}
/**
- * asserts the output before and after simulating shutdown
+ * @see MaintenanceBaseTestCase::createMaintenance
*
- * This function simulates shutdown of self::m.
- *
- * @param string $preShutdownOutput Expected output before simulating shutdown
- * @param bool $expectNLAppending Whether or not shutdown simulation is expected
- * to add a newline to the output. If false, $preShutdownOutput is the
- * expected output after shutdown simulation. Otherwise,
- * $preShutdownOutput with an appended newline is the expected output
- * after shutdown simulation.
+ * Note to extension authors looking for a model to follow: This function
+ * is normally not needed in a maintenance test, it's only overridden here
+ * because Maintenance is abstract.
*/
- private function assertOutputPrePostShutdown( $preShutdownOutput, $expectNLAppending ) {
- $this->assertEquals( $preShutdownOutput, $this->getActualOutput(),
- "Output before shutdown simulation" );
-
- $this->m->simulateShutdown();
- $this->m = null;
+ protected function createMaintenance() {
+ $className = $this->getMaintenanceClass();
+ $obj = $this->getMockForAbstractClass( $className );
- $postShutdownOutput = $preShutdownOutput . ( $expectNLAppending ? "\n" : "" );
- $this->expectOutputString( $postShutdownOutput );
+ return TestingAccessWrapper::newFromObject( $obj );
}
// Although the following tests do not seem to be too consistent (compare for
@@ -189,632 +37,445 @@ class MaintenanceTest extends MediaWikiTestCase {
// test.*Intermittent.* tests), the objective of these tests is not to describe
// consistent behavior, but rather currently existing behavior.
- function testOutputEmpty() {
- $this->m->output( "" );
- $this->assertOutputPrePostShutdown( "", false );
- }
-
- function testOutputString() {
- $this->m->output( "foo" );
- $this->assertOutputPrePostShutdown( "foo", false );
- }
-
- function testOutputStringString() {
- $this->m->output( "foo" );
- $this->m->output( "bar" );
- $this->assertOutputPrePostShutdown( "foobar", false );
- }
-
- function testOutputStringNL() {
- $this->m->output( "foo\n" );
- $this->assertOutputPrePostShutdown( "foo\n", false );
- }
-
- function testOutputStringNLNL() {
- $this->m->output( "foo\n\n" );
- $this->assertOutputPrePostShutdown( "foo\n\n", false );
- }
-
- function testOutputStringNLString() {
- $this->m->output( "foo\nbar" );
- $this->assertOutputPrePostShutdown( "foo\nbar", false );
- }
-
- function testOutputStringNLStringNL() {
- $this->m->output( "foo\nbar\n" );
- $this->assertOutputPrePostShutdown( "foo\nbar\n", false );
- }
-
- function testOutputStringNLStringNLLinewise() {
- $this->m->output( "foo\n" );
- $this->m->output( "bar\n" );
- $this->assertOutputPrePostShutdown( "foo\nbar\n", false );
- }
-
- function testOutputStringNLStringNLArbitrary() {
- $this->m->output( "" );
- $this->m->output( "foo" );
- $this->m->output( "" );
- $this->m->output( "\n" );
- $this->m->output( "ba" );
- $this->m->output( "" );
- $this->m->output( "r\n" );
- $this->assertOutputPrePostShutdown( "foo\nbar\n", false );
- }
-
- function testOutputStringNLStringNLArbitraryAgain() {
- $this->m->output( "" );
- $this->m->output( "foo" );
- $this->m->output( "" );
- $this->m->output( "\nb" );
- $this->m->output( "a" );
- $this->m->output( "" );
- $this->m->output( "r\n" );
- $this->assertOutputPrePostShutdown( "foo\nbar\n", false );
- }
-
- function testOutputWNullChannelEmpty() {
- $this->m->output( "", null );
- $this->assertOutputPrePostShutdown( "", false );
- }
-
- function testOutputWNullChannelString() {
- $this->m->output( "foo", null );
- $this->assertOutputPrePostShutdown( "foo", false );
- }
-
- function testOutputWNullChannelStringString() {
- $this->m->output( "foo", null );
- $this->m->output( "bar", null );
- $this->assertOutputPrePostShutdown( "foobar", false );
- }
-
- function testOutputWNullChannelStringNL() {
- $this->m->output( "foo\n", null );
- $this->assertOutputPrePostShutdown( "foo\n", false );
- }
-
- function testOutputWNullChannelStringNLNL() {
- $this->m->output( "foo\n\n", null );
- $this->assertOutputPrePostShutdown( "foo\n\n", false );
- }
-
- function testOutputWNullChannelStringNLString() {
- $this->m->output( "foo\nbar", null );
- $this->assertOutputPrePostShutdown( "foo\nbar", false );
- }
-
- function testOutputWNullChannelStringNLStringNL() {
- $this->m->output( "foo\nbar\n", null );
- $this->assertOutputPrePostShutdown( "foo\nbar\n", false );
- }
-
- function testOutputWNullChannelStringNLStringNLLinewise() {
- $this->m->output( "foo\n", null );
- $this->m->output( "bar\n", null );
- $this->assertOutputPrePostShutdown( "foo\nbar\n", false );
- }
-
- function testOutputWNullChannelStringNLStringNLArbitrary() {
- $this->m->output( "", null );
- $this->m->output( "foo", null );
- $this->m->output( "", null );
- $this->m->output( "\n", null );
- $this->m->output( "ba", null );
- $this->m->output( "", null );
- $this->m->output( "r\n", null );
- $this->assertOutputPrePostShutdown( "foo\nbar\n", false );
- }
-
- function testOutputWNullChannelStringNLStringNLArbitraryAgain() {
- $this->m->output( "", null );
- $this->m->output( "foo", null );
- $this->m->output( "", null );
- $this->m->output( "\nb", null );
- $this->m->output( "a", null );
- $this->m->output( "", null );
- $this->m->output( "r\n", null );
- $this->assertOutputPrePostShutdown( "foo\nbar\n", false );
- }
-
- function testOutputWChannelString() {
- $this->m->output( "foo", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foo", true );
- }
-
- function testOutputWChannelStringNL() {
- $this->m->output( "foo\n", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foo", true );
- }
-
- function testOutputWChannelStringNLNL() {
- // If this test fails, note that output takes strings with double line
- // endings (although output's implementation in this situation calls
- // outputChanneled with a string ending in a nl ... which is not allowed
- // according to the documentation of outputChanneled)
- $this->m->output( "foo\n\n", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foo\n", true );
- }
-
- function testOutputWChannelStringNLString() {
- $this->m->output( "foo\nbar", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foo\nbar", true );
- }
-
- function testOutputWChannelStringNLStringNL() {
- $this->m->output( "foo\nbar\n", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foo\nbar", true );
- }
-
- function testOutputWChannelStringNLStringNLLinewise() {
- $this->m->output( "foo\n", "bazChannel" );
- $this->m->output( "bar\n", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foobar", true );
- }
-
- function testOutputWChannelStringNLStringNLArbitrary() {
- $this->m->output( "", "bazChannel" );
- $this->m->output( "foo", "bazChannel" );
- $this->m->output( "", "bazChannel" );
- $this->m->output( "\n", "bazChannel" );
- $this->m->output( "ba", "bazChannel" );
- $this->m->output( "", "bazChannel" );
- $this->m->output( "r\n", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foobar", true );
- }
-
- function testOutputWChannelStringNLStringNLArbitraryAgain() {
- $this->m->output( "", "bazChannel" );
- $this->m->output( "foo", "bazChannel" );
- $this->m->output( "", "bazChannel" );
- $this->m->output( "\nb", "bazChannel" );
- $this->m->output( "a", "bazChannel" );
- $this->m->output( "", "bazChannel" );
- $this->m->output( "r\n", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foo\nbar", true );
- }
-
- function testOutputWMultipleChannelsChannelChange() {
- $this->m->output( "foo", "bazChannel" );
- $this->m->output( "bar", "bazChannel" );
- $this->m->output( "qux", "quuxChannel" );
- $this->m->output( "corge", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", true );
- }
-
- function testOutputWMultipleChannelsChannelChangeNL() {
- $this->m->output( "foo", "bazChannel" );
- $this->m->output( "bar\n", "bazChannel" );
- $this->m->output( "qux\n", "quuxChannel" );
- $this->m->output( "corge", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", true );
- }
-
- function testOutputWAndWOChannelStringStartWO() {
- $this->m->output( "foo" );
- $this->m->output( "bar", "bazChannel" );
- $this->m->output( "qux" );
- $this->m->output( "quux", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foobar\nquxquux", true );
- }
-
- function testOutputWAndWOChannelStringStartW() {
- $this->m->output( "foo", "bazChannel" );
- $this->m->output( "bar" );
- $this->m->output( "qux", "bazChannel" );
- $this->m->output( "quux" );
- $this->assertOutputPrePostShutdown( "foo\nbarqux\nquux", false );
- }
-
- function testOutputWChannelTypeSwitch() {
- $this->m->output( "foo", 1 );
- $this->m->output( "bar", 1.0 );
- $this->assertOutputPrePostShutdown( "foo\nbar", true );
- }
-
- function testOutputIntermittentEmpty() {
- $this->m->output( "foo" );
- $this->m->output( "" );
- $this->m->output( "bar" );
- $this->assertOutputPrePostShutdown( "foobar", false );
- }
-
- function testOutputIntermittentFalse() {
- $this->m->output( "foo" );
- $this->m->output( false );
- $this->m->output( "bar" );
- $this->assertOutputPrePostShutdown( "foobar", false );
- }
-
- function testOutputIntermittentFalseAfterOtherChannel() {
- $this->m->output( "qux", "quuxChannel" );
- $this->m->output( "foo" );
- $this->m->output( false );
- $this->m->output( "bar" );
- $this->assertOutputPrePostShutdown( "qux\nfoobar", false );
- }
-
- function testOutputWNullChannelIntermittentEmpty() {
- $this->m->output( "foo", null );
- $this->m->output( "", null );
- $this->m->output( "bar", null );
- $this->assertOutputPrePostShutdown( "foobar", false );
- }
-
- function testOutputWNullChannelIntermittentFalse() {
- $this->m->output( "foo", null );
- $this->m->output( false, null );
- $this->m->output( "bar", null );
- $this->assertOutputPrePostShutdown( "foobar", false );
- }
-
- function testOutputWChannelIntermittentEmpty() {
- $this->m->output( "foo", "bazChannel" );
- $this->m->output( "", "bazChannel" );
- $this->m->output( "bar", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foobar", true );
- }
-
- function testOutputWChannelIntermittentFalse() {
- $this->m->output( "foo", "bazChannel" );
- $this->m->output( false, "bazChannel" );
- $this->m->output( "bar", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foobar", true );
- }
-
- // Note that (per documentation) outputChanneled does take strings that end
- // in \n, hence we do not test such strings.
-
- function testOutputChanneledEmpty() {
- $this->m->outputChanneled( "" );
- $this->assertOutputPrePostShutdown( "\n", false );
- }
-
- function testOutputChanneledString() {
- $this->m->outputChanneled( "foo" );
- $this->assertOutputPrePostShutdown( "foo\n", false );
- }
-
- function testOutputChanneledStringString() {
- $this->m->outputChanneled( "foo" );
- $this->m->outputChanneled( "bar" );
- $this->assertOutputPrePostShutdown( "foo\nbar\n", false );
- }
-
- function testOutputChanneledStringNLString() {
- $this->m->outputChanneled( "foo\nbar" );
- $this->assertOutputPrePostShutdown( "foo\nbar\n", false );
- }
-
- function testOutputChanneledStringNLStringNLArbitraryAgain() {
- $this->m->outputChanneled( "" );
- $this->m->outputChanneled( "foo" );
- $this->m->outputChanneled( "" );
- $this->m->outputChanneled( "\nb" );
- $this->m->outputChanneled( "a" );
- $this->m->outputChanneled( "" );
- $this->m->outputChanneled( "r" );
- $this->assertOutputPrePostShutdown( "\nfoo\n\n\nb\na\n\nr\n", false );
- }
-
- function testOutputChanneledWNullChannelEmpty() {
- $this->m->outputChanneled( "", null );
- $this->assertOutputPrePostShutdown( "\n", false );
- }
-
- function testOutputChanneledWNullChannelString() {
- $this->m->outputChanneled( "foo", null );
- $this->assertOutputPrePostShutdown( "foo\n", false );
- }
-
- function testOutputChanneledWNullChannelStringString() {
- $this->m->outputChanneled( "foo", null );
- $this->m->outputChanneled( "bar", null );
- $this->assertOutputPrePostShutdown( "foo\nbar\n", false );
- }
-
- function testOutputChanneledWNullChannelStringNLString() {
- $this->m->outputChanneled( "foo\nbar", null );
- $this->assertOutputPrePostShutdown( "foo\nbar\n", false );
- }
-
- function testOutputChanneledWNullChannelStringNLStringNLArbitraryAgain() {
- $this->m->outputChanneled( "", null );
- $this->m->outputChanneled( "foo", null );
- $this->m->outputChanneled( "", null );
- $this->m->outputChanneled( "\nb", null );
- $this->m->outputChanneled( "a", null );
- $this->m->outputChanneled( "", null );
- $this->m->outputChanneled( "r", null );
- $this->assertOutputPrePostShutdown( "\nfoo\n\n\nb\na\n\nr\n", false );
- }
-
- function testOutputChanneledWChannelString() {
- $this->m->outputChanneled( "foo", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foo", true );
- }
-
- function testOutputChanneledWChannelStringNLString() {
- $this->m->outputChanneled( "foo\nbar", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foo\nbar", true );
- }
-
- function testOutputChanneledWChannelStringString() {
- $this->m->outputChanneled( "foo", "bazChannel" );
- $this->m->outputChanneled( "bar", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foobar", true );
- }
-
- function testOutputChanneledWChannelStringNLStringNLArbitraryAgain() {
- $this->m->outputChanneled( "", "bazChannel" );
- $this->m->outputChanneled( "foo", "bazChannel" );
- $this->m->outputChanneled( "", "bazChannel" );
- $this->m->outputChanneled( "\nb", "bazChannel" );
- $this->m->outputChanneled( "a", "bazChannel" );
- $this->m->outputChanneled( "", "bazChannel" );
- $this->m->outputChanneled( "r", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foo\nbar", true );
- }
-
- function testOutputChanneledWMultipleChannelsChannelChange() {
- $this->m->outputChanneled( "foo", "bazChannel" );
- $this->m->outputChanneled( "bar", "bazChannel" );
- $this->m->outputChanneled( "qux", "quuxChannel" );
- $this->m->outputChanneled( "corge", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", true );
- }
-
- function testOutputChanneledWMultipleChannelsChannelChangeEnclosedNull() {
- $this->m->outputChanneled( "foo", "bazChannel" );
- $this->m->outputChanneled( "bar", null );
- $this->m->outputChanneled( "qux", null );
- $this->m->outputChanneled( "corge", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foo\nbar\nqux\ncorge", true );
- }
-
- function testOutputChanneledWMultipleChannelsChannelAfterNullChange() {
- $this->m->outputChanneled( "foo", "bazChannel" );
- $this->m->outputChanneled( "bar", null );
- $this->m->outputChanneled( "qux", null );
- $this->m->outputChanneled( "corge", "quuxChannel" );
- $this->assertOutputPrePostShutdown( "foo\nbar\nqux\ncorge", true );
- }
-
- function testOutputChanneledWAndWOChannelStringStartWO() {
- $this->m->outputChanneled( "foo" );
- $this->m->outputChanneled( "bar", "bazChannel" );
- $this->m->outputChanneled( "qux" );
- $this->m->outputChanneled( "quux", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foo\nbar\nqux\nquux", true );
- }
-
- function testOutputChanneledWAndWOChannelStringStartW() {
- $this->m->outputChanneled( "foo", "bazChannel" );
- $this->m->outputChanneled( "bar" );
- $this->m->outputChanneled( "qux", "bazChannel" );
- $this->m->outputChanneled( "quux" );
- $this->assertOutputPrePostShutdown( "foo\nbar\nqux\nquux\n", false );
- }
-
- function testOutputChanneledWChannelTypeSwitch() {
- $this->m->outputChanneled( "foo", 1 );
- $this->m->outputChanneled( "bar", 1.0 );
- $this->assertOutputPrePostShutdown( "foo\nbar", true );
- }
-
- function testOutputChanneledWOChannelIntermittentEmpty() {
- $this->m->outputChanneled( "foo" );
- $this->m->outputChanneled( "" );
- $this->m->outputChanneled( "bar" );
- $this->assertOutputPrePostShutdown( "foo\n\nbar\n", false );
- }
-
- function testOutputChanneledWOChannelIntermittentFalse() {
- $this->m->outputChanneled( "foo" );
- $this->m->outputChanneled( false );
- $this->m->outputChanneled( "bar" );
- $this->assertOutputPrePostShutdown( "foo\nbar\n", false );
- }
-
- function testOutputChanneledWNullChannelIntermittentEmpty() {
- $this->m->outputChanneled( "foo", null );
- $this->m->outputChanneled( "", null );
- $this->m->outputChanneled( "bar", null );
- $this->assertOutputPrePostShutdown( "foo\n\nbar\n", false );
- }
-
- function testOutputChanneledWNullChannelIntermittentFalse() {
- $this->m->outputChanneled( "foo", null );
- $this->m->outputChanneled( false, null );
- $this->m->outputChanneled( "bar", null );
- $this->assertOutputPrePostShutdown( "foo\nbar\n", false );
- }
-
- function testOutputChanneledWChannelIntermittentEmpty() {
- $this->m->outputChanneled( "foo", "bazChannel" );
- $this->m->outputChanneled( "", "bazChannel" );
- $this->m->outputChanneled( "bar", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foobar", true );
+ /**
+ * @dataProvider provideOutputData
+ */
+ function testOutput( $outputs, $expected, $extraNL ) {
+ foreach ( $outputs as $data ) {
+ if ( is_array( $data ) ) {
+ list( $msg, $channel ) = $data;
+ } else {
+ $msg = $data;
+ $channel = null;
+ }
+ $this->maintenance->output( $msg, $channel );
+ }
+ $this->assertOutputPrePostShutdown( $expected, $extraNL );
+ }
+
+ public function provideOutputData() {
+ return [
+ [ [ "" ], "", false ],
+ [ [ "foo" ], "foo", false ],
+ [ [ "foo", "bar" ], "foobar", false ],
+ [ [ "foo\n" ], "foo\n", false ],
+ [ [ "foo\n\n" ], "foo\n\n", false ],
+ [ [ "foo\nbar" ], "foo\nbar", false ],
+ [ [ "foo\nbar\n" ], "foo\nbar\n", false ],
+ [ [ "foo\n", "bar\n" ], "foo\nbar\n", false ],
+ [ [ "", "foo", "", "\n", "ba", "", "r\n" ], "foo\nbar\n", false ],
+ [ [ "", "foo", "", "\nb", "a", "", "r\n" ], "foo\nbar\n", false ],
+ [ [ [ "foo", "bazChannel" ] ], "foo", true ],
+ [ [ [ "foo\n", "bazChannel" ] ], "foo", true ],
+
+ // If this test fails, note that output takes strings with double line
+ // endings (although output's implementation in this situation calls
+ // outputChanneled with a string ending in a nl ... which is not allowed
+ // according to the documentation of outputChanneled)
+ [ [ [ "foo\n\n", "bazChannel" ] ], "foo\n", true ],
+ [ [ [ "foo\nbar", "bazChannel" ] ], "foo\nbar", true ],
+ [ [ [ "foo\nbar\n", "bazChannel" ] ], "foo\nbar", true ],
+ [
+ [
+ [ "foo\n", "bazChannel" ],
+ [ "bar\n", "bazChannel" ],
+ ],
+ "foobar",
+ true
+ ],
+ [
+ [
+ [ "", "bazChannel" ],
+ [ "foo", "bazChannel" ],
+ [ "", "bazChannel" ],
+ [ "\n", "bazChannel" ],
+ [ "ba", "bazChannel" ],
+ [ "", "bazChannel" ],
+ [ "r\n", "bazChannel" ],
+ ],
+ "foobar",
+ true
+ ],
+ [
+ [
+ [ "", "bazChannel" ],
+ [ "foo", "bazChannel" ],
+ [ "", "bazChannel" ],
+ [ "\nb", "bazChannel" ],
+ [ "a", "bazChannel" ],
+ [ "", "bazChannel" ],
+ [ "r\n", "bazChannel" ],
+ ],
+ "foo\nbar",
+ true
+ ],
+ [
+ [
+ [ "foo", "bazChannel" ],
+ [ "bar", "bazChannel" ],
+ [ "qux", "quuxChannel" ],
+ [ "corge", "bazChannel" ],
+ ],
+ "foobar\nqux\ncorge",
+ true
+ ],
+ [
+ [
+ [ "foo", "bazChannel" ],
+ [ "bar\n", "bazChannel" ],
+ [ "qux\n", "quuxChannel" ],
+ [ "corge", "bazChannel" ],
+ ],
+ "foobar\nqux\ncorge",
+ true
+ ],
+ [
+ [
+ [ "foo", null ],
+ [ "bar", "bazChannel" ],
+ [ "qux", null ],
+ [ "quux", "bazChannel" ],
+ ],
+ "foobar\nquxquux",
+ true
+ ],
+ [
+ [
+ [ "foo", "bazChannel" ],
+ [ "bar", null ],
+ [ "qux", "bazChannel" ],
+ [ "quux", null ],
+ ],
+ "foo\nbarqux\nquux",
+ false
+ ],
+ [
+ [
+ [ "foo", 1 ],
+ [ "bar", 1.0 ],
+ ],
+ "foo\nbar",
+ true
+ ],
+ [ [ "foo", "", "bar" ], "foobar", false ],
+ [ [ "foo", false, "bar" ], "foobar", false ],
+ [
+ [
+ [ "qux", "quuxChannel" ],
+ "foo",
+ false,
+ "bar"
+ ],
+ "qux\nfoobar",
+ false
+ ],
+ [
+ [
+ [ "foo", "bazChannel" ],
+ [ "", "bazChannel" ],
+ [ "bar", "bazChannel" ],
+ ],
+ "foobar",
+ true
+ ],
+ [
+ [
+ [ "foo", "bazChannel" ],
+ [ false, "bazChannel" ],
+ [ "bar", "bazChannel" ],
+ ],
+ "foobar",
+ true
+ ],
+ ];
}
- function testOutputChanneledWChannelIntermittentFalse() {
- $this->m->outputChanneled( "foo", "bazChannel" );
- $this->m->outputChanneled( false, "bazChannel" );
- $this->m->outputChanneled( "bar", "bazChannel" );
- $this->assertOutputPrePostShutdown( "foo\nbar", true );
+ /**
+ * @dataProvider provideOutputChanneledData
+ */
+ function testOutputChanneled( $outputs, $expected, $extraNL ) {
+ foreach ( $outputs as $data ) {
+ if ( is_array( $data ) ) {
+ list( $msg, $channel ) = $data;
+ } else {
+ $msg = $data;
+ $channel = null;
+ }
+ $this->maintenance->outputChanneled( $msg, $channel );
+ }
+ $this->assertOutputPrePostShutdown( $expected, $extraNL );
+ }
+
+ public function provideOutputChanneledData() {
+ return [
+ [ [ "" ], "\n", false ],
+ [ [ "foo" ], "foo\n", false ],
+ [ [ "foo", "bar" ], "foo\nbar\n", false ],
+ [ [ "foo\nbar" ], "foo\nbar\n", false ],
+ [ [ "", "foo", "", "\nb", "a", "", "r" ], "\nfoo\n\n\nb\na\n\nr\n", false ],
+ [ [ [ "foo", "bazChannel" ] ], "foo", true ],
+ [
+ [
+ [ "foo\nbar", "bazChannel" ]
+ ],
+ "foo\nbar",
+ true
+ ],
+ [
+ [
+ [ "foo", "bazChannel" ],
+ [ "bar", "bazChannel" ],
+ ],
+ "foobar",
+ true
+ ],
+ [
+ [
+ [ "", "bazChannel" ],
+ [ "foo", "bazChannel" ],
+ [ "", "bazChannel" ],
+ [ "\nb", "bazChannel" ],
+ [ "a", "bazChannel" ],
+ [ "", "bazChannel" ],
+ [ "r", "bazChannel" ],
+ ],
+ "foo\nbar",
+ true
+ ],
+ [
+ [
+ [ "foo", "bazChannel" ],
+ [ "bar", "bazChannel" ],
+ [ "qux", "quuxChannel" ],
+ [ "corge", "bazChannel" ],
+ ],
+ "foobar\nqux\ncorge",
+ true
+ ],
+ [
+ [
+ [ "foo", "bazChannel" ],
+ [ "bar", "bazChannel" ],
+ [ "qux", "quuxChannel" ],
+ [ "corge", "bazChannel" ],
+ ],
+ "foobar\nqux\ncorge",
+ true
+ ],
+ [
+ [
+ [ "foo", "bazChannel" ],
+ [ "bar", null ],
+ [ "qux", null ],
+ [ "corge", "bazChannel" ],
+ ],
+ "foo\nbar\nqux\ncorge",
+ true
+ ],
+ [
+ [
+ [ "foo", null ],
+ [ "bar", "bazChannel" ],
+ [ "qux", null ],
+ [ "quux", "bazChannel" ],
+ ],
+ "foo\nbar\nqux\nquux",
+ true
+ ],
+ [
+ [
+ [ "foo", "bazChannel" ],
+ [ "bar", null ],
+ [ "qux", "bazChannel" ],
+ [ "quux", null ],
+ ],
+ "foo\nbar\nqux\nquux\n",
+ false
+ ],
+ [
+ [
+ [ "foo", 1 ],
+ [ "bar", 1.0 ],
+ ],
+ "foo\nbar",
+ true
+ ],
+ [ [ "foo", "", "bar" ], "foo\n\nbar\n", false ],
+ [ [ "foo", false, "bar" ], "foo\nbar\n", false ],
+ ];
}
function testCleanupChanneledClean() {
- $this->m->cleanupChanneled();
+ $this->maintenance->cleanupChanneled();
$this->assertOutputPrePostShutdown( "", false );
}
function testCleanupChanneledAfterOutput() {
- $this->m->output( "foo" );
- $this->m->cleanupChanneled();
+ $this->maintenance->output( "foo" );
+ $this->maintenance->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foo", false );
}
function testCleanupChanneledAfterOutputWNullChannel() {
- $this->m->output( "foo", null );
- $this->m->cleanupChanneled();
+ $this->maintenance->output( "foo", null );
+ $this->maintenance->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foo", false );
}
function testCleanupChanneledAfterOutputWChannel() {
- $this->m->output( "foo", "bazChannel" );
- $this->m->cleanupChanneled();
+ $this->maintenance->output( "foo", "bazChannel" );
+ $this->maintenance->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foo\n", false );
}
function testCleanupChanneledAfterNLOutput() {
- $this->m->output( "foo\n" );
- $this->m->cleanupChanneled();
+ $this->maintenance->output( "foo\n" );
+ $this->maintenance->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foo\n", false );
}
function testCleanupChanneledAfterNLOutputWNullChannel() {
- $this->m->output( "foo\n", null );
- $this->m->cleanupChanneled();
+ $this->maintenance->output( "foo\n", null );
+ $this->maintenance->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foo\n", false );
}
function testCleanupChanneledAfterNLOutputWChannel() {
- $this->m->output( "foo\n", "bazChannel" );
- $this->m->cleanupChanneled();
+ $this->maintenance->output( "foo\n", "bazChannel" );
+ $this->maintenance->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foo\n", false );
}
function testCleanupChanneledAfterOutputChanneledWOChannel() {
- $this->m->outputChanneled( "foo" );
- $this->m->cleanupChanneled();
+ $this->maintenance->outputChanneled( "foo" );
+ $this->maintenance->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foo\n", false );
}
function testCleanupChanneledAfterOutputChanneledWNullChannel() {
- $this->m->outputChanneled( "foo", null );
- $this->m->cleanupChanneled();
+ $this->maintenance->outputChanneled( "foo", null );
+ $this->maintenance->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foo\n", false );
}
function testCleanupChanneledAfterOutputChanneledWChannel() {
- $this->m->outputChanneled( "foo", "bazChannel" );
- $this->m->cleanupChanneled();
+ $this->maintenance->outputChanneled( "foo", "bazChannel" );
+ $this->maintenance->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foo\n", false );
}
function testMultipleMaintenanceObjectsInteractionOutput() {
- $m2 = new MaintenanceFixup( $this );
+ $m2 = $this->createMaintenance();
- $this->m->output( "foo" );
+ $this->maintenance->output( "foo" );
$m2->output( "bar" );
$this->assertEquals( "foobar", $this->getActualOutput(),
"Output before shutdown simulation (m2)" );
- $m2->simulateShutdown();
+ $m2->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foobar", false );
}
function testMultipleMaintenanceObjectsInteractionOutputWNullChannel() {
- $m2 = new MaintenanceFixup( $this );
+ $m2 = $this->createMaintenance();
- $this->m->output( "foo", null );
+ $this->maintenance->output( "foo", null );
$m2->output( "bar", null );
$this->assertEquals( "foobar", $this->getActualOutput(),
"Output before shutdown simulation (m2)" );
- $m2->simulateShutdown();
+ $m2->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foobar", false );
}
function testMultipleMaintenanceObjectsInteractionOutputWChannel() {
- $m2 = new MaintenanceFixup( $this );
+ $m2 = $this->createMaintenance();
- $this->m->output( "foo", "bazChannel" );
+ $this->maintenance->output( "foo", "bazChannel" );
$m2->output( "bar", "bazChannel" );
$this->assertEquals( "foobar", $this->getActualOutput(),
"Output before shutdown simulation (m2)" );
- $m2->simulateShutdown();
+ $m2->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foobar\n", true );
}
function testMultipleMaintenanceObjectsInteractionOutputWNullChannelNL() {
- $m2 = new MaintenanceFixup( $this );
+ $m2 = $this->createMaintenance();
- $this->m->output( "foo\n", null );
+ $this->maintenance->output( "foo\n", null );
$m2->output( "bar\n", null );
$this->assertEquals( "foo\nbar\n", $this->getActualOutput(),
"Output before shutdown simulation (m2)" );
- $m2->simulateShutdown();
+ $m2->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foo\nbar\n", false );
}
function testMultipleMaintenanceObjectsInteractionOutputWChannelNL() {
- $m2 = new MaintenanceFixup( $this );
+ $m2 = $this->createMaintenance();
- $this->m->output( "foo\n", "bazChannel" );
+ $this->maintenance->output( "foo\n", "bazChannel" );
$m2->output( "bar\n", "bazChannel" );
$this->assertEquals( "foobar", $this->getActualOutput(),
"Output before shutdown simulation (m2)" );
- $m2->simulateShutdown();
+ $m2->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foobar\n", true );
}
function testMultipleMaintenanceObjectsInteractionOutputChanneled() {
- $m2 = new MaintenanceFixup( $this );
+ $m2 = $this->createMaintenance();
- $this->m->outputChanneled( "foo" );
+ $this->maintenance->outputChanneled( "foo" );
$m2->outputChanneled( "bar" );
$this->assertEquals( "foo\nbar\n", $this->getActualOutput(),
"Output before shutdown simulation (m2)" );
- $m2->simulateShutdown();
+ $m2->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foo\nbar\n", false );
}
function testMultipleMaintenanceObjectsInteractionOutputChanneledWNullChannel() {
- $m2 = new MaintenanceFixup( $this );
+ $m2 = $this->createMaintenance();
- $this->m->outputChanneled( "foo", null );
+ $this->maintenance->outputChanneled( "foo", null );
$m2->outputChanneled( "bar", null );
$this->assertEquals( "foo\nbar\n", $this->getActualOutput(),
"Output before shutdown simulation (m2)" );
- $m2->simulateShutdown();
+ $m2->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foo\nbar\n", false );
}
function testMultipleMaintenanceObjectsInteractionOutputChanneledWChannel() {
- $m2 = new MaintenanceFixup( $this );
+ $m2 = $this->createMaintenance();
- $this->m->outputChanneled( "foo", "bazChannel" );
+ $this->maintenance->outputChanneled( "foo", "bazChannel" );
$m2->outputChanneled( "bar", "bazChannel" );
$this->assertEquals( "foobar", $this->getActualOutput(),
"Output before shutdown simulation (m2)" );
- $m2->simulateShutdown();
+ $m2->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foobar\n", true );
}
function testMultipleMaintenanceObjectsInteractionCleanupChanneledWChannel() {
- $m2 = new MaintenanceFixup( $this );
+ $m2 = $this->createMaintenance();
- $this->m->outputChanneled( "foo", "bazChannel" );
+ $this->maintenance->outputChanneled( "foo", "bazChannel" );
$m2->outputChanneled( "bar", "bazChannel" );
$this->assertEquals( "foobar", $this->getActualOutput(),
"Output before first cleanup" );
- $this->m->cleanupChanneled();
+ $this->maintenance->cleanupChanneled();
$this->assertEquals( "foobar\n", $this->getActualOutput(),
"Output after first cleanup" );
$m2->cleanupChanneled();
$this->assertEquals( "foobar\n\n", $this->getActualOutput(),
"Output after second cleanup" );
- $m2->simulateShutdown();
+ $m2->cleanupChanneled();
$this->assertOutputPrePostShutdown( "foobar\n\n", false );
}
@@ -822,10 +483,10 @@ class MaintenanceTest extends MediaWikiTestCase {
* @covers Maintenance::getConfig
*/
public function testGetConfig() {
- $this->assertInstanceOf( 'Config', $this->m->getConfig() );
+ $this->assertInstanceOf( 'Config', $this->maintenance->getConfig() );
$this->assertSame(
MediaWikiServices::getInstance()->getMainConfig(),
- $this->m->getConfig()
+ $this->maintenance->getConfig()
);
}
@@ -834,12 +495,13 @@ class MaintenanceTest extends MediaWikiTestCase {
*/
public function testSetConfig() {
$conf = $this->createMock( 'Config' );
- $this->m->setConfig( $conf );
- $this->assertSame( $conf, $this->m->getConfig() );
+ $this->maintenance->setConfig( $conf );
+ $this->assertSame( $conf, $this->maintenance->getConfig() );
}
function testParseArgs() {
- $m2 = new MaintenanceFixup( $this );
+ $m2 = $this->createMaintenance();
+
// 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( [ '--multi', 'this1', '--multi', 'this2' ] );
@@ -848,9 +510,9 @@ class MaintenanceTest extends MediaWikiTestCase {
$this->assertEquals( [ [ 'multi', 'this1' ], [ 'multi', 'this2' ] ],
$m2->orderedOptions );
- $m2->simulateShutdown();
+ $m2->cleanupChanneled();
- $m2 = new MaintenanceFixup( $this );
+ $m2 = $this->createMaintenance();
$m2->addOption( 'multi', 'This option does stuff', false, false, false, true );
$m2->loadWithArgv( [ '--multi', '--multi' ] );
@@ -858,9 +520,10 @@ class MaintenanceTest extends MediaWikiTestCase {
$this->assertEquals( [ 1, 1 ], $m2->getOption( 'multi' ) );
$this->assertEquals( [ [ 'multi', 1 ], [ 'multi', 1 ] ], $m2->orderedOptions );
- $m2->simulateShutdown();
+ $m2->cleanupChanneled();
+
+ $m2 = $this->createMaintenance();
- $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( [ '--multi=yo' ] );
@@ -868,6 +531,6 @@ class MaintenanceTest extends MediaWikiTestCase {
$this->assertEquals( 'yo', $m2->getOption( 'multi' ) );
$this->assertEquals( [ [ 'multi', 'yo' ] ], $m2->orderedOptions );
- $m2->simulateShutdown();
+ $m2->cleanupChanneled();
}
}
diff --git a/www/wiki/tests/phpunit/maintenance/backupPrefetchTest.php b/www/wiki/tests/phpunit/maintenance/backupPrefetchTest.php
index 2c5931d2..8824c7af 100644
--- a/www/wiki/tests/phpunit/maintenance/backupPrefetchTest.php
+++ b/www/wiki/tests/phpunit/maintenance/backupPrefetchTest.php
@@ -1,6 +1,9 @@
<?php
-require_once __DIR__ . "/../../../maintenance/backupPrefetch.inc";
+namespace MediaWiki\Tests\Maintenance;
+
+use BaseDump;
+use MediaWikiTestCase;
/**
* Tests for BaseDump
@@ -151,7 +154,7 @@ class BaseDumpTest extends MediaWikiTestCase {
$fname = $this->getNewTempFile();
// The header of every prefetch file
- // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ // phpcs:ignore Generic.Files.LineLength
$header = '<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.7/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.7/ http://www.mediawiki.org/xml/export-0.7.xsd" version="0.7" xml:lang="en">
<siteinfo>
<sitename>wikisvn</sitename>
@@ -180,7 +183,6 @@ class BaseDumpTest extends MediaWikiTestCase {
</namespaces>
</siteinfo>
';
- // @codingStandardsIgnoreEnd
// An array holding the pages that are available for prefetch
$available_pages = [];
diff --git a/www/wiki/tests/phpunit/maintenance/backupTextPassTest.php b/www/wiki/tests/phpunit/maintenance/backupTextPassTest.php
index 0a1f3b4e..ad9bf3ea 100644
--- a/www/wiki/tests/phpunit/maintenance/backupTextPassTest.php
+++ b/www/wiki/tests/phpunit/maintenance/backupTextPassTest.php
@@ -1,5 +1,14 @@
<?php
+namespace MediaWiki\Tests\Maintenance;
+
+use MediaWikiLangTestCase;
+use TextContentHandler;
+use TextPassDumper;
+use Title;
+use WikiExporter;
+use WikiPage;
+
require_once __DIR__ . "/../../../maintenance/dumpTextPass.php";
/**
@@ -34,7 +43,7 @@ class TextPassDumperDatabaseTest extends DumpTestCase {
$this->tablesUsed[] = 'text';
$this->mergeMwGlobalArrayValue( 'wgContentHandlers', [
- "BackupTextPassTestModel" => "BackupTextPassTestModelHandler"
+ "BackupTextPassTestModel" => BackupTextPassTestModelHandler::class,
] );
$ns = $this->getDefaultWikitextNS();
@@ -170,7 +179,7 @@ class TextPassDumperDatabaseTest extends DumpTestCase {
];
// The mock itself
- $prefetchMock = $this->getMockBuilder( 'BaseDump' )
+ $prefetchMock = $this->getMockBuilder( BaseDump::class )
->setMethods( [ 'prefetch' ] )
->disableOriginalConstructor()
->getMock();
diff --git a/www/wiki/tests/phpunit/maintenance/backup_LogTest.php b/www/wiki/tests/phpunit/maintenance/backup_LogTest.php
index 91e1b1e7..c215b997 100644
--- a/www/wiki/tests/phpunit/maintenance/backup_LogTest.php
+++ b/www/wiki/tests/phpunit/maintenance/backup_LogTest.php
@@ -1,4 +1,13 @@
<?php
+
+namespace MediaWiki\Tests\Maintenance;
+
+use DumpBackup;
+use ManualLogEntry;
+use Title;
+use User;
+use WikiExporter;
+
/**
* Tests for log dumps of BackupDumper
*
diff --git a/www/wiki/tests/phpunit/maintenance/backup_PageTest.php b/www/wiki/tests/phpunit/maintenance/backup_PageTest.php
index 554d5f66..51a1ed69 100644
--- a/www/wiki/tests/phpunit/maintenance/backup_PageTest.php
+++ b/www/wiki/tests/phpunit/maintenance/backup_PageTest.php
@@ -1,4 +1,13 @@
<?php
+
+namespace MediaWiki\Tests\Maintenance;
+
+use DumpBackup;
+use Language;
+use Title;
+use WikiExporter;
+use WikiPage;
+
/**
* Tests for page dumps of BackupDumper
*
@@ -6,13 +15,12 @@
* @group Dump
* @covers BackupDumper
*/
-
class BackupDumperPageTest extends DumpTestCase {
// We'll add several pages, revision and texts. The following variables hold the
// corresponding ids.
- private $pageId1, $pageId2, $pageId3, $pageId4, $pageId5;
- private $pageTitle1, $pageTitle2, $pageTitle3, $pageTitle4, $pageTitle5;
+ private $pageId1, $pageId2, $pageId3, $pageId4;
+ private $pageTitle1, $pageTitle2, $pageTitle3, $pageTitle4;
private $revId1_1, $textId1_1;
private $revId2_1, $textId2_1, $revId2_2, $textId2_2;
private $revId2_3, $textId2_3, $revId2_4, $textId2_4;
diff --git a/www/wiki/tests/phpunit/maintenance/categoriesRdfTest.php b/www/wiki/tests/phpunit/maintenance/categoriesRdfTest.php
index ec2746e8..5068e701 100644
--- a/www/wiki/tests/phpunit/maintenance/categoriesRdfTest.php
+++ b/www/wiki/tests/phpunit/maintenance/categoriesRdfTest.php
@@ -1,16 +1,46 @@
<?php
+namespace MediaWiki\Tests\Maintenance;
+
+use DumpCategoriesAsRdf;
+use MediaWikiLangTestCase;
+
+/**
+ * @covers CategoriesRdf
+ * @covers DumpCategoriesAsRdf
+ */
class CategoriesRdfTest extends MediaWikiLangTestCase {
public function getCategoryIterator() {
return [
// batch 1
[
- (object)[ 'page_title' => 'Category One', 'page_id' => 1 ],
- (object)[ 'page_title' => '2 Category Two', 'page_id' => 2 ],
+ (object)[
+ 'page_title' => 'Category One',
+ 'page_id' => 1,
+ 'pp_propname' => null,
+ 'cat_pages' => '20',
+ 'cat_subcats' => '10',
+ 'cat_files' => '3'
+ ],
+ (object)[
+ 'page_title' => '2 Category Two',
+ 'page_id' => 2,
+ 'pp_propname' => 'hiddencat',
+ 'cat_pages' => 20,
+ 'cat_subcats' => 0,
+ 'cat_files' => 3
+ ],
],
// batch 2
[
- (object)[ 'page_title' => 'Третья категория', 'page_id' => 3 ],
+ (object)[
+ 'page_title' => 'Третья категория',
+ 'page_id' => 3,
+ 'pp_propname' => null,
+ 'cat_pages' => '0',
+ 'cat_subcats' => '0',
+ 'cat_files' => '0'
+ ],
]
];
}
@@ -60,8 +90,8 @@ class CategoriesRdfTest extends MediaWikiLangTestCase {
$dumpScript->execute();
$actualOut = file_get_contents( $outFileName );
$actualOut = preg_replace(
- '|<http://acme.test/categoriesDump> <http://schema.org/dateModified> "[^"]+?"|',
- '<http://acme.test/categoriesDump> <http://schema.org/dateModified> "{DATE}"',
+ '|<http://acme.test/wiki/Special:CategoryDump> <http://schema.org/dateModified> "[^"]+?"|',
+ '<http://acme.test/wiki/Special:CategoryDump> <http://schema.org/dateModified> "{DATE}"',
$actualOut
);
diff --git a/www/wiki/tests/phpunit/maintenance/deleteAutoPatrolLogsTest.php b/www/wiki/tests/phpunit/maintenance/deleteAutoPatrolLogsTest.php
new file mode 100644
index 00000000..c1418174
--- /dev/null
+++ b/www/wiki/tests/phpunit/maintenance/deleteAutoPatrolLogsTest.php
@@ -0,0 +1,252 @@
+<?php
+
+namespace MediaWiki\Tests\Maintenance;
+
+use DeleteAutoPatrolLogs;
+
+/**
+ * @group Database
+ * @covers DeleteAutoPatrolLogs
+ */
+class DeleteAutoPatrolLogsTest extends MaintenanceBaseTestCase {
+
+ public function getMaintenanceClass() {
+ return DeleteAutoPatrolLogs::class;
+ }
+
+ public function setUp() {
+ parent::setUp();
+ $this->tablesUsed = [ 'logging' ];
+
+ $this->cleanLoggingTable();
+ $this->insertLoggingData();
+ }
+
+ private function cleanLoggingTable() {
+ wfGetDB( DB_MASTER )->delete( 'logging', '*' );
+ }
+
+ private function insertLoggingData() {
+ $logs = [];
+
+ // Manual patrolling
+ $logs[] = [
+ 'log_type' => 'patrol',
+ 'log_action' => 'patrol',
+ 'log_user' => 7251,
+ 'log_params' => '',
+ 'log_timestamp' => 20041223210426
+ ];
+
+ // Autopatrol #1
+ $logs[] = [
+ 'log_type' => 'patrol',
+ 'log_action' => 'autopatrol',
+ 'log_user' => 7252,
+ 'log_params' => '',
+ 'log_timestamp' => 20051223210426
+ ];
+
+ // Block
+ $logs[] = [
+ 'log_type' => 'block',
+ 'log_action' => 'block',
+ 'log_user' => 7253,
+ 'log_params' => '',
+ 'log_timestamp' => 20061223210426
+ ];
+
+ // Very old/ invalid patrol
+ $logs[] = [
+ 'log_type' => 'patrol',
+ 'log_action' => 'patrol',
+ 'log_user' => 7253,
+ 'log_params' => 'nanana',
+ 'log_timestamp' => 20061223210426
+ ];
+
+ // Autopatrol #2
+ $logs[] = [
+ 'log_type' => 'patrol',
+ 'log_action' => 'autopatrol',
+ 'log_user' => 7254,
+ 'log_params' => '',
+ 'log_timestamp' => 20071223210426
+ ];
+
+ // Autopatrol #3 old way
+ $logs[] = [
+ 'log_type' => 'patrol',
+ 'log_action' => 'patrol',
+ 'log_user' => 7255,
+ 'log_params' => serialize( [ '6::auto' => true ] ),
+ 'log_timestamp' => 20081223210426
+ ];
+
+ // Manual patrol #2 old way
+ $logs[] = [
+ 'log_type' => 'patrol',
+ 'log_action' => 'patrol',
+ 'log_user' => 7256,
+ 'log_params' => serialize( [ '6::auto' => false ] ),
+ 'log_timestamp' => 20091223210426
+ ];
+
+ wfGetDB( DB_MASTER )->insert( 'logging', $logs );
+ }
+
+ public function runProvider() {
+ $allRows = [
+ (object)[
+ 'log_type' => 'patrol',
+ 'log_action' => 'patrol',
+ 'log_user' => '7251',
+ ],
+ (object)[
+ 'log_type' => 'patrol',
+ 'log_action' => 'autopatrol',
+ 'log_user' => '7252',
+ ],
+ (object)[
+ 'log_type' => 'block',
+ 'log_action' => 'block',
+ 'log_user' => '7253',
+ ],
+ (object)[
+ 'log_type' => 'patrol',
+ 'log_action' => 'patrol',
+ 'log_user' => '7253',
+ ],
+ (object)[
+ 'log_type' => 'patrol',
+ 'log_action' => 'autopatrol',
+ 'log_user' => '7254',
+ ],
+ (object)[
+ 'log_type' => 'patrol',
+ 'log_action' => 'patrol',
+ 'log_user' => '7255',
+ ],
+ (object)[
+ 'log_type' => 'patrol',
+ 'log_action' => 'patrol',
+ 'log_user' => '7256',
+ ],
+ ];
+
+ $cases = [
+ 'dry run' => [
+ $allRows,
+ [ '--sleep', '0', '--dry-run', '-q' ]
+ ],
+ 'basic run' => [
+ [
+ $allRows[0],
+ $allRows[2],
+ $allRows[3],
+ $allRows[5],
+ $allRows[6],
+ ],
+ [ '--sleep', '0', '-q' ]
+ ],
+ 'run with before' => [
+ [
+ $allRows[0],
+ $allRows[2],
+ $allRows[3],
+ $allRows[4],
+ $allRows[5],
+ $allRows[6],
+ ],
+ [ '--sleep', '0', '--before', '20060123210426', '-q' ]
+ ],
+ 'run with check-old' => [
+ [
+ $allRows[0],
+ $allRows[1],
+ $allRows[2],
+ $allRows[3],
+ $allRows[4],
+ $allRows[6],
+ ],
+ [ '--sleep', '0', '--check-old', '-q' ]
+ ],
+ ];
+
+ foreach ( $cases as $key => $case ) {
+ yield $key . '-batch-size-1' => [
+ $case[0],
+ array_merge( $case[1], [ '--batch-size', '1' ] )
+ ];
+ yield $key . '-batch-size-5' => [
+ $case[0],
+ array_merge( $case[1], [ '--batch-size', '5' ] )
+ ];
+ yield $key . '-batch-size-1000' => [
+ $case[0],
+ array_merge( $case[1], [ '--batch-size', '1000' ] )
+ ];
+ }
+ }
+
+ /**
+ * @dataProvider runProvider
+ */
+ public function testRun( $expected, $args ) {
+ $this->maintenance->loadWithArgv( $args );
+
+ $this->maintenance->execute();
+
+ $remainingLogs = wfGetDB( DB_REPLICA )->select(
+ [ 'logging' ],
+ [ 'log_type', 'log_action', 'log_user' ],
+ [],
+ __METHOD__,
+ [ 'ORDER BY' => 'log_id' ]
+ );
+
+ $this->assertEquals( $expected, iterator_to_array( $remainingLogs, false ) );
+ }
+
+ public function testFromId() {
+ $fromId = wfGetDB( DB_REPLICA )->selectField(
+ 'logging',
+ 'log_id',
+ [ 'log_params' => 'nanana' ]
+ );
+
+ $this->maintenance->loadWithArgv( [ '--sleep', '0', '--from-id', strval( $fromId ), '-q' ] );
+
+ $this->maintenance->execute();
+
+ $remainingLogs = wfGetDB( DB_REPLICA )->select(
+ [ 'logging' ],
+ [ 'log_type', 'log_action', 'log_user' ],
+ [],
+ __METHOD__,
+ [ 'ORDER BY' => 'log_id' ]
+ );
+
+ $deleted = [
+ 'log_type' => 'patrol',
+ 'log_action' => 'autopatrol',
+ 'log_user' => '7254',
+ ];
+ $notDeleted = [
+ 'log_type' => 'patrol',
+ 'log_action' => 'autopatrol',
+ 'log_user' => '7252',
+ ];
+
+ $remainingLogs = array_map(
+ function ( $val ) {
+ return (array)$val;
+ },
+ iterator_to_array( $remainingLogs, false )
+ );
+
+ $this->assertNotContains( $deleted, $remainingLogs );
+ $this->assertContains( $notDeleted, $remainingLogs );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/maintenance/fetchTextTest.php b/www/wiki/tests/phpunit/maintenance/fetchTextTest.php
index 5a28bfb5..97e0c88f 100644
--- a/www/wiki/tests/phpunit/maintenance/fetchTextTest.php
+++ b/www/wiki/tests/phpunit/maintenance/fetchTextTest.php
@@ -1,5 +1,15 @@
<?php
+namespace MediaWiki\Tests\Maintenance;
+
+use ContentHandler;
+use FetchText;
+use MediaWikiTestCase;
+use MWException;
+use Title;
+use PHPUnit_Framework_ExpectationFailedException;
+use WikiPage;
+
require_once __DIR__ . "/../../../maintenance/fetchText.php";
/**
@@ -205,7 +215,7 @@ class FetchTextTest extends MediaWikiTestCase {
function testExistingSeveral() {
$this->assertFilter(
- join( "\n", [
+ implode( "\n", [
self::$textId1,
self::$textId5,
self::$textId3,
diff --git a/www/wiki/tests/phpunit/mocks/MockChangesListFilterGroup.php b/www/wiki/tests/phpunit/mocks/MockChangesListFilterGroup.php
index f2891ce9..e50b9b4e 100644
--- a/www/wiki/tests/phpunit/mocks/MockChangesListFilterGroup.php
+++ b/www/wiki/tests/phpunit/mocks/MockChangesListFilterGroup.php
@@ -1,5 +1,7 @@
<?php
+use Wikimedia\Rdbms\IDatabase;
+
class MockChangesListFilterGroup extends ChangesListFilterGroup {
public function createFilter( array $filterDefinition ) {
return new MockChangesListFilter( $filterDefinition );
@@ -9,11 +11,11 @@ class MockChangesListFilterGroup extends ChangesListFilterGroup {
$this->filters[$filter->getName()] = $filter;
}
- public function isPerGroupRequestParameter() {
- throw new MWException(
- 'Not implemented: If the test relies on this, put it one of the ' .
- 'subclasses\' tests (e.g. ChangesListBooleanFilterGroupTest) ' .
- 'instead of testing the abstract class'
- );
+ public function modifyQuery( IDatabase $dbr, ChangesListSpecialPage $specialPage,
+ &$tables, &$fields, &$conds, &$query_options, &$join_conds, FormOptions $opts,
+ $isStructuredFiltersEnabled ) {
+ }
+
+ public function addOptions( FormOptions $opts, $allowDefaults, $isStructuredFiltersEnabled ) {
}
}
diff --git a/www/wiki/tests/phpunit/mocks/MockMessageLocalizer.php b/www/wiki/tests/phpunit/mocks/MockMessageLocalizer.php
new file mode 100644
index 00000000..143a419f
--- /dev/null
+++ b/www/wiki/tests/phpunit/mocks/MockMessageLocalizer.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * A simple {@link MessageLocalizer} implementation for use in tests.
+ * By default, it sets the message language to 'qqx',
+ * to make the tests independent of the wiki configuration.
+ *
+ * @author Lucas Werkmeister
+ * @license GPL-2.0-or-later
+ */
+class MockMessageLocalizer implements MessageLocalizer {
+
+ /**
+ * @var string|null
+ */
+ private $languageCode;
+
+ /**
+ * @param string|null $languageCode The language code to use for messages by default.
+ * You can specify null to use the user language,
+ * but this is not recommended as it may make your tests depend on the wiki configuration.
+ */
+ public function __construct( $languageCode = 'qqx' ) {
+ $this->languageCode = $languageCode;
+ }
+
+ /**
+ * Get a Message object.
+ * Parameters are the same as {@link wfMessage()}.
+ *
+ * @param string|string[]|MessageSpecifier $key Message key, or array of keys,
+ * or a MessageSpecifier.
+ * @param mixed $args,...
+ * @return Message
+ */
+ public function msg( $key ) {
+ $args = func_get_args();
+
+ /** @var Message $message */
+ $message = call_user_func_array( 'wfMessage', $args );
+
+ if ( $this->languageCode !== null ) {
+ $message->inLanguage( $this->languageCode );
+ }
+
+ return $message;
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/mocks/content/DummyContentForTesting.php b/www/wiki/tests/phpunit/mocks/content/DummyContentForTesting.php
index cdb3f78a..e8259d3a 100644
--- a/www/wiki/tests/phpunit/mocks/content/DummyContentForTesting.php
+++ b/www/wiki/tests/phpunit/mocks/content/DummyContentForTesting.php
@@ -2,8 +2,10 @@
class DummyContentForTesting extends AbstractContent {
+ const MODEL_ID = "testing";
+
public function __construct( $data ) {
- parent::__construct( "testing" );
+ parent::__construct( self::MODEL_ID );
$this->data = $data;
}
@@ -110,7 +112,7 @@ class DummyContentForTesting extends AbstractContent {
*
* @param Title $title Context title for parsing
* @param int|null $revId Revision ID (for {{REVISIONID}})
- * @param ParserOptions $options Parser options
+ * @param ParserOptions $options
* @param bool $generateHtml Whether or not to generate HTML
* @param ParserOutput &$output The output object to fill (reference).
*/
diff --git a/www/wiki/tests/phpunit/mocks/content/DummyContentHandlerForTesting.php b/www/wiki/tests/phpunit/mocks/content/DummyContentHandlerForTesting.php
index 6b9b7823..b71577c7 100644
--- a/www/wiki/tests/phpunit/mocks/content/DummyContentHandlerForTesting.php
+++ b/www/wiki/tests/phpunit/mocks/content/DummyContentHandlerForTesting.php
@@ -2,8 +2,8 @@
class DummyContentHandlerForTesting extends ContentHandler {
- public function __construct( $dataModel ) {
- parent::__construct( $dataModel, [ "testing" ] );
+ public function __construct( $dataModel, $formats = [ DummyContentForTesting::MODEL_ID ] ) {
+ parent::__construct( $dataModel, $formats );
}
/**
diff --git a/www/wiki/tests/phpunit/mocks/content/DummyNonTextContent.php b/www/wiki/tests/phpunit/mocks/content/DummyNonTextContent.php
index afc1a4ae..91bb1866 100644
--- a/www/wiki/tests/phpunit/mocks/content/DummyNonTextContent.php
+++ b/www/wiki/tests/phpunit/mocks/content/DummyNonTextContent.php
@@ -110,7 +110,7 @@ class DummyNonTextContent extends AbstractContent {
*
* @param Title $title Context title for parsing
* @param int|null $revId Revision ID (for {{REVISIONID}})
- * @param ParserOptions $options Parser options
+ * @param ParserOptions $options
* @param bool $generateHtml Whether or not to generate HTML
* @param ParserOutput &$output The output object to fill (reference).
*/
diff --git a/www/wiki/tests/phpunit/mocks/content/DummySerializeErrorContentHandler.php b/www/wiki/tests/phpunit/mocks/content/DummySerializeErrorContentHandler.php
new file mode 100644
index 00000000..720547a5
--- /dev/null
+++ b/www/wiki/tests/phpunit/mocks/content/DummySerializeErrorContentHandler.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/**
+ * A dummy content handler that will throw on an attempt to serialize content.
+ */
+class DummySerializeErrorContentHandler extends DummyContentHandlerForTesting {
+
+ public function __construct( $dataModel ) {
+ parent::__construct( $dataModel, [ "testing-serialize-error" ] );
+ }
+
+ /**
+ * @see ContentHandler::unserializeContent
+ *
+ * @param string $blob
+ * @param string $format
+ *
+ * @return Content
+ */
+ public function unserializeContent( $blob, $format = null ) {
+ throw new MWContentSerializationException( 'Could not unserialize content' );
+ }
+
+ /**
+ * @see ContentHandler::supportsDirectEditing
+ *
+ * @return bool
+ *
+ * @todo Should this be in the parent class?
+ */
+ public function supportsDirectApiEditing() {
+ return true;
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/mocks/media/MockOggHandler.php b/www/wiki/tests/phpunit/mocks/media/MockOggHandler.php
deleted file mode 100644
index b110e213..00000000
--- a/www/wiki/tests/phpunit/mocks/media/MockOggHandler.php
+++ /dev/null
@@ -1,93 +0,0 @@
-<?php
-/**
- * Fake handler for Ogg videos.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-class MockOggHandler extends OggHandlerTMH {
- function doTransform( $file, $dstPath, $dstUrl, $params, $flags = 0 ) {
- # Important or height handling is wrong.
- if ( !$this->normaliseParams( $file, $params ) ) {
- return new TransformParameterError( $params );
- }
-
- $srcWidth = $file->getWidth();
- $srcHeight = $file->getHeight();
-
- // Audio should not be transformed by size, give it a default width and height
- if ( $this->isAudio( $file ) ) {
- $srcWidth = 220;
- $srcHeight = 23;
- }
-
- $params['width'] = isset( $params['width'] ) ? $params['width'] : $srcWidth;
-
- // if height overtakes width use height as max:
- $targetWidth = $params['width'];
- $targetHeight = $srcWidth == 0 ? $srcHeight : round( $params['width'] * $srcHeight / $srcWidth );
- if ( isset( $params['height'] ) && $targetHeight > $params['height'] ) {
- $targetHeight = $params['height'];
- $targetWidth = round( $params['height'] * $srcWidth / $srcHeight );
- }
- $options = [
- 'file' => $file,
- 'length' => $this->getLength( $file ),
- 'offset' => $this->getOffset( $file ),
- 'width' => $targetWidth,
- 'height' => $targetHeight,
- 'isVideo' => !$this->isAudio( $file ),
- 'thumbtime' => isset(
- $params['thumbtime']
- ) ? $params['thumbtime'] : intval( $file->getLength() / 2 ),
- 'start' => isset( $params['start'] ) ? $params['start'] : false,
- 'end' => isset( $params['end'] ) ? $params['end'] : false,
- 'fillwindow' => isset( $params['fillwindow'] ) ? $params['fillwindow'] : false,
- 'disablecontrols' => isset ( $params['disablecontrols'] ) ? $params['disablecontrols'] : false
- ];
-
- // No thumbs for audio
- if ( !$options['isVideo'] ) {
- return new TimedMediaTransformOutput( $options );
- }
-
- // Setup pointer to thumb arguments
- $options[ 'thumbUrl' ] = $dstUrl;
- $options[ 'dstPath' ] = $dstPath;
- $options[ 'path' ] = $dstPath;
-
- return new TimedMediaTransformOutput( $options );
- }
-
- function getLength( $file ) {
- return 4.3666666666667;
- }
-
- function getBitRate( $file ) {
- return 590013;
- }
-
- function getWebType( $file ) {
- return "video/ogg; codecs=\"theora\"";
- }
-
- function getFramerate( $file ) {
- return 30;
- }
-}
diff --git a/www/wiki/tests/phpunit/phpunit.php b/www/wiki/tests/phpunit/phpunit.php
index 72037770..650cfcfa 100755
--- a/www/wiki/tests/phpunit/phpunit.php
+++ b/www/wiki/tests/phpunit/phpunit.php
@@ -21,6 +21,7 @@ class PHPUnitMaintClass extends Maintenance {
'use-bagostuff' => false,
'use-jobqueue' => false,
'use-normal-tables' => false,
+ 'mwdebug' => false,
'reuse-db' => false,
'wiki' => false,
'profiler' => false,
@@ -79,7 +80,7 @@ class PHPUnitMaintClass extends Maintenance {
[ '--configuration', $IP . '/tests/phpunit/suite.xml' ] );
}
- $phpUnitClass = 'PHPUnit_TextUI_Command';
+ $phpUnitClass = PHPUnit_TextUI_Command::class;
if ( $this->hasOption( 'with-phpunitclass' ) ) {
$phpUnitClass = $this->getOption( 'with-phpunitclass' );
@@ -112,7 +113,7 @@ class PHPUnitMaintClass extends Maintenance {
}
}
- if ( !class_exists( 'PHPUnit_Framework_TestCase' ) ) {
+ if ( !class_exists( 'PHPUnit\\Framework\\TestCase' ) ) {
echo "PHPUnit not found. Please install it and other dev dependencies by
running `composer install` in MediaWiki root directory.\n";
exit( 1 );
@@ -123,9 +124,9 @@ class PHPUnitMaintClass extends Maintenance {
exit( 1 );
}
- echo defined( 'HHVM_VERSION' ) ?
+ fwrite( STDERR, defined( 'HHVM_VERSION' ) ?
'Using HHVM ' . HHVM_VERSION . ' (' . PHP_VERSION . ")\n" :
- 'Using PHP ' . PHP_VERSION . "\n";
+ 'Using PHP ' . PHP_VERSION . "\n" );
// Prepare global services for unit tests.
MediaWikiTestCase::prepareServices( new GlobalVarConfig() );
diff --git a/www/wiki/tests/phpunit/skins/SideBarTest.php b/www/wiki/tests/phpunit/skins/SideBarTest.php
index af03fe63..ec85bb03 100644
--- a/www/wiki/tests/phpunit/skins/SideBarTest.php
+++ b/www/wiki/tests/phpunit/skins/SideBarTest.php
@@ -104,10 +104,10 @@ class SideBarTest extends MediaWikiLangTestCase {
] );
$this->assertSideBar(
[ 'Title' => [
- # ** http://www.mediawiki.org/| Home
+ # ** https://www.mediawiki.org/| Home
[
'text' => 'Home',
- 'href' => 'http://www.mediawiki.org/',
+ 'href' => 'https://www.mediawiki.org/',
'id' => 'n-Home',
'active' => null,
'rel' => 'nofollow',
@@ -116,7 +116,7 @@ class SideBarTest extends MediaWikiLangTestCase {
# ... skipped since it is missing a pipe with a description
] ],
'* Title
-** http://www.mediawiki.org/| Home
+** https://www.mediawiki.org/| Home
** http://valid.no.desc.org/
'
);
@@ -160,7 +160,7 @@ class SideBarTest extends MediaWikiLangTestCase {
private function getAttribs() {
# Sidebar text we will use everytime
$text = '* Title
-** http://www.mediawiki.org/| Home';
+** https://www.mediawiki.org/| Home';
$bar = [];
$this->skin->addToSidebarPlain( $bar, $text );
@@ -188,6 +188,7 @@ class SideBarTest extends MediaWikiLangTestCase {
/**
* Test $wgNoFollowLinks in sidebar
+ * @covers Skin::addToSidebarPlain
*/
public function testRespectWgnofollowlinks() {
$this->setMwGlobals( 'wgNoFollowLinks', false );
@@ -201,6 +202,7 @@ class SideBarTest extends MediaWikiLangTestCase {
/**
* Test $wgExternaLinkTarget in sidebar
* @dataProvider dataRespectExternallinktarget
+ * @covers Skin::addToSidebarPlain
*/
public function testRespectExternallinktarget( $externalLinkTarget ) {
$this->setMwGlobals( 'wgExternalLinkTarget', $externalLinkTarget );
diff --git a/www/wiki/tests/phpunit/structure/ApiDocumentationTest.php b/www/wiki/tests/phpunit/structure/ApiDocumentationTest.php
deleted file mode 100644
index 2049e38b..00000000
--- a/www/wiki/tests/phpunit/structure/ApiDocumentationTest.php
+++ /dev/null
@@ -1,180 +0,0 @@
-<?php
-
-/**
- * Checks that all API modules, core and extensions, have documentation i18n messages
- *
- * It won't catch everything since i18n messages can vary based on the wiki
- * configuration, but it should catch many cases for forgotten i18n.
- *
- * @group API
- */
-class ApiDocumentationTest extends MediaWikiTestCase {
-
- /** @var ApiMain */
- private static $main;
-
- /** @var array Sets of globals to test. Each array element is input to HashConfig */
- private static $testGlobals = [
- [
- 'MiserMode' => false,
- 'AllowCategorizedRecentChanges' => false,
- ],
- [
- 'MiserMode' => true,
- 'AllowCategorizedRecentChanges' => true,
- ],
- ];
-
- /**
- * Initialize/fetch the ApiMain instance for testing
- * @return ApiMain
- */
- private static function getMain() {
- if ( !self::$main ) {
- self::$main = new ApiMain( RequestContext::getMain() );
- self::$main->getContext()->setLanguage( 'en' );
- self::$main->getContext()->setTitle(
- Title::makeTitle( NS_SPECIAL, 'Badtitle/dummy title for ApiDocumentationTest' )
- );
- }
- return self::$main;
- }
-
- /**
- * Test a message
- * @param Message $msg
- * @param string $what Which message is being checked
- */
- private function checkMessage( $msg, $what ) {
- $msg = ApiBase::makeMessage( $msg, self::getMain()->getContext() );
- $this->assertInstanceOf( 'Message', $msg, "$what message" );
- $this->assertTrue( $msg->exists(), "$what message {$msg->getKey()} exists" );
- }
-
- /**
- * @dataProvider provideDocumentationExists
- * @param string $path Module path
- * @param array $globals Globals to set
- */
- public function testDocumentationExists( $path, array $globals ) {
- $main = self::getMain();
-
- // Set configuration variables
- $main->getContext()->setConfig( new MultiConfig( [
- new HashConfig( $globals ),
- RequestContext::getMain()->getConfig(),
- ] ) );
- foreach ( $globals as $k => $v ) {
- $this->setMwGlobals( "wg$k", $v );
- }
-
- // Fetch module.
- $module = TestingAccessWrapper::newFromObject( $main->getModuleFromPath( $path ) );
-
- // Test messages for flags.
- foreach ( $module->getHelpFlags() as $flag ) {
- $this->checkMessage( "api-help-flag-$flag", "Flag $flag" );
- }
-
- // Module description messages.
- $this->checkMessage( $module->getDescriptionMessage(), 'Module description' );
-
- // Parameters. Lots of messages in here.
- $params = $module->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
- $tags = [];
- foreach ( $params as $name => $settings ) {
- if ( !is_array( $settings ) ) {
- $settings = [];
- }
-
- // Basic description message
- if ( isset( $settings[ApiBase::PARAM_HELP_MSG] ) ) {
- $msg = $settings[ApiBase::PARAM_HELP_MSG];
- } else {
- $msg = "apihelp-{$path}-param-{$name}";
- }
- $this->checkMessage( $msg, "Parameter $name description" );
-
- // If param-per-value is in use, each value's message
- if ( isset( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) {
- $this->assertInternalType( 'array', $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE],
- "Parameter $name PARAM_HELP_MSG_PER_VALUE is array" );
- $this->assertInternalType( 'array', $settings[ApiBase::PARAM_TYPE],
- "Parameter $name PARAM_TYPE is array for msg-per-value mode" );
- $valueMsgs = $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE];
- foreach ( $settings[ApiBase::PARAM_TYPE] as $value ) {
- if ( isset( $valueMsgs[$value] ) ) {
- $msg = $valueMsgs[$value];
- } else {
- $msg = "apihelp-{$path}-paramvalue-{$name}-{$value}";
- }
- $this->checkMessage( $msg, "Parameter $name value $value" );
- }
- }
-
- // Appended messages (e.g. "disabled in miser mode")
- if ( isset( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
- $this->assertInternalType( 'array', $settings[ApiBase::PARAM_HELP_MSG_APPEND],
- "Parameter $name PARAM_HELP_MSG_APPEND is array" );
- foreach ( $settings[ApiBase::PARAM_HELP_MSG_APPEND] as $i => $msg ) {
- $this->checkMessage( $msg, "Parameter $name HELP_MSG_APPEND #$i" );
- }
- }
-
- // Info tags (e.g. "only usable in mode 1") are typically shared by
- // several parameters, so accumulate them and test them later.
- if ( !empty( $settings[ApiBase::PARAM_HELP_MSG_INFO] ) ) {
- foreach ( $settings[ApiBase::PARAM_HELP_MSG_INFO] as $i ) {
- $tags[array_shift( $i )] = 1;
- }
- }
- }
-
- // Info tags (e.g. "only usable in mode 1") accumulated above
- foreach ( $tags as $tag => $dummy ) {
- $this->checkMessage( "apihelp-{$path}-paraminfo-{$tag}", "HELP_MSG_INFO tag $tag" );
- }
-
- // Messages for examples.
- foreach ( $module->getExamplesMessages() as $qs => $msg ) {
- $this->checkMessage( $msg, "Example $qs" );
- }
- }
-
- public static function provideDocumentationExists() {
- $main = self::getMain();
- $paths = self::getSubModulePaths( $main->getModuleManager() );
- array_unshift( $paths, $main->getModulePath() );
-
- $ret = [];
- foreach ( $paths as $path ) {
- foreach ( self::$testGlobals as $globals ) {
- $g = [];
- foreach ( $globals as $k => $v ) {
- $g[] = "$k=" . var_export( $v, 1 );
- }
- $k = "Module $path with " . implode( ', ', $g );
- $ret[$k] = [ $path, $globals ];
- }
- }
- return $ret;
- }
-
- /**
- * Return paths of all submodules in an ApiModuleManager, recursively
- * @param ApiModuleManager $manager
- * @return string[]
- */
- protected static function getSubModulePaths( ApiModuleManager $manager ) {
- $paths = [];
- foreach ( $manager->getNames() as $name ) {
- $module = $manager->getModule( $name );
- $paths[] = $module->getModulePath();
- $subManager = $module->getModuleManager();
- if ( $subManager ) {
- $paths = array_merge( $paths, self::getSubModulePaths( $subManager ) );
- }
- }
- return $paths;
- }
-}
diff --git a/www/wiki/tests/phpunit/structure/ApiStructureTest.php b/www/wiki/tests/phpunit/structure/ApiStructureTest.php
index 7912f979..77d6e741 100644
--- a/www/wiki/tests/phpunit/structure/ApiStructureTest.php
+++ b/www/wiki/tests/phpunit/structure/ApiStructureTest.php
@@ -20,15 +20,88 @@ class ApiStructureTest extends MediaWikiTestCase {
private static $testGlobals = [
[
'MiserMode' => false,
- 'AllowCategorizedRecentChanges' => false,
],
[
'MiserMode' => true,
- 'AllowCategorizedRecentChanges' => true,
],
];
/**
+ * Values are an array, where each array value is a permitted type. A type
+ * can be a string, which is the name of an internal type or a
+ * class/interface. Or it can be an array, in which case the value must be
+ * an array whose elements are the types given in the array (e.g., [
+ * 'string', integer' ] means an array whose entries are strings and/or
+ * integers).
+ */
+ private static $paramTypes = [
+ // ApiBase::PARAM_DFLT => as appropriate for PARAM_TYPE
+ ApiBase::PARAM_ISMULTI => [ 'boolean' ],
+ ApiBase::PARAM_TYPE => [ 'string', [ 'string' ] ],
+ ApiBase::PARAM_MAX => [ 'integer' ],
+ ApiBase::PARAM_MAX2 => [ 'integer' ],
+ ApiBase::PARAM_MIN => [ 'integer' ],
+ ApiBase::PARAM_ALLOW_DUPLICATES => [ 'boolean' ],
+ ApiBase::PARAM_DEPRECATED => [ 'boolean' ],
+ ApiBase::PARAM_REQUIRED => [ 'boolean' ],
+ ApiBase::PARAM_RANGE_ENFORCE => [ 'boolean' ],
+ ApiBase::PARAM_HELP_MSG => [ 'string', 'array', Message::class ],
+ ApiBase::PARAM_HELP_MSG_APPEND => [ [ 'string', 'array', Message::class ] ],
+ ApiBase::PARAM_HELP_MSG_INFO => [ [ 'array' ] ],
+ ApiBase::PARAM_VALUE_LINKS => [ [ 'string' ] ],
+ ApiBase::PARAM_HELP_MSG_PER_VALUE => [ [ 'string', 'array', Message::class ] ],
+ ApiBase::PARAM_SUBMODULE_MAP => [ [ 'string' ] ],
+ ApiBase::PARAM_SUBMODULE_PARAM_PREFIX => [ 'string' ],
+ ApiBase::PARAM_ALL => [ 'boolean', 'string' ],
+ ApiBase::PARAM_EXTRA_NAMESPACES => [ [ 'integer' ] ],
+ ApiBase::PARAM_SENSITIVE => [ 'boolean' ],
+ ApiBase::PARAM_DEPRECATED_VALUES => [ 'array' ],
+ ApiBase::PARAM_ISMULTI_LIMIT1 => [ 'integer' ],
+ ApiBase::PARAM_ISMULTI_LIMIT2 => [ 'integer' ],
+ ApiBase::PARAM_MAX_BYTES => [ 'integer' ],
+ ApiBase::PARAM_MAX_CHARS => [ 'integer' ],
+ ];
+
+ // param => [ other param that must be present => required value or null ]
+ private static $paramRequirements = [
+ ApiBase::PARAM_ALLOW_DUPLICATES => [ ApiBase::PARAM_ISMULTI => true ],
+ ApiBase::PARAM_ALL => [ ApiBase::PARAM_ISMULTI => true ],
+ ApiBase::PARAM_ISMULTI_LIMIT1 => [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_ISMULTI_LIMIT2 => null,
+ ],
+ ApiBase::PARAM_ISMULTI_LIMIT2 => [
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_ISMULTI_LIMIT1 => null,
+ ],
+ ];
+
+ // param => type(s) allowed for this param ('array' is any array)
+ private static $paramAllowedTypes = [
+ ApiBase::PARAM_MAX => [ 'integer', 'limit' ],
+ ApiBase::PARAM_MAX2 => 'limit',
+ ApiBase::PARAM_MIN => [ 'integer', 'limit' ],
+ ApiBase::PARAM_RANGE_ENFORCE => 'integer',
+ ApiBase::PARAM_VALUE_LINKS => 'array',
+ ApiBase::PARAM_HELP_MSG_PER_VALUE => 'array',
+ ApiBase::PARAM_SUBMODULE_MAP => 'submodule',
+ ApiBase::PARAM_SUBMODULE_PARAM_PREFIX => 'submodule',
+ ApiBase::PARAM_ALL => 'array',
+ ApiBase::PARAM_EXTRA_NAMESPACES => 'namespace',
+ ApiBase::PARAM_DEPRECATED_VALUES => 'array',
+ ApiBase::PARAM_MAX_BYTES => [ 'NULL', 'string', 'text', 'password' ],
+ ApiBase::PARAM_MAX_CHARS => [ 'NULL', 'string', 'text', 'password' ],
+ ];
+
+ private static $paramProhibitedTypes = [
+ ApiBase::PARAM_ISMULTI => [ 'boolean', 'limit', 'upload' ],
+ ApiBase::PARAM_ALL => 'namespace',
+ ApiBase::PARAM_SENSITIVE => 'password',
+ ];
+
+ private static $constantNames = null;
+
+ /**
* Initialize/fetch the ApiMain instance for testing
* @return ApiMain
*/
@@ -50,7 +123,7 @@ class ApiStructureTest extends MediaWikiTestCase {
*/
private function checkMessage( $msg, $what ) {
$msg = ApiBase::makeMessage( $msg, self::getMain()->getContext() );
- $this->assertInstanceOf( 'Message', $msg, "$what message" );
+ $this->assertInstanceOf( Message::class, $msg, "$what message" );
$this->assertTrue( $msg->exists(), "$what message {$msg->getKey()} exists" );
}
@@ -180,26 +253,327 @@ class ApiStructureTest extends MediaWikiTestCase {
// avoid warnings about empty tests when no parameter needs to be checked
$this->assertTrue( true );
+ if ( self::$constantNames === null ) {
+ self::$constantNames = [];
+
+ foreach ( ( new ReflectionClass( 'ApiBase' ) )->getConstants() as $key => $val ) {
+ if ( substr( $key, 0, 6 ) === 'PARAM_' ) {
+ self::$constantNames[$val] = $key;
+ }
+ }
+ }
+
foreach ( [ $paramsPlain, $paramsForHelp ] as $params ) {
foreach ( $params as $param => $config ) {
+ if ( !is_array( $config ) ) {
+ $config = [ ApiBase::PARAM_DFLT => $config ];
+ }
+ if ( !isset( $config[ApiBase::PARAM_TYPE] ) ) {
+ $config[ApiBase::PARAM_TYPE] = isset( $config[ApiBase::PARAM_DFLT] )
+ ? gettype( $config[ApiBase::PARAM_DFLT] )
+ : 'NULL';
+ }
+
+ foreach ( self::$paramTypes as $key => $types ) {
+ if ( !isset( $config[$key] ) ) {
+ continue;
+ }
+ $keyName = self::$constantNames[$key];
+ $this->validateType( $types, $config[$key], $param, $keyName );
+ }
+
+ foreach ( self::$paramRequirements as $key => $required ) {
+ if ( !isset( $config[$key] ) ) {
+ continue;
+ }
+ foreach ( $required as $requireKey => $requireVal ) {
+ $this->assertArrayHasKey( $requireKey, $config,
+ "$param: When " . self::$constantNames[$key] . " is set, " .
+ self::$constantNames[$requireKey] . " must also be set" );
+ if ( $requireVal !== null ) {
+ $this->assertSame( $requireVal, $config[$requireKey],
+ "$param: When " . self::$constantNames[$key] . " is set, " .
+ self::$constantNames[$requireKey] . " must equal " .
+ var_export( $requireVal, true ) );
+ }
+ }
+ }
+
+ foreach ( self::$paramAllowedTypes as $key => $allowedTypes ) {
+ if ( !isset( $config[$key] ) ) {
+ continue;
+ }
+
+ $actualType = is_array( $config[ApiBase::PARAM_TYPE] )
+ ? 'array' : $config[ApiBase::PARAM_TYPE];
+
+ $this->assertContains(
+ $actualType,
+ (array)$allowedTypes,
+ "$param: " . self::$constantNames[$key] .
+ " can only be used with PARAM_TYPE " .
+ implode( ', ', (array)$allowedTypes )
+ );
+ }
+
+ foreach ( self::$paramProhibitedTypes as $key => $prohibitedTypes ) {
+ if ( !isset( $config[$key] ) ) {
+ continue;
+ }
+
+ $actualType = is_array( $config[ApiBase::PARAM_TYPE] )
+ ? 'array' : $config[ApiBase::PARAM_TYPE];
+
+ $this->assertNotContains(
+ $actualType,
+ (array)$prohibitedTypes,
+ "$param: " . self::$constantNames[$key] .
+ " cannot be used with PARAM_TYPE " .
+ implode( ', ', (array)$prohibitedTypes )
+ );
+ }
+
+ if ( isset( $config[ApiBase::PARAM_DFLT] ) ) {
+ $this->assertFalse(
+ isset( $config[ApiBase::PARAM_REQUIRED] ) &&
+ $config[ApiBase::PARAM_REQUIRED],
+ "$param: A required parameter cannot have a default" );
+
+ $this->validateDefault( $param, $config );
+ }
+
+ if ( $config[ApiBase::PARAM_TYPE] === 'limit' ) {
+ $this->assertTrue(
+ isset( $config[ApiBase::PARAM_MAX] ) &&
+ isset( $config[ApiBase::PARAM_MAX2] ),
+ "$param: PARAM_MAX and PARAM_MAX2 are required for limits"
+ );
+ $this->assertGreaterThanOrEqual(
+ $config[ApiBase::PARAM_MAX],
+ $config[ApiBase::PARAM_MAX2],
+ "$param: PARAM_MAX cannot be greater than PARAM_MAX2"
+ );
+ }
+
if (
- isset( $config[ApiBase::PARAM_ISMULTI_LIMIT1] )
- || isset( $config[ApiBase::PARAM_ISMULTI_LIMIT2] )
+ isset( $config[ApiBase::PARAM_MIN] ) &&
+ isset( $config[ApiBase::PARAM_MAX] )
) {
- $this->assertTrue( !empty( $config[ApiBase::PARAM_ISMULTI] ), $param
- . ': PARAM_ISMULTI_LIMIT* only makes sense when PARAM_ISMULTI is true' );
- $this->assertTrue( isset( $config[ApiBase::PARAM_ISMULTI_LIMIT1] )
- && isset( $config[ApiBase::PARAM_ISMULTI_LIMIT2] ), $param
- . ': PARAM_ISMULTI_LIMIT1 and PARAM_ISMULTI_LIMIT2 must be used together' );
- $this->assertType( 'int', $config[ApiBase::PARAM_ISMULTI_LIMIT1], $param
- . 'PARAM_ISMULTI_LIMIT1 must be an integer' );
- $this->assertType( 'int', $config[ApiBase::PARAM_ISMULTI_LIMIT2], $param
- . 'PARAM_ISMULTI_LIMIT2 must be an integer' );
- $this->assertGreaterThanOrEqual( $config[ApiBase::PARAM_ISMULTI_LIMIT1],
- $config[ApiBase::PARAM_ISMULTI_LIMIT2], $param
- . 'PARAM_ISMULTI limit cannot be smaller for users with apihighlimits rights' );
+ $this->assertGreaterThanOrEqual(
+ $config[ApiBase::PARAM_MIN],
+ $config[ApiBase::PARAM_MAX],
+ "$param: PARAM_MIN cannot be greater than PARAM_MAX"
+ );
+ }
+
+ if ( isset( $config[ApiBase::PARAM_RANGE_ENFORCE] ) ) {
+ $this->assertTrue(
+ isset( $config[ApiBase::PARAM_MIN] ) ||
+ isset( $config[ApiBase::PARAM_MAX] ),
+ "$param: PARAM_RANGE_ENFORCE can only be set together with " .
+ "PARAM_MIN or PARAM_MAX"
+ );
+ }
+
+ if ( isset( $config[ApiBase::PARAM_DEPRECATED_VALUES] ) ) {
+ foreach ( $config[ApiBase::PARAM_DEPRECATED_VALUES] as $key => $unused ) {
+ $this->assertContains( $key, $config[ApiBase::PARAM_TYPE],
+ "$param: Deprecated value \"$key\" is not allowed, " .
+ "how can it be deprecated?" );
+ }
+ }
+
+ if (
+ isset( $config[ApiBase::PARAM_ISMULTI_LIMIT1] ) ||
+ isset( $config[ApiBase::PARAM_ISMULTI_LIMIT2] )
+ ) {
+ $this->assertGreaterThanOrEqual( 0, $config[ApiBase::PARAM_ISMULTI_LIMIT1],
+ "$param: PARAM_ISMULTI_LIMIT1 cannot be negative" );
+ // Zero for both doesn't make sense, but you could have
+ // zero for non-bots
+ $this->assertGreaterThanOrEqual( 1, $config[ApiBase::PARAM_ISMULTI_LIMIT2],
+ "$param: PARAM_ISMULTI_LIMIT2 cannot be negative or zero" );
+ $this->assertGreaterThanOrEqual(
+ $config[ApiBase::PARAM_ISMULTI_LIMIT1],
+ $config[ApiBase::PARAM_ISMULTI_LIMIT2],
+ "$param: PARAM_ISMULTI limit cannot be smaller for users with " .
+ "apihighlimits rights" );
+ }
+
+ if ( isset( $config[ApiBase::PARAM_MAX_BYTES] ) ) {
+ $this->assertGreaterThanOrEqual( 1, $config[ApiBase::PARAM_MAX_BYTES],
+ "$param: PARAM_MAX_BYTES cannot be negative or zero" );
+ }
+
+ if ( isset( $config[ApiBase::PARAM_MAX_CHARS] ) ) {
+ $this->assertGreaterThanOrEqual( 1, $config[ApiBase::PARAM_MAX_CHARS],
+ "$param: PARAM_MAX_CHARS cannot be negative or zero" );
+ }
+
+ if (
+ isset( $config[ApiBase::PARAM_MAX_BYTES] ) &&
+ isset( $config[ApiBase::PARAM_MAX_CHARS] )
+ ) {
+ // Length of a string in chars is always <= length in bytes,
+ // so PARAM_MAX_CHARS is pointless if > PARAM_MAX_BYTES
+ $this->assertGreaterThanOrEqual(
+ $config[ApiBase::PARAM_MAX_CHARS],
+ $config[ApiBase::PARAM_MAX_BYTES],
+ "$param: PARAM_MAX_BYTES cannot be less than PARAM_MAX_CHARS"
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * Throws if $value does not match one of the types specified in $types.
+ *
+ * @param array $types From self::$paramTypes array
+ * @param mixed $value Value to check
+ * @param string $param Name of param we're checking, for error messages
+ * @param string $desc Description for error messages
+ */
+ private function validateType( $types, $value, $param, $desc ) {
+ if ( count( $types ) === 1 ) {
+ // Only one type allowed
+ if ( is_string( $types[0] ) ) {
+ $this->assertType( $types[0], $value, "$param: $desc type" );
+ } else {
+ // Array whose values have specified types, recurse
+ $this->assertInternalType( 'array', $value, "$param: $desc type" );
+ foreach ( $value as $subvalue ) {
+ $this->validateType( $types[0], $subvalue, $param, "$desc value" );
+ }
+ }
+ } else {
+ // Multiple options
+ foreach ( $types as $type ) {
+ if ( is_string( $type ) ) {
+ if ( class_exists( $type ) || interface_exists( $type ) ) {
+ if ( $value instanceof $type ) {
+ return;
+ }
+ } else {
+ if ( gettype( $value ) === $type ) {
+ return;
+ }
+ }
+ } else {
+ // Array whose values have specified types, recurse
+ try {
+ $this->validateType( [ $type ], $value, $param, "$desc type" );
+ // Didn't throw, so we're good
+ return;
+ } catch ( Exception $unused ) {
+ }
}
}
+ // Doesn't match any of them
+ $this->fail( "$param: $desc has incorrect type" );
+ }
+ }
+
+ /**
+ * Asserts that $default is a valid default for $type.
+ *
+ * @param string $param Name of param, for error messages
+ * @param array $config Array of configuration options for this parameter
+ */
+ private function validateDefault( $param, $config ) {
+ $type = $config[ApiBase::PARAM_TYPE];
+ $default = $config[ApiBase::PARAM_DFLT];
+
+ if ( !empty( $config[ApiBase::PARAM_ISMULTI] ) ) {
+ if ( $default === '' ) {
+ // The empty array is fine
+ return;
+ }
+ $defaults = explode( '|', $default );
+ $config[ApiBase::PARAM_ISMULTI] = false;
+ foreach ( $defaults as $defaultValue ) {
+ // Only allow integers in their simplest form with no leading
+ // or trailing characters etc.
+ if ( $type === 'integer' && $defaultValue === (string)(int)$defaultValue ) {
+ $defaultValue = (int)$defaultValue;
+ }
+ $config[ApiBase::PARAM_DFLT] = $defaultValue;
+ $this->validateDefault( $param, $config );
+ }
+ return;
+ }
+ switch ( $type ) {
+ case 'boolean':
+ $this->assertFalse( $default,
+ "$param: Boolean params may only default to false" );
+ break;
+
+ case 'integer':
+ $this->assertInternalType( 'integer', $default,
+ "$param: Default $default is not an integer" );
+ break;
+
+ case 'limit':
+ if ( $default === 'max' ) {
+ break;
+ }
+ $this->assertInternalType( 'integer', $default,
+ "$param: Default $default is neither an integer nor \"max\"" );
+ break;
+
+ case 'namespace':
+ $validValues = MWNamespace::getValidNamespaces();
+ if (
+ isset( $config[ApiBase::PARAM_EXTRA_NAMESPACES] ) &&
+ is_array( $config[ApiBase::PARAM_EXTRA_NAMESPACES] )
+ ) {
+ $validValues = array_merge(
+ $validValues,
+ $config[ApiBase::PARAM_EXTRA_NAMESPACES]
+ );
+ }
+ $this->assertContains( $default, $validValues,
+ "$param: Default $default is not a valid namespace" );
+ break;
+
+ case 'NULL':
+ case 'password':
+ case 'string':
+ case 'submodule':
+ case 'tags':
+ case 'text':
+ $this->assertInternalType( 'string', $default,
+ "$param: Default $default is not a string" );
+ break;
+
+ case 'timestamp':
+ if ( $default === 'now' ) {
+ return;
+ }
+ $this->assertNotFalse( wfTimestamp( TS_MW, $default ),
+ "$param: Default $default is not a valid timestamp" );
+ break;
+
+ case 'user':
+ // @todo Should we make user validation a public static method
+ // in ApiBase() or something so we don't have to resort to
+ // this? Or in User for that matter.
+ $wrapper = TestingAccessWrapper::newFromObject( new ApiMain() );
+ try {
+ $wrapper->validateUser( $default, '' );
+ } catch ( ApiUsageException $e ) {
+ $this->fail( "$param: Default $default is not a valid username/IP address" );
+ }
+ break;
+
+ default:
+ if ( is_array( $type ) ) {
+ $this->assertContains( $default, $type,
+ "$param: Default $default is not any of " .
+ implode( ', ', $type ) );
+ } else {
+ $this->fail( "Unrecognized type $type" );
+ }
}
}
diff --git a/www/wiki/tests/phpunit/structure/AutoLoaderTest.php b/www/wiki/tests/phpunit/structure/AutoLoaderTest.php
index d81e8c66..217232e3 100644
--- a/www/wiki/tests/phpunit/structure/AutoLoaderTest.php
+++ b/www/wiki/tests/phpunit/structure/AutoLoaderTest.php
@@ -58,9 +58,9 @@ class AutoLoaderTest extends MediaWikiTestCase {
continue;
}
- MediaWiki\suppressWarnings();
+ Wikimedia\suppressWarnings();
$contents = file_get_contents( $filePath );
- MediaWiki\restoreWarnings();
+ Wikimedia\restoreWarnings();
if ( $contents === false ) {
$actual[$class] = "[couldn't read file '$filePath']";
@@ -161,6 +161,7 @@ class AutoLoaderTest extends MediaWikiTestCase {
$path = realpath( __DIR__ . '/../../..' );
$oldAutoload = file_get_contents( $path . '/autoload.php' );
$generator = new AutoloadGenerator( $path, 'local' );
+ $generator->setExcludePaths( array_values( AutoLoader::getAutoloadNamespaces() ) );
$generator->initMediaWikiDefault();
$newAutoload = $generator->getAutoload( 'maintenance/generateLocalAutoload.php' );
diff --git a/www/wiki/tests/phpunit/structure/AvailableRightsTest.php b/www/wiki/tests/phpunit/structure/AvailableRightsTest.php
index 16a9a24d..6c2ff024 100644
--- a/www/wiki/tests/phpunit/structure/AvailableRightsTest.php
+++ b/www/wiki/tests/phpunit/structure/AvailableRightsTest.php
@@ -6,7 +6,9 @@
*
* @author Marius Hoch < hoo@online.de >
*/
-class AvailableRightsTest extends PHPUnit_Framework_TestCase {
+class AvailableRightsTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
/**
* Returns all rights that should be in $wgAvailableRights + all rights
diff --git a/www/wiki/tests/phpunit/structure/ExtensionJsonValidationTest.php b/www/wiki/tests/phpunit/structure/ExtensionJsonValidationTest.php
index b19376d3..60c97ccf 100644
--- a/www/wiki/tests/phpunit/structure/ExtensionJsonValidationTest.php
+++ b/www/wiki/tests/phpunit/structure/ExtensionJsonValidationTest.php
@@ -20,7 +20,9 @@
* Validates all loaded extensions and skins using the ExtensionRegistry
* against the extension.json schema in the docs/ folder.
*/
-class ExtensionJsonValidationTest extends PHPUnit_Framework_TestCase {
+class ExtensionJsonValidationTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
/**
* @var ExtensionJsonValidator
diff --git a/www/wiki/tests/phpunit/structure/ResourcesTest.php b/www/wiki/tests/phpunit/structure/ResourcesTest.php
index 2fba76b6..62ddaceb 100644
--- a/www/wiki/tests/phpunit/structure/ResourcesTest.php
+++ b/www/wiki/tests/phpunit/structure/ResourcesTest.php
@@ -44,15 +44,24 @@ class ResourcesTest extends MediaWikiTestCase {
}
/**
- * Verify that nothing explicitly depends on the 'jquery' and 'mediawiki' modules.
- * They are always loaded, depending on them is unsupported and leads to unexpected behaviour.
+ * Verify that nothing explicitly depends on base modules, or other raw modules.
+ *
+ * Depending on them is unsupported as they are not registered client-side by the startup module.
+ *
* TODO Modules can dynamically choose dependencies based on context. This method does not
* test such dependencies. The same goes for testMissingDependencies() and
* testUnsatisfiableDependencies().
*/
public function testIllegalDependencies() {
$data = self::getAllModules();
- $illegalDeps = [ 'jquery', 'mediawiki' ];
+
+ $illegalDeps = ResourceLoaderStartUpModule::getStartupModules();
+ foreach ( $data['modules'] as $moduleName => $module ) {
+ if ( $module->isRaw() ) {
+ $illegalDeps[] = $moduleName;
+ }
+ }
+ $illegalDeps = array_unique( $illegalDeps );
/** @var ResourceLoaderModule $module */
foreach ( $data['modules'] as $moduleName => $module ) {
diff --git a/www/wiki/tests/phpunit/structure/StructureTest.php b/www/wiki/tests/phpunit/structure/StructureTest.php
index 9016cb7b..4df791ec 100644
--- a/www/wiki/tests/phpunit/structure/StructureTest.php
+++ b/www/wiki/tests/phpunit/structure/StructureTest.php
@@ -25,6 +25,8 @@ class StructureTest extends MediaWikiTestCase {
'MediaWikiTestCase',
'ResourceLoaderTestCase',
'PHPUnit_Framework_TestCase',
+ '\\?PHPUnit\\Framework\\TestCase',
+ 'TestCase', // \PHPUnit\Framework\TestCase with appropriate use statement
'DumpTestCase',
] );
$testClassRegex = "^class .* extends ($testClassRegex)";
diff --git a/www/wiki/tests/phpunit/suite.xml b/www/wiki/tests/phpunit/suite.xml
index e8256ef2..16c0c17c 100644
--- a/www/wiki/tests/phpunit/suite.xml
+++ b/www/wiki/tests/phpunit/suite.xml
@@ -16,6 +16,7 @@
beStrictAboutTestsThatDoNotTestAnything="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTestSize="true"
+ stderr="true"
verbose="false">
<testsuites>
<testsuite name="includes">
diff --git a/www/wiki/tests/phpunit/suites/ParserTestFileSuite.php b/www/wiki/tests/phpunit/suites/ParserTestFileSuite.php
index dbee8947..b72d8b84 100644
--- a/www/wiki/tests/phpunit/suites/ParserTestFileSuite.php
+++ b/www/wiki/tests/phpunit/suites/ParserTestFileSuite.php
@@ -23,6 +23,10 @@ class ParserTestFileSuite extends PHPUnit_Framework_TestSuite {
}
function setUp() {
- $this->ptRunner->addArticles( $this->ptFileInfo[ 'articles'] );
+ if ( !$this->ptRunner->meetsRequirements( $this->ptFileInfo['requirements'] ) ) {
+ $this->markTestSuiteSkipped( 'required extension not enabled' );
+ } else {
+ $this->ptRunner->addArticles( $this->ptFileInfo[ 'articles'] );
+ }
}
}
diff --git a/www/wiki/tests/phpunit/suites/UploadFromUrlTestSuite.php b/www/wiki/tests/phpunit/suites/UploadFromUrlTestSuite.php
index f2e6858a..556c7541 100644
--- a/www/wiki/tests/phpunit/suites/UploadFromUrlTestSuite.php
+++ b/www/wiki/tests/phpunit/suites/UploadFromUrlTestSuite.php
@@ -18,8 +18,7 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite {
protected function setUp() {
global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, $wgUser,
$wgLang, $wgOut, $wgRequest, $wgStyleDirectory,
- $wgParserCacheType, $wgNamespaceAliases, $wgNamespaceProtection,
- $parserMemc;
+ $wgParserCacheType, $wgNamespaceAliases, $wgNamespaceProtection;
$tmpDir = $this->getNewTempDirectory();
$tmpGlobals = [];
@@ -30,7 +29,7 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite {
$tmpGlobals['wgStylePath'] = '/skins';
$tmpGlobals['wgThumbnailScriptPath'] = false;
$tmpGlobals['wgLocalFileRepo'] = [
- 'class' => 'LocalRepo',
+ 'class' => LocalRepo::class,
'name' => 'local',
'url' => 'http://example.com/images',
'hashLevels' => 2,
@@ -89,54 +88,6 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite {
parent::tearDown();
}
- /**
- * Delete the specified files, if they exist.
- *
- * @param array $files Full paths to files to delete.
- */
- private static function deleteFiles( $files ) {
- foreach ( $files as $file ) {
- if ( file_exists( $file ) ) {
- unlink( $file );
- }
- }
- }
-
- /**
- * Delete the specified directories, if they exist. Must be empty.
- *
- * @param array $dirs Full paths to directories to delete.
- */
- private static function deleteDirs( $dirs ) {
- foreach ( $dirs as $dir ) {
- if ( is_dir( $dir ) ) {
- rmdir( $dir );
- }
- }
- }
-
- /**
- * Create a dummy uploads directory which will contain a couple
- * of files in order to pass existence tests.
- *
- * @return string The directory
- */
- private function setupUploadDir() {
- global $IP;
-
- $dir = $this->getNewTempDirectory();
-
- wfDebug( "Creating upload directory $dir\n" );
-
- wfMkdirParents( $dir . '/3/3a', null, __METHOD__ );
- copy( "$IP/tests/phpunit/data/upload/headbg.jpg", "$dir/3/3a/Foobar.jpg" );
-
- wfMkdirParents( $dir . '/0/09', null, __METHOD__ );
- copy( "$IP/tests/phpunit/data/upload/headbg.jpg", "$dir/0/09/Bad.jpg" );
-
- return $dir;
- }
-
public static function suite() {
// Hack to invoke the autoloader required to get phpunit to recognize
// the UploadFromUrlTest class
diff --git a/www/wiki/tests/phpunit/tests/MediaWikiTestCaseSchema1Test.php b/www/wiki/tests/phpunit/tests/MediaWikiTestCaseSchema1Test.php
new file mode 100644
index 00000000..d794d131
--- /dev/null
+++ b/www/wiki/tests/phpunit/tests/MediaWikiTestCaseSchema1Test.php
@@ -0,0 +1,51 @@
+<?php
+use Wikimedia\Rdbms\IMaintainableDatabase;
+
+/**
+ * @covers MediaWikiTestCase
+ *
+ * @group Database
+ * @group MediaWikiTestCaseTest
+ */
+class MediaWikiTestCaseSchema1Test extends MediaWikiTestCase {
+
+ public static $hasRun = false;
+
+ public function getSchemaOverrides( IMaintainableDatabase $db ) {
+ return [
+ 'create' => [ 'MediaWikiTestCaseTestTable', 'imagelinks' ],
+ 'drop' => [ 'oldimage' ],
+ 'alter' => [ 'pagelinks' ],
+ 'scripts' => [ __DIR__ . '/MediaWikiTestCaseSchemaTest.sql' ]
+ ];
+ }
+
+ public function testMediaWikiTestCaseSchemaTestOrder() {
+ // The test must be run before the second test
+ self::$hasRun = true;
+ $this->assertTrue( self::$hasRun );
+ }
+
+ public function testTableWasCreated() {
+ // Make sure MediaWikiTestCaseTestTable was created.
+ $this->assertTrue( $this->db->tableExists( 'MediaWikiTestCaseTestTable' ) );
+ }
+
+ public function testTableWasDropped() {
+ // Make sure oldimage was dropped
+ $this->assertFalse( $this->db->tableExists( 'oldimage' ) );
+ }
+
+ public function testTableWasOverriden() {
+ // Make sure imagelinks was overwritten
+ $this->assertTrue( $this->db->tableExists( 'imagelinks' ) );
+ $this->assertTrue( $this->db->fieldExists( 'imagelinks', 'il_frobnitz' ) );
+ }
+
+ public function testTableWasAltered() {
+ // Make sure pagelinks was altered
+ $this->assertTrue( $this->db->tableExists( 'pagelinks' ) );
+ $this->assertTrue( $this->db->fieldExists( 'pagelinks', 'pl_frobnitz' ) );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/tests/MediaWikiTestCaseSchema2Test.php b/www/wiki/tests/phpunit/tests/MediaWikiTestCaseSchema2Test.php
new file mode 100644
index 00000000..5464dc43
--- /dev/null
+++ b/www/wiki/tests/phpunit/tests/MediaWikiTestCaseSchema2Test.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @covers MediaWikiTestCase
+ *
+ * @group Database
+ * @group MediaWikiTestCaseTest
+ *
+ * This test is intended to be executed AFTER MediaWikiTestCaseSchema1Test to ensure
+ * that any schema modifications have been cleaned up between test cases.
+ * As there seems to be no way to force execution order, we currently rely on
+ * test classes getting run in anpha-numerical order.
+ * Order is checked by the testMediaWikiTestCaseSchemaTestOrder test in both classes.
+ */
+class MediaWikiTestCaseSchema2Test extends MediaWikiTestCase {
+
+ public function testMediaWikiTestCaseSchemaTestOrder() {
+ // The first test must have run before this one
+ $this->assertTrue( MediaWikiTestCaseSchema1Test::$hasRun );
+ }
+
+ public function testCreatedTableWasRemoved() {
+ // Make sure MediaWikiTestCaseTestTable created by MediaWikiTestCaseSchema1Test
+ // was dropped before executing MediaWikiTestCaseSchema2Test.
+ $this->assertFalse( $this->db->tableExists( 'MediaWikiTestCaseTestTable' ) );
+ }
+
+ public function testDroppedTableWasRestored() {
+ // Make sure oldimage that was dropped by MediaWikiTestCaseSchema1Test
+ // was restored before executing MediaWikiTestCaseSchema2Test.
+ $this->assertTrue( $this->db->tableExists( 'oldimage' ) );
+ }
+
+ public function testOverridenTableWasRestored() {
+ // Make sure imagelinks overwritten by MediaWikiTestCaseSchema1Test
+ // was restored to the original schema before executing MediaWikiTestCaseSchema2Test.
+ $this->assertTrue( $this->db->tableExists( 'imagelinks' ) );
+ $this->assertFalse( $this->db->fieldExists( 'imagelinks', 'il_frobnitz' ) );
+ }
+
+ public function testAlteredTableWasRestored() {
+ // Make sure pagelinks altered by MediaWikiTestCaseSchema1Test
+ // was restored to the original schema before executing MediaWikiTestCaseSchema2Test.
+ $this->assertTrue( $this->db->tableExists( 'pagelinks' ) );
+ $this->assertFalse( $this->db->fieldExists( 'pagelinks', 'pl_frobnitz' ) );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/tests/MediaWikiTestCaseSchemaTest.sql b/www/wiki/tests/phpunit/tests/MediaWikiTestCaseSchemaTest.sql
new file mode 100644
index 00000000..e2818b55
--- /dev/null
+++ b/www/wiki/tests/phpunit/tests/MediaWikiTestCaseSchemaTest.sql
@@ -0,0 +1,18 @@
+CREATE TABLE /*_*/MediaWikiTestCaseTestTable (
+ id INT NOT NULL,
+ name VARCHAR(20) NOT NULL,
+ PRIMARY KEY (id)
+) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*_*/imagelinks (
+ il_from int NOT NULL DEFAULT 0,
+ il_from_namespace int NOT NULL DEFAULT 0,
+ il_to varchar(127) NOT NULL DEFAULT '',
+ il_frobnitz varchar(127) NOT NULL DEFAULT 'FROB',
+ PRIMARY KEY (il_from,il_to)
+) /*$wgDBTableOptions*/;
+
+ALTER TABLE /*_*/pagelinks
+ADD pl_frobnitz varchar(127) NOT NULL DEFAULT 'FROB';
+
+DROP TABLE /*_*/oldimage;
diff --git a/www/wiki/tests/phpunit/tests/MediaWikiTestCaseTest.php b/www/wiki/tests/phpunit/tests/MediaWikiTestCaseTest.php
index 7d75ffe6..1850f6fe 100644
--- a/www/wiki/tests/phpunit/tests/MediaWikiTestCaseTest.php
+++ b/www/wiki/tests/phpunit/tests/MediaWikiTestCaseTest.php
@@ -2,9 +2,12 @@
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use Psr\Log\LoggerInterface;
+use Wikimedia\Rdbms\LoadBalancer;
/**
* @covers MediaWikiTestCase
+ * @group MediaWikiTestCaseTest
+ *
* @author Addshore
*/
class MediaWikiTestCaseTest extends MediaWikiTestCase {
@@ -162,7 +165,7 @@ class MediaWikiTestCaseTest extends MediaWikiTestCase {
$logger2 = LoggerFactory::getInstance( 'foo' );
$this->assertNotSame( $logger1, $logger2 );
- $this->assertInstanceOf( '\Psr\Log\LoggerInterface', $logger2 );
+ $this->assertInstanceOf( \Psr\Log\LoggerInterface::class, $logger2 );
}
/**
diff --git a/www/wiki/tests/qunit/QUnitTestResources.php b/www/wiki/tests/qunit/QUnitTestResources.php
index 7367560e..785e1146 100644
--- a/www/wiki/tests/qunit/QUnitTestResources.php
+++ b/www/wiki/tests/qunit/QUnitTestResources.php
@@ -33,7 +33,6 @@ return [
'mediawiki.page.startup',
'test.sinonjs',
],
- 'position' => 'top',
'targets' => [ 'desktop', 'mobile' ],
],
@@ -46,14 +45,12 @@ return [
'scripts' => [
'tests/qunit/suites/resources/startup.test.js',
'tests/qunit/suites/resources/jquery/jquery.accessKeyLabel.test.js',
- 'tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js',
- 'tests/qunit/suites/resources/jquery/jquery.byteLength.test.js',
- 'tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js',
'tests/qunit/suites/resources/jquery/jquery.color.test.js',
'tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js',
'tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js',
'tests/qunit/suites/resources/jquery/jquery.hidpi.test.js',
'tests/qunit/suites/resources/jquery/jquery.highlightText.test.js',
+ 'tests/qunit/suites/resources/jquery/jquery.lengthLimit.test.js',
'tests/qunit/suites/resources/jquery/jquery.localize.test.js',
'tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js',
'tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js',
@@ -67,6 +64,8 @@ return [
'tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js',
+ 'tests/qunit/suites/resources/mediawiki/mediawiki.String.byteLength.test.js',
+ 'tests/qunit/suites/resources/mediawiki/mediawiki.String.trimByteLength.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',
@@ -93,22 +92,23 @@ return [
'tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js',
'tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js',
'tests/qunit/suites/resources/mediawiki.rcfilters/dm.FilterItem.test.js',
+ 'tests/qunit/suites/resources/mediawiki.rcfilters/dm.SavedQueryItemModel.test.js',
+ 'tests/qunit/suites/resources/mediawiki.rcfilters/dm.SavedQueriesModel.test.js',
'tests/qunit/suites/resources/mediawiki.rcfilters/UriProcessor.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.experiments.test.js',
+ 'tests/qunit/suites/resources/mediawiki/mediawiki.visibleTimeout.test.js',
],
'dependencies' => [
'jquery.accessKeyLabel',
- 'jquery.autoEllipsis',
- 'jquery.byteLength',
- 'jquery.byteLimit',
'jquery.color',
'jquery.colorUtil',
'jquery.getAttrs',
'jquery.hidpi',
'jquery.highlightText',
+ 'jquery.lengthLimit',
'jquery.localize',
'jquery.makeCollapsible',
'jquery.tabIndex',
@@ -125,6 +125,7 @@ return [
'mediawiki.jqueryMsg',
'mediawiki.messagePoster',
'mediawiki.RegExp',
+ 'mediawiki.String',
'mediawiki.storage',
'mediawiki.Title',
'mediawiki.toc',
@@ -141,6 +142,7 @@ return [
'mediawiki.cookie',
'mediawiki.experiments',
'mediawiki.inspect',
+ 'mediawiki.visibleTimeout',
'test.mediawiki.qunit.testrunner',
],
]
diff --git a/www/wiki/tests/qunit/data/callMwLoaderTestCallback.js b/www/wiki/tests/qunit/data/callMwLoaderTestCallback.js
deleted file mode 100644
index dd034115..00000000
--- a/www/wiki/tests/qunit/data/callMwLoaderTestCallback.js
+++ /dev/null
@@ -1 +0,0 @@
-mediaWiki.loader.testCallback();
diff --git a/www/wiki/tests/qunit/data/generateJqueryMsgData.php b/www/wiki/tests/qunit/data/generateJqueryMsgData.php
index 1c79f6d1..e4f87f81 100644
--- a/www/wiki/tests/qunit/data/generateJqueryMsgData.php
+++ b/www/wiki/tests/qunit/data/generateJqueryMsgData.php
@@ -21,7 +21,7 @@
$.each( mw.libs.phpParserData.tests, function ( i, test ) {
QUnit.stop();
getMwLanguage( test.lang, function ( langClass ) {
- var parser = new mw.jqueryMsg.parser( { language: langClass } );
+ var parser = new mw.jqueryMsg.Parser( { language: langClass } );
assert.equal(
parser.parse( test.key, test.args ).html(),
test.result,
@@ -50,7 +50,7 @@
}, 'Language class should be loaded', 1000 );
runs( function () {
console.log( test.lang, 'running tests' );
- var parser = new mw.jqueryMsg.parser( { language: langClass } );
+ var parser = new mw.jqueryMsg.Parser( { language: langClass } );
expect(
parser.parse( test.key, test.args ).html()
).toEqual( test.result );
diff --git a/www/wiki/tests/qunit/data/load.mock.php b/www/wiki/tests/qunit/data/load.mock.php
index 671bdf1f..23009498 100644
--- a/www/wiki/tests/qunit/data/load.mock.php
+++ b/www/wiki/tests/qunit/data/load.mock.php
@@ -24,27 +24,22 @@
*/
header( 'Content-Type: text/javascript; charset=utf-8' );
-require_once __DIR__ . '/../../../includes/json/FormatJson.php';
-require_once __DIR__ . '/../../../includes/Xml.php';
-
$moduleImplementations = [
'testUsesMissing' => "
mw.loader.implement( 'testUsesMissing', function () {
- QUnit.ok( false, 'Module usesMissing script should not run.' );
- QUnit.start();
+ mw.loader.testFail( 'Module usesMissing script should not run.' );
}, {}, {});
",
'testUsesNestedMissing' => "
mw.loader.implement( 'testUsesNestedMissing', function () {
- QUnit.ok( false, 'Module testUsesNestedMissing script should not run.' );
- QUnit.start();
+ mw.loader.testFail('Module testUsesNestedMissing script should not run.' );
}, {}, {});
",
'testSkipped' => "
mw.loader.implement( 'testSkipped', function () {
- QUnit.ok( false, 'Module testSkipped was supposed to be skipped.' );
+ mw.loader.testFail( false, 'Module testSkipped was supposed to be skipped.' );
}, {}, {});
",
@@ -55,18 +50,56 @@ mw.loader.implement( 'testNotSkipped', function () {}, {}, {});
'testUsesSkippable' => "
mw.loader.implement( 'testUsesSkippable', function () {}, {}, {});
",
+
+ 'testUrlInc' => "
+mw.loader.implement( 'testUrlInc', function () {} );
+",
+ 'testUrlInc.a' => "
+mw.loader.implement( 'testUrlInc.a', function () {} );
+",
+ 'testUrlInc.b' => "
+mw.loader.implement( 'testUrlInc.b', function () {} );
+",
+ 'testUrlOrder' => "
+mw.loader.implement( 'testUrlOrder', function () {} );
+",
+ 'testUrlOrder.a' => "
+mw.loader.implement( 'testUrlOrder.a', function () {} );
+",
+ 'testUrlOrder.b' => "
+mw.loader.implement( 'testUrlOrder.b', function () {} );
+",
];
$response = '';
-// Only support for non-encoded module names, full module names expected
+// Does not support the full behaviour of ResourceLoaderContext::expandModuleNames(),
+// Only supports dotless module names joined by comma,
+// with the exception of the hardcoded cases for testUrl*.
if ( isset( $_GET['modules'] ) ) {
- $modules = explode( ',', $_GET['modules'] );
+ if ( $_GET['modules'] === 'testUrlInc,testUrlIncDump|testUrlInc.a,b' ) {
+ $modules = [ 'testUrlInc', 'testUrlIncDump', 'testUrlInc.a', 'testUrlInc.b' ];
+ } elseif ( $_GET['modules'] === 'testUrlOrder,testUrlOrderDump|testUrlOrder.a,b' ) {
+ $modules = [ 'testUrlOrder', 'testUrlOrderDump', 'testUrlOrder.a', 'testUrlOrder.b' ];
+ } else {
+ $modules = explode( ',', $_GET['modules'] );
+ }
foreach ( $modules as $module ) {
if ( isset( $moduleImplementations[$module] ) ) {
$response .= $moduleImplementations[$module];
+ } elseif ( preg_match( '/^test.*Dump$/', $module ) === 1 ) {
+ $queryModules = $_GET['modules'];
+ $queryVersion = isset( $_GET['version'] ) ? strval( $_GET['version'] ) : null;
+ $response .= 'mw.loader.implement( ' . json_encode( $module )
+ . ', function ( $, jQuery, require, module ) {'
+ . 'module.exports.query = { '
+ . 'modules: ' . json_encode( $queryModules ) . ','
+ . 'version: ' . json_encode( $queryVersion )
+ . ' };'
+ . '} );';
} else {
- $response .= Xml::encodeJsCall( 'mw.loader.state', [ $module, 'missing' ], true );
+ // Default
+ $response .= 'mw.loader.state(' . json_encode( $module ) . ', "missing" );' . "\n";
}
}
}
diff --git a/www/wiki/tests/qunit/data/qunitOkCall.js b/www/wiki/tests/qunit/data/qunitOkCall.js
deleted file mode 100644
index 3ed5514e..00000000
--- a/www/wiki/tests/qunit/data/qunitOkCall.js
+++ /dev/null
@@ -1,2 +0,0 @@
-QUnit.start();
-QUnit.assert.ok( true, 'Successfully loaded!' );
diff --git a/www/wiki/tests/qunit/data/testrunner.js b/www/wiki/tests/qunit/data/testrunner.js
index e944ef01..06c146c2 100644
--- a/www/wiki/tests/qunit/data/testrunner.js
+++ b/www/wiki/tests/qunit/data/testrunner.js
@@ -6,18 +6,21 @@
/**
* Make a safe copy of localEnv:
- * - Creates a copy so that when the same object reference to module hooks is
- * used by multipe test hooks, our QUnit.module extension will not wrap the
- * callbacks multiple times. Instead, they wrap using a new object.
- * - Normalise setup/teardown to avoid having to repeat this in each extension
+ * - Creates a new object that inherits, instead of modifying the original.
+ * This prevents recursion in the event that a test suite stores inherits
+ * hooks object statically and passes it to multiple QUnit.module() calls.
+ * - Supporting QUnit 1.x 'setup' and 'teardown' hooks
* (deprecated in QUnit 1.16, removed in QUnit 2).
- * - Strip any other properties.
*/
function makeSafeEnv( localEnv ) {
- return {
- beforeEach: localEnv.setup || localEnv.beforeEach,
- afterEach: localEnv.teardown || localEnv.afterEach
- };
+ var wrap = localEnv ? Object.create( localEnv ) : {};
+ if ( wrap.setup ) {
+ wrap.beforeEach = wrap.beforeEach || wrap.setup;
+ }
+ if ( wrap.teardown ) {
+ wrap.afterEach = wrap.afterEach || wrap.teardown;
+ }
+ return wrap;
}
/**
@@ -73,13 +76,16 @@
useFakeTimers: false,
useFakeServer: false
};
- // Extend QUnit.module to provide a Sinon sandbox.
+ // Extend QUnit.module with:
+ // - Add support for QUnit 1.x 'setup' and 'teardown' hooks
+ // - Add a Sinon sandbox to the test context.
+ // - Add a test fixture to the test context.
( function () {
var orgModule = QUnit.module;
QUnit.module = function ( name, localEnv, executeNow ) {
- var orgBeforeEach, orgAfterEach, orgExecute;
+ var orgExecute, orgBeforeEach, orgAfterEach;
if ( nested ) {
- // In a nested module, don't re-run our handlers.
+ // In a nested module, don't re-add our hooks, QUnit does that already.
return orgModule.apply( this, arguments );
}
if ( arguments.length === 2 && typeof localEnv === 'function' ) {
@@ -98,49 +104,17 @@
};
}
- localEnv = localEnv || {};
+ localEnv = makeSafeEnv( localEnv );
orgBeforeEach = localEnv.beforeEach;
orgAfterEach = localEnv.afterEach;
+
localEnv.beforeEach = function () {
+ // Sinon sandbox
var config = sinon.getConfig( sinon.config );
config.injectInto = this;
sinon.sandbox.create( config );
- if ( orgBeforeEach ) {
- return orgBeforeEach.apply( this, arguments );
- }
- };
- localEnv.afterEach = function () {
- var ret;
- if ( orgAfterEach ) {
- ret = orgAfterEach.apply( this, arguments );
- }
-
- this.sandbox.verifyAndRestore();
- return ret;
- };
- return orgModule( name, localEnv, executeNow );
- };
- }() );
-
- // Extend QUnit.module to provide a fixture element.
- ( function () {
- var orgModule = QUnit.module;
- QUnit.module = function ( name, localEnv, executeNow ) {
- var orgBeforeEach, orgAfterEach;
- if ( nested ) {
- // In a nested module, don't re-run our handlers.
- return orgModule.apply( this, arguments );
- }
- if ( arguments.length === 2 && typeof localEnv === 'function' ) {
- executeNow = localEnv;
- localEnv = undefined;
- }
-
- localEnv = localEnv || {};
- orgBeforeEach = localEnv.beforeEach;
- orgAfterEach = localEnv.afterEach;
- localEnv.beforeEach = function () {
+ // Fixture element
this.fixture = document.createElement( 'div' );
this.fixture.id = 'qunit-fixture';
document.body.appendChild( this.fixture );
@@ -154,23 +128,11 @@
if ( orgAfterEach ) {
ret = orgAfterEach.apply( this, arguments );
}
-
+ this.sandbox.verifyAndRestore();
this.fixture.parentNode.removeChild( this.fixture );
return ret;
};
- return orgModule( name, localEnv, executeNow );
- };
- }() );
- // Extend QUnit.module to normalise localEnv.
- // NOTE: This MUST be the last QUnit.module extension so that the above extensions
- // may safely modify the object and assume beforeEach/afterEach.
- ( function () {
- var orgModule = QUnit.module;
- QUnit.module = function ( name, localEnv, executeNow ) {
- if ( typeof localEnv === 'object' ) {
- localEnv = makeSafeEnv( localEnv );
- }
return orgModule( name, localEnv, executeNow );
};
}() );
@@ -239,98 +201,101 @@
}
return function ( orgEnv ) {
- var localEnv = orgEnv ? makeSafeEnv( orgEnv ) : {};
- // MediaWiki env testing
- localEnv.config = orgEnv && orgEnv.config || {};
- localEnv.messages = orgEnv && orgEnv.messages || {};
-
- return {
- beforeEach: function () {
- // Greetings, mock environment!
- mw.config = new MwMap();
- mw.config.set( freshConfigCopy( localEnv.config ) );
- mw.messages = new MwMap();
- mw.messages.set( freshMessagesCopy( localEnv.messages ) );
- // Update reference to mw.messages
- mw.jqueryMsg.setParserDefaults( {
- messages: mw.messages
- } );
-
- this.suppressWarnings = suppressWarnings;
- this.restoreWarnings = restoreWarnings;
+ var localEnv, orgBeforeEach, orgAfterEach;
- // Start tracking ajax requests
- $( document ).on( 'ajaxSend', trackAjax );
-
- if ( localEnv.beforeEach ) {
- return localEnv.beforeEach.apply( this, arguments );
- }
- },
+ localEnv = makeSafeEnv( orgEnv );
+ // MediaWiki env testing
+ localEnv.config = localEnv.config || {};
+ localEnv.messages = localEnv.messages || {};
- afterEach: function () {
- var timers, pending, $activeLen, ret;
+ orgBeforeEach = localEnv.beforeEach;
+ orgAfterEach = localEnv.afterEach;
- if ( localEnv.afterEach ) {
- ret = localEnv.afterEach.apply( this, arguments );
- }
+ localEnv.beforeEach = function () {
+ // Greetings, mock environment!
+ mw.config = new MwMap();
+ mw.config.set( freshConfigCopy( localEnv.config ) );
+ mw.messages = new MwMap();
+ mw.messages.set( freshMessagesCopy( localEnv.messages ) );
+ // Update reference to mw.messages
+ mw.jqueryMsg.setParserDefaults( {
+ messages: mw.messages
+ } );
+
+ this.suppressWarnings = suppressWarnings;
+ this.restoreWarnings = restoreWarnings;
+
+ // Start tracking ajax requests
+ $( document ).on( 'ajaxSend', trackAjax );
- // Stop tracking ajax requests
- $( document ).off( 'ajaxSend', trackAjax );
+ if ( orgBeforeEach ) {
+ return orgBeforeEach.apply( this, arguments );
+ }
+ };
+ localEnv.afterEach = function () {
+ var timers, pending, $activeLen, ret;
- // As a convenience feature, automatically restore warnings if they're
- // still suppressed by the end of the test.
- restoreWarnings();
+ if ( orgAfterEach ) {
+ ret = orgAfterEach.apply( this, arguments );
+ }
- // Farewell, mock environment!
- mw.config = liveConfig;
- mw.messages = liveMessages;
- // Restore reference to mw.messages
- mw.jqueryMsg.setParserDefaults( {
- messages: liveMessages
+ // Stop tracking ajax requests
+ $( document ).off( 'ajaxSend', trackAjax );
+
+ // As a convenience feature, automatically restore warnings if they're
+ // still suppressed by the end of the test.
+ restoreWarnings();
+
+ // Farewell, mock environment!
+ mw.config = liveConfig;
+ mw.messages = liveMessages;
+ // Restore reference to mw.messages
+ mw.jqueryMsg.setParserDefaults( {
+ messages: liveMessages
+ } );
+
+ // Tests should use fake timers or wait for animations to complete
+ // Check for incomplete animations/requests/etc and throw if there are any.
+ if ( $.timers && $.timers.length !== 0 ) {
+ timers = $.timers.length;
+ $.each( $.timers, function ( i, timer ) {
+ var node = timer.elem;
+ mw.log.warn( 'Unfinished animation #' + i + ' in ' + timer.queue + ' queue on ' +
+ mw.html.element( node.nodeName.toLowerCase(), $( node ).getAttrs() )
+ );
} );
+ // Force animations to stop to give the next test a clean start
+ $.timers = [];
+ $.fx.stop();
- // Tests should use fake timers or wait for animations to complete
- // Check for incomplete animations/requests/etc and throw if there are any.
- if ( $.timers && $.timers.length !== 0 ) {
- timers = $.timers.length;
- $.each( $.timers, function ( i, timer ) {
- var node = timer.elem;
- mw.log.warn( 'Unfinished animation #' + i + ' in ' + timer.queue + ' queue on ' +
- mw.html.element( node.nodeName.toLowerCase(), $( node ).getAttrs() )
- );
- } );
- // Force animations to stop to give the next test a clean start
- $.timers = [];
- $.fx.stop();
-
- throw new Error( 'Unfinished animations: ' + timers );
- }
+ throw new Error( 'Unfinished animations: ' + timers );
+ }
- // Test should use fake XHR, wait for requests, or call abort()
- $activeLen = $.active;
- if ( $activeLen !== undefined && $activeLen !== 0 ) {
- pending = $.grep( ajaxRequests, function ( ajax ) {
- return ajax.xhr.state() === 'pending';
- } );
- if ( pending.length !== $activeLen ) {
- mw.log.warn( 'Pending requests does not match jQuery.active count' );
- }
- // Force requests to stop to give the next test a clean start
- $.each( ajaxRequests, function ( i, ajax ) {
- mw.log.warn(
- 'AJAX request #' + i + ' (state: ' + ajax.xhr.state() + ')',
- ajax.options
- );
- ajax.xhr.abort();
- } );
- ajaxRequests = [];
-
- throw new Error( 'Pending AJAX requests: ' + pending.length + ' (active: ' + $activeLen + ')' );
+ // Test should use fake XHR, wait for requests, or call abort()
+ $activeLen = $.active;
+ if ( $activeLen !== undefined && $activeLen !== 0 ) {
+ pending = ajaxRequests.filter( function ( ajax ) {
+ return ajax.xhr.state() === 'pending';
+ } );
+ if ( pending.length !== $activeLen ) {
+ mw.log.warn( 'Pending requests does not match jQuery.active count' );
}
+ // Force requests to stop to give the next test a clean start
+ ajaxRequests.forEach( function ( ajax, i ) {
+ mw.log.warn(
+ 'AJAX request #' + i + ' (state: ' + ajax.xhr.state() + ')',
+ ajax.options
+ );
+ ajax.xhr.abort();
+ } );
+ ajaxRequests = [];
- return ret;
+ throw new Error( 'Pending AJAX requests: ' + pending.length + ' (active: ' + $activeLen + ')' );
}
+
+ return ret;
};
+ return localEnv;
};
}() );
@@ -657,4 +622,31 @@
} );
} );
+ QUnit.module( 'testrunner-hooks-outer', function () {
+ var beforeHookWasExecuted = false,
+ afterHookWasExecuted = false;
+ QUnit.module( 'testrunner-hooks', {
+ before: function () {
+ beforeHookWasExecuted = true;
+
+ // This way we can be sure that module `testrunner-hook-after` will always
+ // be executed after module `testrunner-hooks`
+ QUnit.module( 'testrunner-hooks-after' );
+ QUnit.test(
+ '`after` hook for module `testrunner-hooks` was executed',
+ function ( assert ) {
+ assert.ok( afterHookWasExecuted );
+ }
+ );
+ },
+ after: function () {
+ afterHookWasExecuted = true;
+ }
+ } );
+
+ QUnit.test( '`before` hook was executed', function ( assert ) {
+ assert.ok( beforeHookWasExecuted );
+ } );
+ } );
+
}( jQuery, mediaWiki, QUnit ) );
diff --git a/www/wiki/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js b/www/wiki/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js
deleted file mode 100644
index c3521ba8..00000000
--- a/www/wiki/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js
+++ /dev/null
@@ -1,53 +0,0 @@
-( function ( $ ) {
-
- QUnit.module( 'jquery.autoEllipsis', QUnit.newMwEnvironment() );
-
- function createWrappedDiv( text, width ) {
- var $wrapper = $( '<div>' ).css( 'width', width ),
- $div = $( '<div>' ).text( text );
- $wrapper.append( $div );
- return $wrapper;
- }
-
- function findDivergenceIndex( a, b ) {
- var i = 0;
- while ( i < a.length && i < b.length && a[ i ] === b[ i ] ) {
- i++;
- }
- return i;
- }
-
- QUnit.test( 'Position right', function ( assert ) {
- // We need this thing to be visible, so append it to the DOM
- var $span, spanText, d, spanTextNew,
- origText = 'This is a really long random string and there is no way it fits in 100 pixels.',
- $wrapper = createWrappedDiv( origText, '100px' );
-
- $( '#qunit-fixture' ).append( $wrapper );
- $wrapper.autoEllipsis( { position: 'right' } );
-
- // Verify that, and only one, span element was created
- $span = $wrapper.find( '> span' );
- assert.strictEqual( $span.length, 1, 'autoEllipsis wrapped the contents in a span element' );
-
- // Check that the text fits by turning on word wrapping
- $span.css( 'whiteSpace', 'nowrap' );
- assert.ltOrEq(
- $span.width(),
- $span.parent().width(),
- 'Text fits (making the span "white-space: nowrap" does not make it wider than its parent)'
- );
-
- // Add two characters using scary black magic
- spanText = $span.text();
- d = findDivergenceIndex( origText, spanText );
- spanTextNew = spanText.slice( 0, d ) + origText[ d ] + origText[ d ] + '...';
-
- assert.gt( spanTextNew.length, spanText.length, 'Verify that the new span-length is indeed greater' );
-
- // Put this text in the span and verify it doesn't fit
- $span.text( spanTextNew );
- assert.gt( $span.width(), $span.parent().width(), 'Fit is maximal (adding two characters makes it not fit any more)' );
- } );
-
-}( jQuery ) );
diff --git a/www/wiki/tests/qunit/suites/resources/jquery/jquery.byteLength.test.js b/www/wiki/tests/qunit/suites/resources/jquery/jquery.byteLength.test.js
deleted file mode 100644
index 558e6416..00000000
--- a/www/wiki/tests/qunit/suites/resources/jquery/jquery.byteLength.test.js
+++ /dev/null
@@ -1,37 +0,0 @@
-( function ( $ ) {
- QUnit.module( 'jquery.byteLength', QUnit.newMwEnvironment() );
-
- QUnit.test( 'Simple text', function ( assert ) {
- var azLc = 'abcdefghijklmnopqrstuvwxyz',
- azUc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
- num = '0123456789',
- x = '*',
- space = ' ';
-
- assert.equal( $.byteLength( azLc ), 26, 'Lowercase a-z' );
- assert.equal( $.byteLength( azUc ), 26, 'Uppercase A-Z' );
- assert.equal( $.byteLength( num ), 10, 'Numbers 0-9' );
- assert.equal( $.byteLength( x ), 1, 'An asterisk' );
- assert.equal( $.byteLength( space ), 3, '3 spaces' );
-
- } );
-
- QUnit.test( 'Special text', function ( assert ) {
- // https://en.wikipedia.org/wiki/UTF-8
- var u0024 = '$',
- // Cent symbol
- u00A2 = '\u00A2',
- // Euro symbol
- u20AC = '\u20AC',
- // Character \U00024B62 (Han script) can't be represented in javascript as a single
- // code point, instead it is composed as a surrogate pair of two separate code units.
- // http://codepoints.net/U+24B62
- // http://www.fileformat.info/info/unicode/char/24B62/index.htm
- u024B62 = '\uD852\uDF62';
-
- assert.strictEqual( $.byteLength( u0024 ), 1, 'U+0024' );
- assert.strictEqual( $.byteLength( u00A2 ), 2, 'U+00A2' );
- assert.strictEqual( $.byteLength( u20AC ), 3, 'U+20AC' );
- assert.strictEqual( $.byteLength( u024B62 ), 4, 'U+024B62 (surrogate pair: \\uD852\\uDF62)' );
- } );
-}( jQuery ) );
diff --git a/www/wiki/tests/qunit/suites/resources/jquery/jquery.highlightText.test.js b/www/wiki/tests/qunit/suites/resources/jquery/jquery.highlightText.test.js
index 0dac22e9..277ba3f2 100644
--- a/www/wiki/tests/qunit/suites/resources/jquery/jquery.highlightText.test.js
+++ b/www/wiki/tests/qunit/suites/resources/jquery/jquery.highlightText.test.js
@@ -222,7 +222,7 @@
}
];
- $.each( cases, function ( i, item ) {
+ cases.forEach( function ( item ) {
$fixture = $( '<p>' ).text( item.text ).highlightText( item.highlight );
assert.equal(
$fixture.html(),
diff --git a/www/wiki/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js b/www/wiki/tests/qunit/suites/resources/jquery/jquery.lengthLimit.test.js
index 8555a7e4..7117d1f4 100644
--- a/www/wiki/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js
+++ b/www/wiki/tests/qunit/suites/resources/jquery/jquery.lengthLimit.test.js
@@ -1,7 +1,7 @@
( function ( $, mw ) {
- var simpleSample, U_20AC, mbSample;
+ var simpleSample, U_20AC, poop, mbSample;
- QUnit.module( 'jquery.byteLimit', QUnit.newMwEnvironment() );
+ QUnit.module( 'jquery.lengthLimit', QUnit.newMwEnvironment() );
// Simple sample (20 chars, 20 bytes)
simpleSample = '12345678901234567890';
@@ -9,6 +9,9 @@
// 3 bytes (euro-symbol)
U_20AC = '\u20AC';
+ // Outside of the BMP (pile of poo emoji)
+ poop = '\uD83D\uDCA9'; // "💩"
+
// Multi-byte sample (22 chars, 26 bytes)
mbSample = '1234567890' + U_20AC + '1234567890' + U_20AC;
@@ -110,6 +113,14 @@
} );
byteLimitTest( {
+ description: 'Limit using a custom value (multibyte, outside BMP)',
+ $input: $( '<input>' ).attr( 'type', 'text' )
+ .byteLimit( 3 ),
+ sample: poop,
+ expected: ''
+ } );
+
+ byteLimitTest( {
description: 'Limit using a custom value (multibyte) overlapping a byte',
$input: $( '<input>' ).attr( 'type', 'text' )
.byteLimit( 12 ),
@@ -176,8 +187,6 @@
return 'prefix' + text;
} ),
sample: simpleSample,
- hasLimit: true,
- limit: 6, // 'prefix' length
expected: ''
} );
@@ -245,4 +254,33 @@
assert.strictEqual( $el.val(), 'abc', 'Trim from the insertion point (at 1), not the end' );
} );
+
+ QUnit.test( 'Do not cut up false matching substrings in emoji insertions', function ( assert ) {
+ var $el,
+ oldVal = '\uD83D\uDCA9\uD83D\uDCA9', // "💩💩"
+ newVal = '\uD83D\uDCA9\uD83D\uDCB9\uD83E\uDCA9\uD83D\uDCA9', // "💩💹🢩💩"
+ expected = '\uD83D\uDCA9\uD83D\uDCB9\uD83D\uDCA9'; // "💩💹💩"
+
+ // Possible bad results:
+ // * With no surrogate support:
+ // '\uD83D\uDCA9\uD83D\uDCB9\uD83E\uDCA9' "💩💹🢩"
+ // * With correct trimming but bad detection of inserted text:
+ // '\uD83D\uDCA9\uD83D\uDCB9\uDCA9' "💩💹�"
+
+ $el = $( '<input>' ).attr( 'type', 'text' )
+ .appendTo( '#qunit-fixture' )
+ .byteLimit( 12 )
+ .val( oldVal ).trigger( 'change' )
+ .val( newVal ).trigger( 'change' );
+
+ assert.strictEqual( $el.val(), expected, 'Pasted emoji correctly trimmed at the end' );
+ } );
+
+ byteLimitTest( {
+ description: 'Unpaired surrogates do not crash',
+ $input: $( '<input>' ).attr( 'type', 'text' ).byteLimit( 4 ),
+ sample: '\uD800\uD800\uDFFF',
+ expected: '\uD800'
+ } );
+
}( jQuery, mediaWiki ) );
diff --git a/www/wiki/tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js b/www/wiki/tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js
index 0c91e43f..d51dc373 100644
--- a/www/wiki/tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js
+++ b/www/wiki/tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function ( $ ) {
var loremIpsum = 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.';
QUnit.module( 'jquery.makeCollapsible', QUnit.newMwEnvironment() );
@@ -374,4 +374,4 @@
$clone.find( '.mw-collapsible-toggle a' ).trigger( 'click' );
} );
-}( mediaWiki, jQuery ) );
+}( jQuery ) );
diff --git a/www/wiki/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js b/www/wiki/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js
deleted file mode 100644
index 029edd55..00000000
--- a/www/wiki/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js
+++ /dev/null
@@ -1,64 +0,0 @@
-( function ( $ ) {
- QUnit.module( 'jquery.mwExtension', QUnit.newMwEnvironment( {
- // This entire module is deprecated.
- // Surpress deprecation warnings in test output.
- setup: function () {
- this.suppressWarnings();
- },
- teardown: function () {
- this.restoreWarnings();
- }
- } ) );
-
- QUnit.test( 'String functions', 7, function ( assert ) {
- assert.equal( $.trimLeft( ' foo bar ' ), 'foo bar ', 'trimLeft' );
- assert.equal( $.trimRight( ' foo bar ' ), ' foo bar', 'trimRight' );
- assert.equal( $.ucFirst( 'foo' ), 'Foo', 'ucFirst' );
-
- assert.equal( $.escapeRE( '<!-- ([{+mW+}]) $^|?>' ),
- '<!\\-\\- \\(\\[\\{\\+mW\\+\\}\\]\\) \\$\\^\\|\\?>', 'escapeRE - Escape specials' );
- assert.equal( $.escapeRE( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ),
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'escapeRE - Leave uppercase alone' );
- assert.equal( $.escapeRE( 'abcdefghijklmnopqrstuvwxyz' ),
- 'abcdefghijklmnopqrstuvwxyz', 'escapeRE - Leave lowercase alone' );
- assert.equal( $.escapeRE( '0123456789' ), '0123456789', 'escapeRE - Leave numbers alone' );
- } );
-
- QUnit.test( 'isDomElement', 6, function ( assert ) {
- assert.strictEqual( $.isDomElement( document.createElement( 'div' ) ), true,
- 'isDomElement: HTMLElement' );
- assert.strictEqual( $.isDomElement( document.createTextNode( '' ) ), true,
- 'isDomElement: TextNode' );
- assert.strictEqual( $.isDomElement( null ), false,
- 'isDomElement: null' );
- assert.strictEqual( $.isDomElement( document.getElementsByTagName( 'div' ) ), false,
- 'isDomElement: NodeList' );
- assert.strictEqual( $.isDomElement( $( 'div' ) ), false,
- 'isDomElement: jQuery' );
- assert.strictEqual( $.isDomElement( { foo: 1 } ), false,
- 'isDomElement: Plain Object' );
- } );
-
- QUnit.test( 'isEmpty', 7, function ( assert ) {
- assert.strictEqual( $.isEmpty( 'string' ), false, 'isEmpty: "string"' );
- assert.strictEqual( $.isEmpty( '0' ), true, 'isEmpty: "0"' );
- assert.strictEqual( $.isEmpty( '' ), true, 'isEmpty: ""' );
- assert.strictEqual( $.isEmpty( 1 ), false, 'isEmpty: 1' );
- assert.strictEqual( $.isEmpty( [] ), true, 'isEmpty: []' );
- assert.strictEqual( $.isEmpty( {} ), true, 'isEmpty: {}' );
-
- // Documented behavior
- assert.strictEqual( $.isEmpty( { length: 0 } ), true, 'isEmpty: { length: 0 }' );
- } );
-
- QUnit.test( 'Comparison functions', 5, function ( assert ) {
- assert.ok( $.compareArray( [ 0, 'a', [], [ 2, 'b' ] ], [ 0, 'a', [], [ 2, 'b' ] ] ),
- 'compareArray: Two deep arrays that are excactly the same' );
- assert.ok( !$.compareArray( [ 1 ], [ 2 ] ), 'compareArray: Two different arrays (false)' );
-
- assert.ok( $.compareObject( {}, {} ), 'compareObject: Two empty objects' );
- assert.ok( $.compareObject( { foo: 1 }, { foo: 1 } ), 'compareObject: Two the same objects' );
- assert.ok( !$.compareObject( { bar: true }, { baz: false } ),
- 'compareObject: Two different objects (false)' );
- } );
-}( jQuery ) );
diff --git a/www/wiki/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js b/www/wiki/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js
deleted file mode 100644
index 5d0ddebb..00000000
--- a/www/wiki/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js
+++ /dev/null
@@ -1,145 +0,0 @@
-( function ( $ ) {
-
- QUnit.module( 'jquery.placeholder', QUnit.newMwEnvironment() );
-
- QUnit.test( 'caches results of feature tests', 2, function ( assert ) {
- assert.strictEqual( typeof $.fn.placeholder.input, 'boolean', '$.fn.placeholder.input' );
- assert.strictEqual( typeof $.fn.placeholder.textarea, 'boolean', '$.fn.placeholder.textarea' );
- } );
-
- if ( $.fn.placeholder.input && $.fn.placeholder.textarea ) {
- return;
- }
-
- var html = '<form>' +
- '<input id="input-type-search" type="search" placeholder="Search this site...">' +
- '<input id="input-type-text" type="text" placeholder="e.g. John Doe">' +
- '<input id="input-type-email" type="email" placeholder="e.g. address@example.ext">' +
- '<input id="input-type-url" type="url" placeholder="e.g. http://mathiasbynens.be/">' +
- '<input id="input-type-tel" type="tel" placeholder="e.g. +32 472 77 69 88">' +
- '<input id="input-type-password" type="password" placeholder="e.g. hunter2">' +
- '<textarea id="textarea" name="message" placeholder="Your message goes here"></textarea>' +
- '</form>',
- testElement = function ( $el, assert ) {
-
- var el = $el[ 0 ],
- placeholder = el.getAttribute( 'placeholder' );
-
- assert.strictEqual( $el.placeholder(), $el, 'should be chainable' );
-
- assert.strictEqual( el.value, placeholder, 'should set `placeholder` text as `value`' );
- assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' );
- assert.strictEqual( $el.val(), '', 'valHooks works properly' );
- assert.ok( $el.hasClass( 'placeholder' ), 'should have `placeholder` class' );
-
- // test on focus
- $el.focus();
- assert.strictEqual( el.value, '', '`value` should be the empty string on focus' );
- assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' );
- assert.strictEqual( $el.val(), '', 'valHooks works properly' );
- assert.ok( !$el.hasClass( 'placeholder' ), 'should not have `placeholder` class on focus' );
-
- // and unfocus (blur) again
- $el.blur();
-
- assert.strictEqual( el.value, placeholder, 'should set `placeholder` text as `value`' );
- assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' );
- assert.strictEqual( $el.val(), '', 'valHooks works properly' );
- assert.ok( $el.hasClass( 'placeholder' ), 'should have `placeholder` class' );
-
- // change the value
- $el.val( 'lorem ipsum' );
- assert.strictEqual( $el.prop( 'value' ), 'lorem ipsum', '`$el.val(string)` should change the `value` property' );
- assert.strictEqual( el.value, 'lorem ipsum', '`$el.val(string)` should change the `value` attribute' );
- assert.ok( !$el.hasClass( 'placeholder' ), '`$el.val(string)` should remove `placeholder` class' );
-
- // and clear it again
- $el.val( '' );
- assert.strictEqual( $el.prop( 'value' ), '', '`$el.val("")` should change the `value` property' );
- assert.strictEqual( el.value, placeholder, '`$el.val("")` should change the `value` attribute' );
- assert.ok( $el.hasClass( 'placeholder' ), '`$el.val("")` should re-enable `placeholder` class' );
-
- // make sure the placeholder property works as expected.
- assert.strictEqual( $el.prop( 'placeholder' ), placeholder, '$el.prop(`placeholder`) should return the placeholder value' );
- $el.placeholder( 'new placeholder' );
- assert.strictEqual( el.getAttribute( 'placeholder' ), 'new placeholder', '$el.placeholder(<string>) should set the placeholder value' );
- assert.strictEqual( el.value, 'new placeholder', '$el.placeholder(<string>) should update the displayed placeholder value' );
- $el.placeholder( placeholder );
- };
-
- QUnit.test( 'emulates placeholder for <input type=text>', 22, function ( assert ) {
- $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) );
- testElement( $( '#input-type-text' ), assert );
- } );
-
- QUnit.test( 'emulates placeholder for <input type=search>', 22, function ( assert ) {
- $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) );
- testElement( $( '#input-type-search' ), assert );
- } );
-
- QUnit.test( 'emulates placeholder for <input type=email>', 22, function ( assert ) {
- $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) );
- testElement( $( '#input-type-email' ), assert );
- } );
-
- QUnit.test( 'emulates placeholder for <input type=url>', 22, function ( assert ) {
- $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) );
- testElement( $( '#input-type-url' ), assert );
- } );
-
- QUnit.test( 'emulates placeholder for <input type=tel>', 22, function ( assert ) {
- $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) );
- testElement( $( '#input-type-tel' ), assert );
- } );
-
- QUnit.test( 'emulates placeholder for <input type=password>', 13, function ( assert ) {
- $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) );
-
- var selector = '#input-type-password',
- $el = $( selector ),
- el = $el[ 0 ],
- placeholder = el.getAttribute( 'placeholder' );
-
- assert.strictEqual( $el.placeholder(), $el, 'should be chainable' );
-
- // Re-select the element, as it gets replaced by another one in some browsers
- $el = $( selector );
- el = $el[ 0 ];
-
- assert.strictEqual( el.value, placeholder, 'should set `placeholder` text as `value`' );
- assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' );
- assert.strictEqual( $el.val(), '', 'valHooks works properly' );
- assert.ok( $el.hasClass( 'placeholder' ), 'should have `placeholder` class' );
-
- // test on focus
- $el.focus();
-
- // Re-select the element, as it gets replaced by another one in some browsers
- $el = $( selector );
- el = $el[ 0 ];
-
- assert.strictEqual( el.value, '', '`value` should be the empty string on focus' );
- assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' );
- assert.strictEqual( $el.val(), '', 'valHooks works properly' );
- assert.ok( !$el.hasClass( 'placeholder' ), 'should not have `placeholder` class on focus' );
-
- // and unfocus (blur) again
- $el.blur();
-
- // Re-select the element, as it gets replaced by another one in some browsers
- $el = $( selector );
- el = $el[ 0 ];
-
- assert.strictEqual( el.value, placeholder, 'should set `placeholder` text as `value`' );
- assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' );
- assert.strictEqual( $el.val(), '', 'valHooks works properly' );
- assert.ok( $el.hasClass( 'placeholder' ), 'should have `placeholder` class' );
-
- } );
-
- QUnit.test( 'emulates placeholder for <textarea></textarea>', 22, function ( assert ) {
- $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) );
- testElement( $( '#textarea' ), assert );
- } );
-
-}( jQuery ) );
diff --git a/www/wiki/tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js b/www/wiki/tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js
index 01589c33..2865cbba 100644
--- a/www/wiki/tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js
+++ b/www/wiki/tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js
@@ -62,7 +62,7 @@
}
parser = $.tablesorter.getParser( parserId );
- $.each( data, function ( index, testcase ) {
+ data.forEach( function ( testcase ) {
extractedR = parser.is( testcase[ 0 ] );
extractedF = parser.format( testcase[ 0 ] );
diff --git a/www/wiki/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js b/www/wiki/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
index 27d7e8da..23ef26f6 100644
--- a/www/wiki/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
+++ b/www/wiki/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
@@ -130,7 +130,7 @@
[ '$ 1.50' ],
[ '$ 3.00' ],
[ '$3.50' ],
- // Comma's sort after dots
+ // Commas sort after dots
// Not intentional but test to detect changes
[ '€ 2,99' ]
],
@@ -238,7 +238,7 @@
$tbody = $table.find( 'tbody' ),
$tr = $( '<tr>' );
- $.each( header, function ( i, str ) {
+ header.forEach( function ( str ) {
var $th = $( '<th>' );
$th.text( str ).appendTo( $tr );
} );
@@ -247,7 +247,7 @@
for ( i = 0; i < data.length; i++ ) {
$tr = $( '<tr>' );
// eslint-disable-next-line no-loop-func
- $.each( data[ i ], function ( j, str ) {
+ data[ i ].forEach( function ( str ) {
var $td = $( '<td>' );
$td.text( str ).appendTo( $tr );
} );
diff --git a/www/wiki/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js b/www/wiki/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js
index 5b3c2ed0..32cda7eb 100644
--- a/www/wiki/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js
+++ b/www/wiki/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js
@@ -224,7 +224,7 @@
function among( actual, expected, message ) {
if ( Array.isArray( expected ) ) {
- assert.ok( $.inArray( actual, expected ) !== -1, message + ' (got ' + actual + '; expected one of ' + expected.join( ', ' ) + ')' );
+ assert.ok( expected.indexOf( actual ) !== -1, message + ' (got ' + actual + '; expected one of ' + expected.join( ', ' ) + ')' );
} else {
assert.equal( actual, expected, message );
}
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.category.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.category.test.js
index 8ad12900..50fa6d15 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.category.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.category.test.js
@@ -22,4 +22,93 @@
);
} );
} );
+
+ QUnit.test( '.isCategory("")', function ( assert ) {
+ this.server.respondWith( /titles=$/, [
+ 200,
+ { 'Content-Type': 'application/json' },
+ '{"batchcomplete":true}'
+ ] );
+ return new mw.Api().isCategory( '' ).then( function ( response ) {
+ assert.equal( response, false );
+ } );
+ } );
+
+ QUnit.test( '.isCategory("#")', function ( assert ) {
+ this.server.respondWith( /titles=%23$/, [
+ 200,
+ { 'Content-Type': 'application/json' },
+ '{"batchcomplete":true,"query":{"normalized":[{"fromencoded":false,"from":"#","to":""}]}}'
+ ] );
+ return new mw.Api().isCategory( '#' ).then( function ( response ) {
+ assert.equal( response, false );
+ } );
+ } );
+
+ QUnit.test( '.isCategory("mw:")', function ( assert ) {
+ this.server.respondWith( /titles=mw%3A$/, [
+ 200,
+ { 'Content-Type': 'application/json' },
+ '{"batchcomplete":true,"query":{"interwiki":[{"title":"mw:","iw":"mw"}]}}'
+ ] );
+ return new mw.Api().isCategory( 'mw:' ).then( function ( response ) {
+ assert.equal( response, false );
+ } );
+ } );
+
+ QUnit.test( '.isCategory("|")', function ( assert ) {
+ this.server.respondWith( /titles=%1F%7C$/, [
+ 200,
+ { 'Content-Type': 'application/json' },
+ '{"batchcomplete":true,"query":{"pages":[{"title":"|","invalidreason":"The requested page title contains invalid characters: \\"|\\".","invalid":true}]}}'
+ ] );
+ return new mw.Api().isCategory( '|' ).then( function ( response ) {
+ assert.equal( response, false );
+ } );
+ } );
+
+ QUnit.test( '.getCategories("")', function ( assert ) {
+ this.server.respondWith( /titles=$/, [
+ 200,
+ { 'Content-Type': 'application/json' },
+ '{"batchcomplete":true}'
+ ] );
+ return new mw.Api().getCategories( '' ).then( function ( response ) {
+ assert.equal( response, false );
+ } );
+ } );
+
+ QUnit.test( '.getCategories("#")', function ( assert ) {
+ this.server.respondWith( /titles=%23$/, [
+ 200,
+ { 'Content-Type': 'application/json' },
+ '{"batchcomplete":true,"query":{"normalized":[{"fromencoded":false,"from":"#","to":""}]}}'
+ ] );
+ return new mw.Api().getCategories( '#' ).then( function ( response ) {
+ assert.equal( response, false );
+ } );
+ } );
+
+ QUnit.test( '.getCategories("mw:")', function ( assert ) {
+ this.server.respondWith( /titles=mw%3A$/, [
+ 200,
+ { 'Content-Type': 'application/json' },
+ '{"batchcomplete":true,"query":{"interwiki":[{"title":"mw:","iw":"mw"}]}}'
+ ] );
+ return new mw.Api().getCategories( 'mw:' ).then( function ( response ) {
+ assert.equal( response, false );
+ } );
+ } );
+
+ QUnit.test( '.getCategories("|")', function ( assert ) {
+ this.server.respondWith( /titles=%1F%7C$/, [
+ 200,
+ { 'Content-Type': 'application/json' },
+ '{"batchcomplete":true,"query":{"pages":[{"title":"|","invalidreason":"The requested page title contains invalid characters: \\"|\\".","invalid":true}]}}'
+ ] );
+ return new mw.Api().getCategories( '|' ).then( function ( response ) {
+ assert.equal( response, false );
+ } );
+ } );
+
}( mediaWiki ) );
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.edit.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.edit.test.js
index 13d7dcc2..4ce7c5db 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.edit.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.edit.test.js
@@ -47,6 +47,47 @@
} );
} );
+ QUnit.test( 'edit( mw.Title, transform String )', function ( assert ) {
+ this.server.respond( function ( req ) {
+ if ( /query.+titles=Sandbox/.test( req.url ) ) {
+ req.respond( 200, { 'Content-Type': 'application/json' }, JSON.stringify( {
+ curtimestamp: '2016-01-02T12:00:00Z',
+ query: {
+ pages: [ {
+ pageid: 1,
+ ns: 0,
+ title: 'Sandbox',
+ revisions: [ {
+ timestamp: '2016-01-01T12:00:00Z',
+ contentformat: 'text/x-wiki',
+ contentmodel: 'wikitext',
+ content: 'Sand.'
+ } ]
+ } ]
+ }
+ } ) );
+ }
+ if ( /edit.+basetimestamp=2016-01-01.+starttimestamp=2016-01-02.+text=Box%2E/.test( req.requestBody ) ) {
+ req.respond( 200, { 'Content-Type': 'application/json' }, JSON.stringify( {
+ edit: {
+ result: 'Success',
+ oldrevid: 11,
+ newrevid: 13,
+ newtimestamp: '2016-01-03T12:00:00Z'
+ }
+ } ) );
+ }
+ } );
+
+ return new mw.Api()
+ .edit( new mw.Title( 'Sandbox' ), function ( revision ) {
+ return revision.content.replace( 'Sand', 'Box' );
+ } )
+ .then( function ( edit ) {
+ assert.equal( edit.newrevid, 13 );
+ } );
+ } );
+
QUnit.test( 'edit( title, transform Promise )', function ( assert ) {
this.server.respond( function ( req ) {
if ( /query.+titles=Async/.test( req.url ) ) {
@@ -129,6 +170,32 @@
} );
} );
+ QUnit.test( 'edit( invalid-title, transform String )', function ( assert ) {
+ this.server.respond( function ( req ) {
+ if ( /query.+titles=%1F%7C/.test( req.url ) ) {
+ req.respond( 200, { 'Content-Type': 'application/json' }, JSON.stringify( {
+ query: {
+ pages: [ {
+ title: '|',
+ invalidreason: 'The requested page title contains invalid characters: "|".',
+ invalid: true
+ } ]
+ }
+ } ) );
+ }
+ } );
+
+ return new mw.Api()
+ .edit( '|', function ( revision ) {
+ return revision.content.replace( 'Sand', 'Box' );
+ } )
+ .then( function () {
+ return $.Deferred().reject( 'Unexpected success' );
+ }, function ( reason ) {
+ assert.equal( reason, 'invalidtitle' );
+ } );
+ } );
+
QUnit.test( 'create( title, content )', function ( assert ) {
this.server.respond( function ( req ) {
if ( /edit.+text=Sand/.test( req.requestBody ) ) {
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js
index 4ee8038e..997a42c8 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js
@@ -30,7 +30,7 @@
// Requests are POST, match requestBody instead of url
this.server.respond( function ( request ) {
- if ( $.inArray( request.requestBody, [
+ if ( [
// simple
'action=options&format=json&formatversion=2&change=foo%3Dbar&token=%2B%5C',
// two options
@@ -43,7 +43,7 @@
'action=options&format=json&formatversion=2&change=foo&token=%2B%5C',
// reset an option, not bundleable
'action=options&format=json&formatversion=2&optionname=foo%7Cbar%3Dquux&token=%2B%5C'
- ] ) !== -1 ) {
+ ].indexOf( request.requestBody ) !== -1 ) {
assert.ok( true, 'Repond to ' + request.requestBody );
request.respond( 200, { 'Content-Type': 'application/json' },
'{ "options": "success" }' );
@@ -88,7 +88,7 @@
// Requests are POST, match requestBody instead of url
this.server.respond( function ( request ) {
- if ( $.inArray( request.requestBody, [
+ if ( [
// simple
'action=options&format=json&formatversion=2&change=foo%3Dbar&token=%2B%5C',
// two options
@@ -102,7 +102,7 @@
'action=options&format=json&formatversion=2&change=foo&token=%2B%5C',
// reset an option, not bundleable
'action=options&format=json&formatversion=2&optionname=foo%7Cbar%3Dquux&token=%2B%5C'
- ] ) !== -1 ) {
+ ].indexOf( request.requestBody ) !== -1 ) {
assert.ok( true, 'Repond to ' + request.requestBody );
request.respond(
200,
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js
index 2361f700..417ad3d8 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js
@@ -18,7 +18,7 @@
}
function sequenceBodies( status, headers, bodies ) {
- jQuery.each( bodies, function ( i, body ) {
+ bodies.forEach( function ( body, i ) {
bodies[ i ] = [ status, headers, body ];
} );
return sequence( bodies );
@@ -203,7 +203,7 @@
// Don't cache error (T67268)
return api.getToken( 'testerror' )
- .then( null, function ( err ) {
+ .catch( function ( err ) {
assert.equal( err, 'bite-me', 'Expected error' );
return api.getToken( 'testerror' );
@@ -449,7 +449,7 @@
} );
this.api.abort();
assert.ok( this.requests.length === 2, 'Check both requests triggered' );
- $.each( this.requests, function ( i, request ) {
+ this.requests.forEach( function ( request, i ) {
assert.ok( request.abort.calledOnce, 'abort request number ' + i );
} );
} );
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js
index bfaf7f26..788a427e 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js
@@ -19,15 +19,15 @@
api.uploadWithIframe( $( '<input>' )[ 0 ], { filename: 'Testing API upload.jpg' } );
- $iframe = $( 'iframe' );
+ $iframe = $( 'iframe:last-child' );
$form = $( 'form.mw-api-upload-form' );
$input = $form.find( 'input[name=filename]' );
- assert.ok( $form.length > 0 );
- assert.ok( $input.length > 0 );
- assert.ok( $iframe.length > 0 );
- assert.strictEqual( $form.prop( 'target' ), $iframe.prop( 'id' ) );
- assert.strictEqual( $input.val(), 'Testing API upload.jpg' );
+ assert.ok( $form.length > 0, 'form' );
+ assert.ok( $input.length > 0, 'input' );
+ assert.ok( $iframe.length > 0, 'frame' );
+ assert.strictEqual( $form.prop( 'target' ), $iframe.prop( 'id' ), 'form.target and frame.id ' );
+ assert.strictEqual( $input.val(), 'Testing API upload.jpg', 'input value' );
} );
}( mediaWiki, jQuery ) );
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/UriProcessor.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/UriProcessor.test.js
index edaaa39f..872f4ddf 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/UriProcessor.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/UriProcessor.test.js
@@ -6,24 +6,33 @@
title: 'Group 1',
type: 'send_unselected_if_any',
filters: [
- { name: 'filter1', default: true },
- { name: 'filter2' }
+ { name: 'filter1', cssClass: 'filter1class', default: true },
+ { name: 'filter2', cssClass: 'filter2class' }
]
}, {
name: 'group2',
title: 'Group 2',
type: 'send_unselected_if_any',
filters: [
- { name: 'filter3' },
- { name: 'filter4', default: true }
+ { name: 'filter3', cssClass: 'filter3class' },
+ { name: 'filter4', cssClass: 'filter4class', default: true }
]
}, {
name: 'group3',
title: 'Group 3',
type: 'string_options',
filters: [
- { name: 'filter5' },
- { name: 'filter6' }
+ { name: 'filter5', cssClass: 'filter5class' },
+ { name: 'filter6' } // Not supporting highlights
+ ]
+ }, {
+ name: 'group4',
+ title: 'Group 4',
+ type: 'boolean',
+ sticky: true,
+ filters: [
+ { name: 'stickyFilter7', cssClass: 'filter7class' },
+ { name: 'stickyFilter8', cssClass: 'filter8class' }
]
} ],
minimalDefaultParams = {
@@ -49,66 +58,75 @@
);
} );
- QUnit.test( 'updateModelBasedOnQuery & getUriParametersFromModel', function ( assert ) {
+ QUnit.test( 'getUpdatedUri', function ( assert ) {
var uriProcessor,
filtersModel = new mw.rcfilters.dm.FiltersViewModel(),
- baseParams = {
- filter1: '0',
- filter2: '0',
- filter3: '0',
- filter4: '0',
- group3: '',
- highlight: '0',
- invert: '0',
- group1__filter1_color: null,
- group1__filter2_color: null,
- group2__filter3_color: null,
- group2__filter4_color: null,
- group3__filter5_color: null,
- group3__filter6_color: null
+ makeUri = function ( queryParams ) {
+ var uri = new mw.Uri( 'http://server/wiki/Special:RC' );
+ uri.query = queryParams;
+ return uri;
};
filtersModel.initializeFilters( mockFilterStructure );
uriProcessor = new mw.rcfilters.UriProcessor( filtersModel );
- uriProcessor.updateModelBasedOnQuery( {} );
assert.deepEqual(
- uriProcessor.getUriParametersFromModel(),
- $.extend( true, {}, baseParams, minimalDefaultParams ),
- 'Version 1: Empty url query sets model to defaults'
+ ( uriProcessor.getUpdatedUri( makeUri( {} ) ) ).query,
+ { urlversion: '2' },
+ 'Empty model state with empty uri state, assumes the given uri is already normalized, and adds urlversion=2'
);
- uriProcessor.updateModelBasedOnQuery( { urlversion: '2' } );
assert.deepEqual(
- uriProcessor.getUriParametersFromModel(),
- baseParams,
- 'Version 2: Empty url query sets model to all-false'
+ ( uriProcessor.getUpdatedUri( makeUri( { foo: 'bar' } ) ) ).query,
+ { urlversion: '2', foo: 'bar' },
+ 'Empty model state with unrecognized params retains unrecognized params'
);
- uriProcessor.updateModelBasedOnQuery( { filter1: '1', urlversion: '2' } );
+ // Update the model
+ filtersModel.toggleFiltersSelected( {
+ group1__filter1: true, // Param: filter2: '1'
+ group3__filter5: true // Param: group3: 'filter5'
+ } );
+
assert.deepEqual(
- uriProcessor.getUriParametersFromModel(),
- $.extend( true, {}, baseParams, { filter1: '1' } ),
- 'Parameters in Uri query set parameter value in the model'
+ ( uriProcessor.getUpdatedUri( makeUri( {} ) ) ).query,
+ { urlversion: '2', filter2: '1', group3: 'filter5' },
+ 'Model state is reflected in the updated URI'
);
- uriProcessor.updateModelBasedOnQuery( { highlight: '1', group1__filter1_color: 'c1', urlversion: '2' } );
assert.deepEqual(
- uriProcessor.getUriParametersFromModel(),
- $.extend( true, {}, baseParams, {
- highlight: '1',
- group1__filter1_color: 'c1'
- } ),
- 'Highlight parameters in Uri query set highlight state in the model'
+ ( uriProcessor.getUpdatedUri( makeUri( { foo: 'bar' } ) ) ).query,
+ { urlversion: '2', filter2: '1', group3: 'filter5', foo: 'bar' },
+ 'Model state is reflected in the updated URI with existing uri params'
);
+ } );
+
+ QUnit.test( 'updateModelBasedOnQuery', function ( assert ) {
+ var uriProcessor,
+ filtersModel = new mw.rcfilters.dm.FiltersViewModel();
+
+ filtersModel.initializeFilters( mockFilterStructure );
+ uriProcessor = new mw.rcfilters.UriProcessor( filtersModel );
- uriProcessor.updateModelBasedOnQuery( { invert: '1', urlversion: '2' } );
+ uriProcessor.updateModelBasedOnQuery( {} );
assert.deepEqual(
- uriProcessor.getUriParametersFromModel(),
- $.extend( true, {}, baseParams, {
- invert: '1'
- } ),
- 'Invert parameter in Uri query set invert state in the model'
+ filtersModel.getCurrentParameterState(),
+ minimalDefaultParams,
+ 'Version 1: Empty url query sets model to defaults'
+ );
+
+ uriProcessor.updateModelBasedOnQuery( { urlversion: '2' } );
+ assert.deepEqual(
+ filtersModel.getCurrentParameterState(),
+ {},
+ 'Version 2: Empty url query sets model to all-false'
+ );
+
+ uriProcessor.updateModelBasedOnQuery( { filter1: '1', urlversion: '2' } );
+ assert.deepEqual(
+ filtersModel.getCurrentParameterState(),
+ $.extend( true, {}, { filter1: '1' } ),
+ 'Parameters in Uri query set parameter value in the model'
);
} );
@@ -259,4 +277,78 @@
} );
} );
+ QUnit.test( '_normalizeTargetInUri', function ( assert ) {
+ var cases = [
+ {
+ input: 'http://host/wiki/Special:RecentChangesLinked/Moai',
+ output: 'http://host/wiki/Special:RecentChangesLinked?target=Moai',
+ message: 'Target as subpage in path'
+ },
+ {
+ input: 'http://host/wiki/Special:RecentChangesLinked/Château',
+ output: 'http://host/wiki/Special:RecentChangesLinked?target=Château',
+ message: 'Target as subpage in path with special characters'
+ },
+ {
+ input: 'http://host/wiki/Special:RecentChangesLinked/Moai/Sub1',
+ output: 'http://host/wiki/Special:RecentChangesLinked?target=Moai/Sub1',
+ message: 'Target as subpage also has a subpage'
+ },
+ {
+ input: 'http://host/wiki/Special:RecentChangesLinked/Category:Foo',
+ output: 'http://host/wiki/Special:RecentChangesLinked?target=Category:Foo',
+ message: 'Target as subpage in path (with namespace)'
+ },
+ {
+ input: 'http://host/wiki/Special:RecentChangesLinked/Category:Foo/Bar',
+ output: 'http://host/wiki/Special:RecentChangesLinked?target=Category:Foo/Bar',
+ message: 'Target as subpage in path also has a subpage (with namespace)'
+ },
+ {
+ input: 'http://host/w/index.php?title=Special:RecentChangesLinked/Moai',
+ output: 'http://host/w/index.php?title=Special:RecentChangesLinked&target=Moai',
+ message: 'Target as subpage in title param'
+ },
+ {
+ input: 'http://host/w/index.php?title=Special:RecentChangesLinked/Moai/Sub1',
+ output: 'http://host/w/index.php?title=Special:RecentChangesLinked&target=Moai/Sub1',
+ message: 'Target as subpage in title param also has a subpage'
+ },
+ {
+ input: 'http://host/w/index.php?title=Special:RecentChangesLinked/Category:Foo/Bar',
+ output: 'http://host/w/index.php?title=Special:RecentChangesLinked&target=Category:Foo/Bar',
+ message: 'Target as subpage in title param also has a subpage (with namespace)'
+ },
+ {
+ input: 'http://host/wiki/Special:Watchlist',
+ output: 'http://host/wiki/Special:Watchlist',
+ message: 'No target specified'
+ },
+ {
+ normalizeTarget: false,
+ input: 'http://host/wiki/Special:RecentChanges/Foo',
+ output: 'http://host/wiki/Special:RecentChanges/Foo',
+ message: 'Do not normalize if "normalizeTarget" is false.'
+ }
+ ];
+
+ cases.forEach( function ( testCase ) {
+ var uriProcessor = new mw.rcfilters.UriProcessor(
+ null,
+ {
+ normalizeTarget: testCase.normalizeTarget === undefined ?
+ true : testCase.normalizeTarget
+ }
+ );
+
+ assert.equal(
+ uriProcessor._normalizeTargetInUri(
+ new mw.Uri( testCase.input )
+ ).toString(),
+ new mw.Uri( testCase.output ).toString(),
+ testCase.message
+ );
+ } );
+ } );
+
}( mediaWiki, jQuery ) );
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FilterItem.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FilterItem.test.js
index 271648f5..18a2c9ce 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FilterItem.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FilterItem.test.js
@@ -184,4 +184,22 @@
'Events emitted successfully.'
);
} );
+
+ QUnit.test( 'get/set boolean value', function ( assert ) {
+ var group = new mw.rcfilters.dm.FilterGroup( 'group1', { type: 'boolean' } ),
+ item = new mw.rcfilters.dm.FilterItem( 'filter1', group );
+
+ item.setValue( '1' );
+
+ assert.equal( item.getValue(), true, 'Value is coerced to boolean' );
+ } );
+
+ QUnit.test( 'get/set any value', function ( assert ) {
+ var group = new mw.rcfilters.dm.FilterGroup( 'group1', { type: 'any_value' } ),
+ item = new mw.rcfilters.dm.FilterItem( 'filter1', group );
+
+ item.setValue( '1' );
+
+ assert.equal( item.getValue(), '1', 'Value is kept as-is' );
+ } );
}( mediaWiki ) );
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js
index 58e4d29c..2b42b5ab 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js
@@ -7,6 +7,7 @@
{
name: 'filter1', label: 'group1filter1-label', description: 'group1filter1-desc',
default: true,
+ cssClass: 'filter1class',
conflicts: [ { group: 'group2' } ],
subset: [
{
@@ -22,6 +23,7 @@
{
name: 'filter2', label: 'group1filter2-label', description: 'group1filter2-desc',
conflicts: [ { group: 'group2', filter: 'filter6' } ],
+ cssClass: 'filter2class',
subset: [
{
group: 'group1',
@@ -29,6 +31,7 @@
}
]
},
+ // NOTE: This filter has no highlight!
{ name: 'filter3', label: 'group1filter3-label', description: 'group1filter3-desc', default: true }
]
}, {
@@ -37,10 +40,10 @@
fullCoverage: true,
conflicts: [ { group: 'group1', filter: 'filter1' } ],
filters: [
- { name: 'filter4', label: 'group2filter4-label', description: 'group2filter4-desc' },
- { name: 'filter5', label: 'group2filter5-label', description: 'group2filter5-desc', default: true },
+ { name: 'filter4', label: 'group2filter4-label', description: 'group2filter4-desc', cssClass: 'filter4class' },
+ { name: 'filter5', label: 'group2filter5-label', description: 'group2filter5-desc', default: true, cssClass: 'filter5class' },
{
- name: 'filter6', label: 'group2filter6-label', description: 'group2filter6-desc',
+ name: 'filter6', label: 'group2filter6-label', description: 'group2filter6-desc', cssClass: 'filter6class',
conflicts: [ { group: 'group1', filter: 'filter2' } ]
}
]
@@ -50,15 +53,17 @@
separator: ',',
default: 'filter8',
filters: [
- { name: 'filter7', label: 'group3filter7-label', description: 'group3filter7-desc' },
- { name: 'filter8', label: 'group3filter8-label', description: 'group3filter8-desc' },
- { name: 'filter9', label: 'group3filter9-label', description: 'group3filter9-desc' }
+ { name: 'filter7', label: 'group3filter7-label', description: 'group3filter7-desc', cssClass: 'filter7class' },
+ { name: 'filter8', label: 'group3filter8-label', description: 'group3filter8-desc', cssClass: 'filter8class' },
+ { name: 'filter9', label: 'group3filter9-label', description: 'group3filter9-desc', cssClass: 'filter9class' }
]
}, {
name: 'group4',
type: 'single_option',
+ hidden: true,
default: 'option2',
filters: [
+ // NOTE: The entire group has no highlight supported
{ name: 'option1', label: 'group4option1-label', description: 'group4option1-desc' },
{ name: 'option2', label: 'group4option2-label', description: 'group4option2-desc' },
{ name: 'option3', label: 'group4option3-label', description: 'group4option3-desc' }
@@ -67,30 +72,46 @@
name: 'group5',
type: 'single_option',
filters: [
- { name: 'option1', label: 'group5option1-label', description: 'group5option1-desc' },
- { name: 'option2', label: 'group5option2-label', description: 'group5option2-desc' },
- { name: 'option3', label: 'group5option3-label', description: 'group5option3-desc' }
+ { name: 'option1', label: 'group5option1-label', description: 'group5option1-desc', cssClass: 'group5opt1class' },
+ { name: 'option2', label: 'group5option2-label', description: 'group5option2-desc', cssClass: 'group5opt2class' },
+ { name: 'option3', label: 'group5option3-label', description: 'group5option3-desc', cssClass: 'group5opt3class' }
]
}, {
name: 'group6',
type: 'boolean',
- isSticky: true,
+ sticky: true,
filters: [
- { name: 'group6option1', label: 'group6option1-label', description: 'group6option1-desc' },
- { name: 'group6option2', label: 'group6option2-label', description: 'group6option2-desc', default: true },
- { name: 'group6option3', label: 'group6option3-label', description: 'group6option3-desc', default: true }
+ { name: 'group6option1', label: 'group6option1-label', description: 'group6option1-desc', cssClass: 'group6opt1class' },
+ { name: 'group6option2', label: 'group6option2-label', description: 'group6option2-desc', default: true, cssClass: 'group6opt2class' },
+ { name: 'group6option3', label: 'group6option3-label', description: 'group6option3-desc', default: true, cssClass: 'group6opt3class' }
]
}, {
name: 'group7',
type: 'single_option',
- isSticky: true,
+ sticky: true,
default: 'group7option2',
filters: [
- { name: 'group7option1', label: 'group7option1-label', description: 'group7option1-desc' },
- { name: 'group7option2', label: 'group7option2-label', description: 'group7option2-desc' },
- { name: 'group7option3', label: 'group7option3-label', description: 'group7option3-desc' }
+ { name: 'group7option1', label: 'group7option1-label', description: 'group7option1-desc', cssClass: 'group7opt1class' },
+ { name: 'group7option2', label: 'group7option2-label', description: 'group7option2-desc', cssClass: 'group7opt2class' },
+ { name: 'group7option3', label: 'group7option3-label', description: 'group7option3-desc', cssClass: 'group7opt3class' }
]
} ],
+ shortFilterDefinition = [ {
+ name: 'group1',
+ type: 'send_unselected_if_any',
+ filters: [ { name: 'filter1' }, { name: 'filter2' } ]
+ }, {
+ name: 'group2',
+ type: 'boolean',
+ hidden: true,
+ filters: [ { name: 'filter3' }, { name: 'filter4' } ]
+ }, {
+ name: 'group3',
+ type: 'string_options',
+ sticky: true,
+ default: 'filter6',
+ filters: [ { name: 'filter5' }, { name: 'filter6' }, { name: 'filter7' } ]
+ } ],
viewsDefinition = {
namespaces: {
label: 'Namespaces',
@@ -101,10 +122,10 @@
type: 'string_options',
separator: ';',
filters: [
- { name: 0, label: 'Main' },
- { name: 1, label: 'Talk' },
- { name: 2, label: 'User' },
- { name: 3, label: 'User talk' }
+ { name: 0, label: 'Main', cssClass: 'namespace-0' },
+ { name: 1, label: 'Talk', cssClass: 'namespace-1' },
+ { name: 2, label: 'User', cssClass: 'namespace-2' },
+ { name: 3, label: 'User talk', cssClass: 'namespace-3' }
]
} ]
}
@@ -119,10 +140,6 @@
group3: 'filter8',
group4: 'option2',
group5: 'option1',
- group6option1: '0',
- group6option2: '1',
- group6option3: '1',
- group7: 'group7option2',
namespace: ''
},
baseParamRepresentation = {
@@ -141,6 +158,48 @@
group7: 'group7option2',
namespace: ''
},
+ emptyParamRepresentation = {
+ filter1: '0',
+ filter2: '0',
+ filter3: '0',
+ filter4: '0',
+ filter5: '0',
+ filter6: '0',
+ group3: '',
+ group4: '',
+ group5: '',
+ group6option1: '0',
+ group6option2: '0',
+ group6option3: '0',
+ group7: '',
+ namespace: '',
+ // Null highlights
+ group1__filter1_color: null,
+ group1__filter2_color: null,
+ // group1__filter3_color: null, // Highlight isn't supported
+ group2__filter4_color: null,
+ group2__filter5_color: null,
+ group2__filter6_color: null,
+ group3__filter7_color: null,
+ group3__filter8_color: null,
+ group3__filter9_color: null,
+ // group4__option1_color: null, // Highlight isn't supported
+ // group4__option2_color: null, // Highlight isn't supported
+ // group4__option3_color: null, // Highlight isn't supported
+ group5__option1_color: null,
+ group5__option2_color: null,
+ group5__option3_color: null,
+ group6__group6option1_color: null,
+ group6__group6option2_color: null,
+ group6__group6option3_color: null,
+ group7__group7option1_color: null,
+ group7__group7option2_color: null,
+ group7__group7option3_color: null,
+ namespace__0_color: null,
+ namespace__1_color: null,
+ namespace__2_color: null,
+ namespace__3_color: null
+ },
baseFilterRepresentation = {
group1__filter1: false,
group1__filter2: false,
@@ -213,9 +272,6 @@
'group2filter5-desc': 'Description of Filter 5 in Group 2',
'group2filter6-label': 'xGroup 2: Filter 6',
'group2filter6-desc': 'Description of Filter 6 in Group 2'
- },
- config: {
- wgStructuredChangeFiltersEnableExperimentalViews: true
}
} ) );
@@ -263,22 +319,165 @@
assert.deepEqual(
model.getDefaultParams(),
defaultParameters,
- 'Default parameters are stored properly per filter and group'
+ 'Default parameters are stored properly per filter and group (sticky groups are ignored)'
+ );
+ } );
+
+ QUnit.test( 'Parameter minimal state', function ( assert ) {
+ var model = new mw.rcfilters.dm.FiltersViewModel(),
+ cases = [
+ {
+ input: {},
+ result: {},
+ msg: 'Empty parameter representation produces an empty result'
+ },
+ {
+ input: {
+ filter1: '1',
+ filter2: '0',
+ filter3: '0',
+ group3: '',
+ group4: 'option2'
+ },
+ result: {
+ filter1: '1',
+ group4: 'option2'
+ },
+ msg: 'Mixed input results in only non-falsey values as result'
+ },
+ {
+ input: {
+ filter1: '0',
+ filter2: '0',
+ filter3: '0',
+ group3: '',
+ group4: '',
+ group1__filter1_color: null
+ },
+ result: {},
+ msg: 'An all-falsey input results in an empty result.'
+ },
+ {
+ input: {
+ filter1: '0',
+ filter2: '0',
+ filter3: '0',
+ group3: '',
+ group4: '',
+ group1__filter1_color: 'c1'
+ },
+ result: {
+ group1__filter1_color: 'c1'
+ },
+ msg: 'An all-falsey input with highlight params result in only the highlight param.'
+ },
+ {
+ input: {
+ group1__filter1_color: 'c1',
+ group1__filter3_color: 'c3' // Not supporting highlights
+ },
+ result: {
+ group1__filter1_color: 'c1'
+ },
+ msg: 'Unsupported highlights are removed.'
+ }
+ ];
+
+ model.initializeFilters( filterDefinition, viewsDefinition );
+
+ cases.forEach( function ( test ) {
+ assert.deepEqual(
+ model.getMinimizedParamRepresentation( test.input ),
+ test.result,
+ test.msg
+ );
+ } );
+ } );
+
+ QUnit.test( 'Parameter states', function ( assert ) {
+ // Some groups / params have their defaults immediately applied
+ // to their state. These include single_option which can never
+ // be empty, etc. These are these states:
+ var parametersWithoutExcluded,
+ appliedDefaultParameters = {
+ group4: 'option2',
+ group5: 'option1',
+ // Sticky, their defaults apply immediately
+ group6option2: '1',
+ group6option3: '1',
+ group7: 'group7option2'
+ },
+ model = new mw.rcfilters.dm.FiltersViewModel();
+
+ model.initializeFilters( filterDefinition, viewsDefinition );
+ assert.deepEqual(
+ model.getEmptyParameterState(),
+ emptyParamRepresentation,
+ 'Producing an empty parameter state'
);
- // Change sticky filter
model.toggleFiltersSelected( {
- group7__group7option1: true
+ group1__filter1: true,
+ group3__filter7: true
} );
- // Make sure defaults have changed
assert.deepEqual(
- model.getDefaultParams(),
- $.extend( true, {}, defaultParameters, {
- group7: 'group7option1'
+ model.getCurrentParameterState(),
+ // appliedDefaultParams applies the default value to parameters
+ // who must have an initial value to begin with, so we have to
+ // take it into account in the current state
+ $.extend( true, {}, appliedDefaultParameters, {
+ filter2: '1',
+ filter3: '1',
+ group3: 'filter7'
} ),
- 'Default parameters are stored properly per filter and group'
+ 'Producing a current parameter state'
);
+
+ // Reset
+ model = new mw.rcfilters.dm.FiltersViewModel();
+ model.initializeFilters( filterDefinition, viewsDefinition );
+
+ parametersWithoutExcluded = $.extend( true, {}, appliedDefaultParameters );
+ delete parametersWithoutExcluded.group7;
+ delete parametersWithoutExcluded.group6option2;
+ delete parametersWithoutExcluded.group6option3;
+
+ assert.deepEqual(
+ model.getCurrentParameterState( true ),
+ parametersWithoutExcluded,
+ 'Producing a current clean parameter state without excluded filters'
+ );
+ } );
+
+ QUnit.test( 'Cleaning up parameter states', function ( assert ) {
+ var model = new mw.rcfilters.dm.FiltersViewModel(),
+ cases = [
+ {
+ input: {},
+ result: {},
+ msg: 'Empty parameter representation produces an empty result'
+ },
+ {
+ input: {
+ filter1: '1', // Regular (do not strip)
+ group6option1: '1' // Sticky
+ },
+ result: { filter1: '1' },
+ msg: 'Valid input strips all sticky params regardless of value'
+ }
+ ];
+
+ model.initializeFilters( filterDefinition, viewsDefinition );
+
+ cases.forEach( function ( test ) {
+ assert.deepEqual(
+ model.removeStickyParams( test.input ),
+ test.result,
+ test.msg
+ );
+ } );
+
} );
QUnit.test( 'Finding matching filters', function ( assert ) {
@@ -1319,4 +1518,45 @@
'Items without a specified class identifier are not highlighted.'
);
} );
+
+ QUnit.test( 'emptyAllFilters', function ( assert ) {
+ var model = new mw.rcfilters.dm.FiltersViewModel();
+
+ model.initializeFilters( shortFilterDefinition, null );
+
+ model.toggleFiltersSelected( {
+ group1__filter1: true,
+ group2__filter4: true, // hidden
+ group3__filter5: true // sticky
+ } );
+
+ model.emptyAllFilters();
+
+ assert.deepEqual(
+ model.getSelectedState( true ),
+ {
+ group3__filter5: true,
+ group3__filter6: true
+ },
+ 'Emptying filters does not affect sticky filters'
+ );
+ } );
+
+ QUnit.test( 'areVisibleFiltersEmpty', function ( assert ) {
+ var model = new mw.rcfilters.dm.FiltersViewModel();
+ model.initializeFilters( shortFilterDefinition, null );
+
+ model.emptyAllFilters();
+ assert.ok( model.areVisibleFiltersEmpty() );
+
+ model.toggleFiltersSelected( {
+ group3__filter5: true // sticky
+ } );
+ assert.ok( model.areVisibleFiltersEmpty() );
+
+ model.toggleFiltersSelected( {
+ group1__filter1: true
+ } );
+ assert.notOk( model.areVisibleFiltersEmpty() );
+ } );
}( mediaWiki, jQuery ) );
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.SavedQueriesModel.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.SavedQueriesModel.test.js
new file mode 100644
index 00000000..ed054bd7
--- /dev/null
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.SavedQueriesModel.test.js
@@ -0,0 +1,520 @@
+/* eslint-disable camelcase */
+( function ( mw ) {
+ var filterDefinition = [ {
+ name: 'group1',
+ type: 'send_unselected_if_any',
+ filters: [
+ // Note: The fact filter2 is default means that in the
+ // filter representation, filter1 and filter3 are 'true'
+ { name: 'filter1', cssClass: 'filter1class' },
+ { name: 'filter2', cssClass: 'filter2class', default: true },
+ { name: 'filter3', cssClass: 'filter3class' }
+ ]
+ }, {
+ name: 'group2',
+ type: 'string_options',
+ separator: ',',
+ filters: [
+ { name: 'filter4', cssClass: 'filter4class' },
+ { name: 'filter5' }, // NOTE: Not supporting highlights!
+ { name: 'filter6', cssClass: 'filter6class' }
+ ]
+ }, {
+ name: 'group3',
+ type: 'boolean',
+ sticky: true,
+ filters: [
+ { name: 'group3option1', cssClass: 'filter1class' },
+ { name: 'group3option2', cssClass: 'filter1class' },
+ { name: 'group3option3', cssClass: 'filter1class' }
+ ]
+ }, {
+ // Copy of the way the controller defines invert
+ // to check whether the conversion works
+ name: 'invertGroup',
+ type: 'boolean',
+ hidden: true,
+ filters: [ {
+ name: 'invert',
+ default: '0'
+ } ]
+ } ],
+ queriesFilterRepresentation = {
+ queries: {
+ 1234: {
+ label: 'Item converted',
+ data: {
+ filters: {
+ // - This value is true, but the original filter-representation
+ // of the saved queries ran against defaults. Since filter1 was
+ // set as default in the definition, the value would actually
+ // not appear in the representation itself.
+ // It is considered 'true', though, and should appear in the
+ // converted result in its parameter representation.
+ // >> group1__filter1: true,
+ // - The reverse is true for filter3. Filter3 is set as default
+ // but we don't want it in this representation of the saved query.
+ // Since the filter representation ran against default values,
+ // it will appear as 'false' value in this representation explicitly
+ // and the resulting parameter representation should have that
+ // as the result as well
+ group1__filter3: false,
+ group2__filter4: true,
+ group3__group3option1: true
+ },
+ highlights: {
+ highlight: true,
+ group1__filter1: 'c5',
+ group3__group3option1: 'c1'
+ },
+ invert: true
+ }
+ }
+ }
+ },
+ queriesParamRepresentation = {
+ version: '2',
+ queries: {
+ 1234: {
+ label: 'Item converted',
+ data: {
+ params: {
+ // filter1 is 'true' so filter2 and filter3 are both '1'
+ // in param representation
+ filter2: '1', filter3: '1',
+ // Group type string_options
+ group2: 'filter4'
+ // Note - Group3 is sticky, so it won't show in output
+ },
+ highlights: {
+ group1__filter1_color: 'c5',
+ group3__group3option1_color: 'c1'
+ }
+ }
+ }
+ }
+ },
+ removeHighlights = function ( data ) {
+ var copy = $.extend( true, {}, data );
+ copy.queries[ 1234 ].data.highlights = {};
+ return copy;
+ };
+
+ QUnit.module( 'mediawiki.rcfilters - SavedQueriesModel' );
+
+ QUnit.test( 'Initializing queries', function ( assert ) {
+ var filtersModel = new mw.rcfilters.dm.FiltersViewModel(),
+ queriesModel = new mw.rcfilters.dm.SavedQueriesModel( filtersModel ),
+ exampleQueryStructure = {
+ version: '2',
+ default: '1234',
+ queries: {
+ 1234: {
+ label: 'Query 1234',
+ data: {
+ params: {
+ filter2: '1'
+ },
+ highlights: {
+ group1__filter3_color: 'c2'
+ }
+ }
+ }
+ }
+ },
+ cases = [
+ {
+ input: {},
+ finalState: { version: '2', queries: {} },
+ msg: 'Empty initial query structure results in base saved queries structure.'
+ },
+ {
+ input: $.extend( true, {}, exampleQueryStructure ),
+ finalState: $.extend( true, {}, exampleQueryStructure ),
+ msg: 'Initialization of given query structure does not corrupt the structure.'
+ },
+ {
+ // Converting from old structure
+ input: $.extend( true, {}, queriesFilterRepresentation ),
+ finalState: $.extend( true, {}, queriesParamRepresentation ),
+ msg: 'Conversion from filter representation to parameters retains data.'
+ },
+ {
+ // Converting from old structure
+ input: $.extend( true, {}, queriesFilterRepresentation, { queries: { 1234: { data: {
+ filters: {
+ // Entire group true: normalize params
+ filter1: true,
+ filter2: true,
+ filter3: true
+ },
+ highlights: {
+ filter3: null // Get rid of empty highlight
+ }
+ } } } } ),
+ finalState: $.extend( true, {}, queriesParamRepresentation ),
+ msg: 'Conversion from filter representation to parameters normalizes params and highlights.'
+ },
+ {
+ // Converting from old structure with default
+ input: $.extend( true, { default: '1234' }, queriesFilterRepresentation ),
+ finalState: $.extend( true, { default: '1234' }, queriesParamRepresentation ),
+ msg: 'Conversion from filter representation to parameters, with default set up, retains data.'
+ },
+ {
+ // Converting from old structure and cleaning up highlights
+ input: $.extend( true, queriesFilterRepresentation, { queries: { 1234: { data: { highlights: { highlight: false } } } } } ),
+ finalState: removeHighlights( queriesParamRepresentation ),
+ msg: 'Conversion from filter representation to parameters and highlight cleanup'
+ },
+ {
+ // New structure
+ input: $.extend( true, {}, queriesParamRepresentation ),
+ finalState: $.extend( true, {}, queriesParamRepresentation ),
+ msg: 'Parameter representation retains its queries structure'
+ },
+ {
+ // Do not touch invalid color parameters from the initialization routine
+ // (Normalization, or "fixing" the query should only happen when we add new query or actively convert queries)
+ input: $.extend( true, { queries: { 1234: { data: { highlights: { group2__filter5_color: 'c2' } } } } }, exampleQueryStructure ),
+ finalState: $.extend( true, { queries: { 1234: { data: { highlights: { group2__filter5_color: 'c2' } } } } }, exampleQueryStructure ),
+ msg: 'Structure that contains invalid highlights remains the same in initialization'
+ },
+ {
+ // Trim colors when highlight=false is stored
+ input: $.extend( true, { queries: { 1234: { data: { params: { highlight: '0' } } } } }, queriesParamRepresentation ),
+ finalState: removeHighlights( queriesParamRepresentation ),
+ msg: 'Colors are removed when highlight=false'
+ },
+ {
+ // Remove highlight when it is true but no colors are specified
+ input: $.extend( true, { queries: { 1234: { data: { params: { highlight: '1' } } } } }, removeHighlights( queriesParamRepresentation ) ),
+ finalState: removeHighlights( queriesParamRepresentation ),
+ msg: 'remove highlight when it is true but there is no colors'
+ }
+ ];
+
+ filtersModel.initializeFilters( filterDefinition );
+
+ cases.forEach( function ( testCase ) {
+ queriesModel.initialize( testCase.input );
+ assert.deepEqual(
+ queriesModel.getState(),
+ testCase.finalState,
+ testCase.msg
+ );
+ } );
+ } );
+
+ QUnit.test( 'Adding new queries', function ( assert ) {
+ var filtersModel = new mw.rcfilters.dm.FiltersViewModel(),
+ queriesModel = new mw.rcfilters.dm.SavedQueriesModel( filtersModel ),
+ cases = [
+ {
+ methodParams: [
+ 'label1', // Label
+ { // Data
+ filter1: '1',
+ filter2: '2',
+ group1__filter1_color: 'c2',
+ group1__filter3_color: 'c5'
+ },
+ true, // isDefault
+ '1234' // ID
+ ],
+ result: {
+ itemState: {
+ label: 'label1',
+ data: {
+ params: {
+ filter1: '1',
+ filter2: '2'
+ },
+ highlights: {
+ group1__filter1_color: 'c2',
+ group1__filter3_color: 'c5'
+ }
+ }
+ },
+ isDefault: true,
+ id: '1234'
+ },
+ msg: 'Given valid data is preserved.'
+ },
+ {
+ methodParams: [
+ 'label2',
+ {
+ filter1: '1',
+ invert: '1',
+ filter15: '1', // Invalid filter - removed
+ filter2: '0', // Falsey value - removed
+ group1__filter1_color: 'c3',
+ foobar: 'w00t' // Unrecognized parameter - removed
+ }
+ ],
+ result: {
+ itemState: {
+ label: 'label2',
+ data: {
+ params: {
+ filter1: '1' // Invert will be dropped because there are no namespaces
+ },
+ highlights: {
+ group1__filter1_color: 'c3'
+ }
+ }
+ },
+ isDefault: false
+ },
+ msg: 'Given data with invalid filters and highlights is normalized'
+ }
+ ];
+
+ filtersModel.initializeFilters( filterDefinition );
+
+ // Start with an empty saved queries model
+ queriesModel.initialize( {} );
+
+ cases.forEach( function ( testCase ) {
+ var itemID = queriesModel.addNewQuery.apply( queriesModel, testCase.methodParams ),
+ item = queriesModel.getItemByID( itemID );
+
+ assert.deepEqual(
+ item.getState(),
+ testCase.result.itemState,
+ testCase.msg + ' (itemState)'
+ );
+
+ assert.equal(
+ item.isDefault(),
+ testCase.result.isDefault,
+ testCase.msg + ' (isDefault)'
+ );
+
+ if ( testCase.result.id !== undefined ) {
+ assert.equal(
+ item.getID(),
+ testCase.result.id,
+ testCase.msg + ' (item ID)'
+ );
+ }
+ } );
+ } );
+
+ QUnit.test( 'Manipulating queries', function ( assert ) {
+ var id1, id2, item1, matchingItem,
+ queriesStructure = {},
+ filtersModel = new mw.rcfilters.dm.FiltersViewModel(),
+ queriesModel = new mw.rcfilters.dm.SavedQueriesModel( filtersModel );
+
+ filtersModel.initializeFilters( filterDefinition );
+
+ // Start with an empty saved queries model
+ queriesModel.initialize( {} );
+
+ // Add items
+ id1 = queriesModel.addNewQuery(
+ 'New query 1',
+ {
+ group2: 'filter5',
+ group1__filter1_color: 'c5',
+ group3__group3option1_color: 'c1'
+ }
+ );
+ id2 = queriesModel.addNewQuery(
+ 'New query 2',
+ {
+ filter1: '1',
+ filter2: '1',
+ invert: '1'
+ }
+ );
+ item1 = queriesModel.getItemByID( id1 );
+
+ assert.equal(
+ item1.getID(),
+ id1,
+ 'Item created and its data retained successfully'
+ );
+
+ // NOTE: All other methods that the item itself returns are
+ // tested in the dm.SavedQueryItemModel.test.js file
+
+ // Build the query structure we expect per item
+ queriesStructure[ id1 ] = {
+ label: 'New query 1',
+ data: {
+ params: {
+ group2: 'filter5'
+ },
+ highlights: {
+ group1__filter1_color: 'c5',
+ group3__group3option1_color: 'c1'
+ }
+ }
+ };
+ queriesStructure[ id2 ] = {
+ label: 'New query 2',
+ data: {
+ params: {
+ filter1: '1',
+ filter2: '1'
+ },
+ highlights: {}
+ }
+ };
+
+ assert.deepEqual(
+ queriesModel.getState(),
+ {
+ version: '2',
+ queries: queriesStructure
+ },
+ 'Full query represents current state of items'
+ );
+
+ // Add default
+ queriesModel.setDefault( id2 );
+
+ assert.deepEqual(
+ queriesModel.getState(),
+ {
+ version: '2',
+ default: id2,
+ queries: queriesStructure
+ },
+ 'Setting default is reflected in queries state'
+ );
+
+ // Remove default
+ queriesModel.setDefault( null );
+
+ assert.deepEqual(
+ queriesModel.getState(),
+ {
+ version: '2',
+ queries: queriesStructure
+ },
+ 'Removing default is reflected in queries state'
+ );
+
+ // Find matching query
+ matchingItem = queriesModel.findMatchingQuery(
+ {
+ group2: 'filter5',
+ group1__filter1_color: 'c5',
+ group3__group3option1_color: 'c1'
+ }
+ );
+ assert.deepEqual(
+ matchingItem.getID(),
+ id1,
+ 'Finding matching item by identical state'
+ );
+
+ // Find matching query with 0-values (base state)
+ matchingItem = queriesModel.findMatchingQuery(
+ {
+ group2: 'filter5',
+ filter1: '0',
+ filter2: '0',
+ group1__filter1_color: 'c5',
+ group3__group3option1_color: 'c1'
+ }
+ );
+ assert.deepEqual(
+ matchingItem.getID(),
+ id1,
+ 'Finding matching item by "dirty" state with 0-base values'
+ );
+ } );
+
+ QUnit.test( 'Testing invert property', function ( assert ) {
+ var itemID, item,
+ filtersModel = new mw.rcfilters.dm.FiltersViewModel(),
+ queriesModel = new mw.rcfilters.dm.SavedQueriesModel( filtersModel ),
+ viewsDefinition = {
+ namespace: {
+ label: 'Namespaces',
+ trigger: ':',
+ groups: [ {
+ name: 'namespace',
+ label: 'Namespaces',
+ type: 'string_options',
+ separator: ';',
+ filters: [
+ { name: 0, label: 'Main', cssClass: 'namespace-0' },
+ { name: 1, label: 'Talk', cssClass: 'namespace-1' },
+ { name: 2, label: 'User', cssClass: 'namespace-2' },
+ { name: 3, label: 'User talk', cssClass: 'namespace-3' }
+ ]
+ } ]
+ }
+ };
+
+ filtersModel.initializeFilters( filterDefinition, viewsDefinition );
+
+ // Start with an empty saved queries model
+ queriesModel.initialize( {} );
+
+ filtersModel.toggleFiltersSelected( {
+ group1__filter3: true,
+ invertGroup__invert: true
+ } );
+ itemID = queriesModel.addNewQuery(
+ 'label1', // Label
+ filtersModel.getMinimizedParamRepresentation(),
+ true, // isDefault
+ '2345' // ID
+ );
+ item = queriesModel.getItemByID( itemID );
+
+ assert.deepEqual(
+ item.getState(),
+ {
+ label: 'label1',
+ data: {
+ params: {
+ filter1: '1',
+ filter2: '1'
+ },
+ highlights: {}
+ }
+ },
+ 'Invert parameter is not saved if there are no namespaces.'
+ );
+
+ // Reset
+ filtersModel.initializeFilters( filterDefinition, viewsDefinition );
+ filtersModel.toggleFiltersSelected( {
+ group1__filter3: true,
+ invertGroup__invert: true,
+ namespace__1: true
+ } );
+ itemID = queriesModel.addNewQuery(
+ 'label1', // Label
+ filtersModel.getMinimizedParamRepresentation(),
+ true, // isDefault
+ '1234' // ID
+ );
+ item = queriesModel.getItemByID( itemID );
+
+ assert.deepEqual(
+ item.getState(),
+ {
+ label: 'label1',
+ data: {
+ params: {
+ filter1: '1',
+ filter2: '1',
+ invert: '1',
+ namespace: '1'
+ },
+ highlights: {}
+ }
+ },
+ 'Invert parameter saved if there are namespaces.'
+ );
+ } );
+}( mediaWiki ) );
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.SavedQueryItemModel.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.SavedQueryItemModel.test.js
new file mode 100644
index 00000000..181e9925
--- /dev/null
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.SavedQueryItemModel.test.js
@@ -0,0 +1,89 @@
+/* eslint-disable camelcase */
+( function ( mw ) {
+ var itemData = {
+ params: {
+ param1: '1',
+ param2: 'foo|bar',
+ invert: '0'
+ },
+ highlights: {
+ param1_color: 'c1',
+ param2_color: 'c2'
+ }
+ };
+
+ QUnit.module( 'mediawiki.rcfilters - SavedQueryItemModel' );
+
+ QUnit.test( 'Initializing and getters', function ( assert ) {
+ var model;
+
+ model = new mw.rcfilters.dm.SavedQueryItemModel(
+ 'randomID',
+ 'Some label',
+ $.extend( true, {}, itemData )
+ );
+
+ assert.equal(
+ model.getID(),
+ 'randomID',
+ 'Item ID is retained'
+ );
+
+ assert.equal(
+ model.getLabel(),
+ 'Some label',
+ 'Item label is retained'
+ );
+
+ assert.deepEqual(
+ model.getData(),
+ itemData,
+ 'Item data is retained'
+ );
+
+ assert.ok(
+ !model.isDefault(),
+ 'Item default state is retained.'
+ );
+ } );
+
+ QUnit.test( 'Default', function ( assert ) {
+ var model;
+
+ model = new mw.rcfilters.dm.SavedQueryItemModel(
+ 'randomID',
+ 'Some label',
+ $.extend( true, {}, itemData )
+ );
+
+ assert.ok(
+ !model.isDefault(),
+ 'Default state represented when item initialized with default:false.'
+ );
+
+ model.toggleDefault( true );
+ assert.ok(
+ model.isDefault(),
+ 'Default state toggles to true successfully'
+ );
+
+ model.toggleDefault( false );
+ assert.ok(
+ !model.isDefault(),
+ 'Default state toggles to false successfully'
+ );
+
+ // Reset
+ model = new mw.rcfilters.dm.SavedQueryItemModel(
+ 'randomID',
+ 'Some label',
+ $.extend( true, {}, itemData ),
+ { default: true }
+ );
+
+ assert.ok(
+ model.isDefault(),
+ 'Default state represented when item initialized with default:true.'
+ );
+ } );
+}( mediaWiki ) );
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js
index 35b6b718..14c2bb4c 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function ( $ ) {
QUnit.module( 'mediawiki.special.recentchanges', QUnit.newMwEnvironment() );
// TODO: verify checkboxes == [ 'nsassociated', 'nsinvert' ]
@@ -61,4 +61,4 @@
// DOM cleanup
$env.remove();
} );
-}( mediaWiki, jQuery ) );
+}( jQuery ) );
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js
index 97c82fb3..4e15cf01 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function ( mw ) {
QUnit.module( 'mediawiki.RegExp' );
QUnit.test( 'escape', function ( assert ) {
@@ -28,11 +28,11 @@
'0123456789'
].join( '' );
- $.each( specials, function ( i, str ) {
+ specials.forEach( function ( str ) {
assert.propEqual( str.match( new RegExp( mw.RegExp.escape( str ) ) ), [ str ], 'Match ' + str );
} );
assert.equal( mw.RegExp.escape( normal ), normal, 'Alphanumerals are left alone' );
} );
-}( mediaWiki, jQuery ) );
+}( mediaWiki ) );
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.String.byteLength.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.String.byteLength.test.js
new file mode 100644
index 00000000..ae3ebbf7
--- /dev/null
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.String.byteLength.test.js
@@ -0,0 +1,39 @@
+( function () {
+ var byteLength = require( 'mediawiki.String' ).byteLength;
+
+ QUnit.module( 'mediawiki.String.byteLength', QUnit.newMwEnvironment() );
+
+ QUnit.test( 'Simple text', function ( assert ) {
+ var azLc = 'abcdefghijklmnopqrstuvwxyz',
+ azUc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+ num = '0123456789',
+ x = '*',
+ space = ' ';
+
+ assert.equal( byteLength( azLc ), 26, 'Lowercase a-z' );
+ assert.equal( byteLength( azUc ), 26, 'Uppercase A-Z' );
+ assert.equal( byteLength( num ), 10, 'Numbers 0-9' );
+ assert.equal( byteLength( x ), 1, 'An asterisk' );
+ assert.equal( byteLength( space ), 3, '3 spaces' );
+
+ } );
+
+ QUnit.test( 'Special text', function ( assert ) {
+ // https://en.wikipedia.org/wiki/UTF-8
+ var u0024 = '$',
+ // Cent symbol
+ u00A2 = '\u00A2',
+ // Euro symbol
+ u20AC = '\u20AC',
+ // Character \U00024B62 (Han script) can't be represented in javascript as a single
+ // code point, instead it is composed as a surrogate pair of two separate code units.
+ // http://codepoints.net/U+24B62
+ // http://www.fileformat.info/info/unicode/char/24B62/index.htm
+ u024B62 = '\uD852\uDF62';
+
+ assert.strictEqual( byteLength( u0024 ), 1, 'U+0024' );
+ assert.strictEqual( byteLength( u00A2 ), 2, 'U+00A2' );
+ assert.strictEqual( byteLength( u20AC ), 3, 'U+20AC' );
+ assert.strictEqual( byteLength( u024B62 ), 4, 'U+024B62 (surrogate pair: \\uD852\\uDF62)' );
+ } );
+}() );
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.String.trimByteLength.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.String.trimByteLength.test.js
new file mode 100644
index 00000000..e2eea94e
--- /dev/null
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.String.trimByteLength.test.js
@@ -0,0 +1,150 @@
+( function ( $, mw ) {
+ var simpleSample, U_20AC, poop, mbSample,
+ trimByteLength = require( 'mediawiki.String' ).trimByteLength;
+
+ QUnit.module( 'mediawiki.String.trimByteLength', QUnit.newMwEnvironment() );
+
+ // Simple sample (20 chars, 20 bytes)
+ simpleSample = '12345678901234567890';
+
+ // 3 bytes (euro-symbol)
+ U_20AC = '\u20AC';
+
+ // Outside of the BMP (pile of poo emoji)
+ poop = '\uD83D\uDCA9'; // "💩"
+
+ // Multi-byte sample (22 chars, 26 bytes)
+ mbSample = '1234567890' + U_20AC + '1234567890' + U_20AC;
+
+ /**
+ * Test factory for mw.String#trimByteLength
+ *
+ * @param {Object} options
+ * @param {string} options.description Test name
+ * @param {string} options.sample Sequence of characters to trim
+ * @param {string} [options.initial] Previous value of the sequence of characters, if any
+ * @param {Number} options.limit Length to trim to
+ * @param {Function} [options.fn] Filter function
+ * @param {string} options.expected Expected final value
+ */
+ function byteLimitTest( options ) {
+ var opt = $.extend( {
+ description: '',
+ sample: '',
+ initial: '',
+ limit: 0,
+ fn: function ( a ) { return a; },
+ expected: ''
+ }, options );
+
+ QUnit.test( opt.description, function ( assert ) {
+ var res = trimByteLength( opt.initial, opt.sample, opt.limit, opt.fn );
+
+ assert.equal(
+ res.newVal,
+ opt.expected,
+ 'New value matches the expected string'
+ );
+ } );
+ }
+
+ byteLimitTest( {
+ description: 'Limit using the maxlength attribute',
+ limit: 10,
+ sample: simpleSample,
+ expected: '1234567890'
+ } );
+
+ byteLimitTest( {
+ description: 'Limit using a custom value (multibyte)',
+ limit: 14,
+ sample: mbSample,
+ expected: '1234567890' + U_20AC + '1'
+ } );
+
+ byteLimitTest( {
+ description: 'Limit using a custom value (multibyte, outside BMP)',
+ limit: 3,
+ sample: poop,
+ expected: ''
+ } );
+
+ byteLimitTest( {
+ description: 'Limit using a custom value (multibyte) overlapping a byte',
+ limit: 12,
+ sample: mbSample,
+ expected: '1234567890'
+ } );
+
+ byteLimitTest( {
+ description: 'Pass the limit and a callback as input filter',
+ limit: 6,
+ fn: function ( val ) {
+ var title = mw.Title.newFromText( String( val ) );
+ // Return without namespace prefix
+ return title ? title.getMain() : '';
+ },
+ sample: 'User:Sample',
+ expected: 'User:Sample'
+ } );
+
+ byteLimitTest( {
+ description: 'Pass the limit and a callback as input filter',
+ limit: 6,
+ fn: function ( val ) {
+ var title = mw.Title.newFromText( String( val ) );
+ // Return without namespace prefix
+ return title ? title.getMain() : '';
+ },
+ sample: 'User:Example',
+ // The callback alters the value to be used to calculeate
+ // the length. The altered value is "Exampl" which has
+ // a length of 6, the "e" would exceed the limit.
+ expected: 'User:Exampl'
+ } );
+
+ byteLimitTest( {
+ description: 'Input filter that increases the length',
+ limit: 10,
+ fn: function ( text ) {
+ return 'prefix' + text;
+ },
+ sample: simpleSample,
+ // Prefix adds 6 characters, limit is reached after 4
+ expected: '1234'
+ } );
+
+ byteLimitTest( {
+ description: 'Trim from insertion when limit exceeded',
+ limit: 3,
+ initial: 'abc',
+ sample: 'zabc',
+ // Trim from the insertion point (at 0), not the end
+ expected: 'abc'
+ } );
+
+ byteLimitTest( {
+ description: 'Trim from insertion when limit exceeded',
+ limit: 3,
+ initial: 'abc',
+ sample: 'azbc',
+ // Trim from the insertion point (at 1), not the end
+ expected: 'abc'
+ } );
+
+ byteLimitTest( {
+ description: 'Do not cut up false matching substrings in emoji insertions',
+ limit: 12,
+ initial: '\uD83D\uDCA9\uD83D\uDCA9', // "💩💩"
+ sample: '\uD83D\uDCA9\uD83D\uDCB9\uD83E\uDCA9\uD83D\uDCA9', // "💩💹🢩💩"
+ expected: '\uD83D\uDCA9\uD83D\uDCB9\uD83D\uDCA9' // "💩💹💩"
+ } );
+
+ byteLimitTest( {
+ description: 'Unpaired surrogates do not crash',
+ limit: 4,
+ sample: '\uD800\uD800\uDFFF',
+ expected: '\uD800'
+ } );
+
+}( jQuery, mediaWiki ) );
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js
index e56c9337..918c923a 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function ( mw ) {
QUnit.module( 'mediawiki.Uri', QUnit.newMwEnvironment( {
setup: function () {
this.mwUriOrg = mw.Uri;
@@ -10,7 +10,7 @@
}
} ) );
- $.each( [ true, false ], function ( i, strictMode ) {
+ [ true, false ].forEach( function ( strictMode ) {
QUnit.test( 'Basic construction and properties (' + ( strictMode ? '' : 'non-' ) + 'strict mode)', function ( assert ) {
var uriString, uri;
uriString = 'http://www.ietf.org/rfc/rfc2396.txt';
@@ -206,6 +206,8 @@
uri = uriBase.clone();
uri.fragment = 'frag';
assert.equal( uri.toString(), 'http://en.wiki.local/w/api.php#frag', 'add a fragment' );
+ uri.fragment = 'café';
+ assert.equal( uri.toString(), 'http://en.wiki.local/w/api.php#caf%C3%A9', 'fragment is url-encoded' );
uri = uriBase.clone();
uri.host = 'fr.wiki.local';
@@ -391,7 +393,7 @@
QUnit.test( 'Advanced URL', function ( assert ) {
var uri, queryString, relativePath;
- uri = new mw.Uri( 'http://auth@www.example.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=value+%28escaped%29#top' );
+ uri = new mw.Uri( 'http://auth@www.example.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=value+%28escaped%29#caf%C3%A9' );
assert.deepEqual(
{
@@ -412,7 +414,7 @@
port: '81',
path: '/dir/dir.2/index.htm',
query: { q1: '0', test1: null, test2: 'value (escaped)' },
- fragment: 'top'
+ fragment: 'café'
},
'basic object properties'
);
@@ -432,7 +434,7 @@
relativePath = uri.getRelativePath();
assert.ok( relativePath.indexOf( uri.path ) >= 0, 'path in relative path' );
assert.ok( relativePath.indexOf( uri.getQueryString() ) >= 0, 'query string in relative path' );
- assert.ok( relativePath.indexOf( uri.fragment ) >= 0, 'fragment in relative path' );
+ assert.ok( relativePath.indexOf( mw.Uri.encode( uri.fragment ) ) >= 0, 'escaped fragment in relative path' );
} );
QUnit.test( 'Parse a uri with an @ symbol in the path and query', function ( assert ) {
@@ -504,4 +506,4 @@
href = uri.toString();
assert.equal( href, testProtocol + testServer + ':' + testPort + testPath, 'Root-relative URL gets host, protocol, and port supplied' );
} );
-}( mediaWiki, jQuery ) );
+}( mediaWiki ) );
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js
index 46d7837f..2a4d9912 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js
@@ -1,4 +1,4 @@
-( function ( $, mw ) {
+( function ( mw ) {
QUnit.module( 'mediawiki.errorLogger', QUnit.newMwEnvironment() );
QUnit.test( 'installGlobalHandler', function ( assert ) {
@@ -39,4 +39,4 @@
assert.strictEqual( w.onerror( errorMessage, errorUrl, errorLine ), true,
'Global handler preserves true return from previous handler' );
} );
-}( jQuery, mediaWiki ) );
+}( mediaWiki ) );
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
index db51fb32..0653dfd3 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
@@ -374,8 +374,7 @@
.then( function ( langClass ) {
var parser;
mw.config.set( 'wgUserLanguage', test.lang );
- // eslint-disable-next-line new-cap
- parser = new mw.jqueryMsg.parser( { language: langClass } );
+ parser = new mw.jqueryMsg.Parser( { language: langClass } );
assert.equal(
parser.parse( test.key, test.args ).html(),
test.result,
@@ -504,11 +503,11 @@
]
];
- $.each( testCases, function () {
+ testCases.forEach( function ( testCase ) {
var
- key = this[ 0 ],
- input = this[ 1 ],
- output = this[ 2 ];
+ key = testCase[ 0 ],
+ input = testCase[ 1 ],
+ output = testCase[ 2 ];
mw.messages.set( key, input );
assert.htmlEqual(
formatParse( key ),
@@ -592,11 +591,11 @@
]
];
- $.each( testCases, function () {
+ testCases.forEach( function ( testCase ) {
var
- key = this[ 0 ],
- input = this[ 1 ],
- output = this[ 2 ],
+ key = testCase[ 0 ],
+ input = testCase[ 1 ],
+ output = testCase[ 2 ],
paramHref = key.slice( 0, 8 ) === 'wikilink' ? 'Example' : 'http://example.com',
paramText = 'Text';
mw.messages.set( key, input );
@@ -898,15 +897,14 @@
var queue;
mw.messages.set( 'formatnum-msg', '{{formatnum:$1}}' );
mw.messages.set( 'formatnum-msg-int', '{{formatnum:$1|R}}' );
- queue = $.map( formatnumTests, function ( test ) {
+ queue = formatnumTests.map( function ( test ) {
var done = assert.async();
return function ( next, abort ) {
getMwLanguage( test.lang )
.then( function ( langClass ) {
var parser;
mw.config.set( 'wgUserLanguage', test.lang );
- // eslint-disable-next-line new-cap
- parser = new mw.jqueryMsg.parser( { language: langClass } );
+ parser = new mw.jqueryMsg.Parser( { language: langClass } );
assert.equal(
parser.parse( test.integer ? 'formatnum-msg-int' : 'formatnum-msg',
[ test.number ] ).html(),
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js
index 5ce61ea7..e4db771c 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js
@@ -49,11 +49,20 @@
mw.language.setData( 'en', 'digitGroupingPattern', null );
mw.language.setData( 'en', 'digitTransformTable', null );
mw.language.setData( 'en', 'separatorTransformTable', { ',': '.', '.': ',' } );
+ mw.language.setData( 'en', 'minimumGroupingDigits', null );
mw.config.set( 'wgUserLanguage', 'en' );
mw.config.set( 'wgTranslateNumerals', true );
- assert.equal( mw.language.convertNumber( 1800 ), '1.800', 'formatting' );
+ assert.equal( mw.language.convertNumber( 180 ), '180', 'formatting 3-digit' );
+ assert.equal( mw.language.convertNumber( 1800 ), '1.800', 'formatting 4-digit' );
+ assert.equal( mw.language.convertNumber( 18000 ), '18.000', 'formatting 5-digit' );
+
assert.equal( mw.language.convertNumber( '1.800', true ), '1800', 'unformatting' );
+
+ mw.language.setData( 'en', 'minimumGroupingDigits', 2 );
+ assert.equal( mw.language.convertNumber( 180 ), '180', 'formatting 3-digit with minimumGroupingDigits=2' );
+ assert.equal( mw.language.convertNumber( 1800 ), '1800', 'formatting 4-digit with minimumGroupingDigits=2' );
+ assert.equal( mw.language.convertNumber( 18000 ), '18.000', 'formatting 5-digit with minimumGroupingDigits=2' );
} );
QUnit.test( 'mw.language.convertNumber - digitTransformTable', function ( assert ) {
@@ -61,6 +70,7 @@
mw.config.set( 'wgTranslateNumerals', true );
mw.language.setData( 'hi', 'digitGroupingPattern', null );
mw.language.setData( 'hi', 'separatorTransformTable', { ',': '.', '.': ',' } );
+ mw.language.setData( 'hi', 'minimumGroupingDigits', null );
// Example from Hindi (MessagesHi.php)
mw.language.setData( 'hi', 'digitTransformTable', {
@@ -303,6 +313,18 @@
description: 'Grammar test for prepositional case, привилегия -> привилегии'
},
{
+ word: 'университет',
+ grammarForm: 'prepositional',
+ expected: 'университете',
+ description: 'Grammar test for prepositional case, университет -> университете'
+ },
+ {
+ word: 'университет',
+ grammarForm: 'genitive',
+ expected: 'университета',
+ description: 'Grammar test for prepositional case, университет -> университете'
+ },
+ {
word: 'установка',
grammarForm: 'prepositional',
expected: 'установке',
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js
index 64415e02..42bc0a76 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js
@@ -1,7 +1,12 @@
( function ( mw, $ ) {
- QUnit.module( 'mediawiki (mw.loader)', QUnit.newMwEnvironment( {
- setup: function () {
+ QUnit.module( 'mediawiki.loader', QUnit.newMwEnvironment( {
+ setup: function ( assert ) {
mw.loader.store.enabled = false;
+
+ // Expose for load.mock.php
+ mw.loader.testFail = function ( reason ) {
+ assert.ok( false, reason );
+ };
},
teardown: function () {
mw.loader.store.enabled = false;
@@ -10,6 +15,10 @@
window.Set = this.nativeSet;
mw.redefineFallbacksForTest();
}
+ // Remove any remaining temporary statics
+ // exposed for cross-file mocks.
+ delete mw.loader.testCallback;
+ delete mw.loader.testFail;
}
} ) );
@@ -82,66 +91,35 @@
);
}
- QUnit.test( 'Basic', function ( assert ) {
- var isAwesomeDone;
-
+ QUnit.test( '.using( .., Function callback ) Promise', function ( assert ) {
+ var script = 0, callback = 0;
mw.loader.testCallback = function () {
- assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined' );
- isAwesomeDone = true;
+ script++;
};
+ mw.loader.implement( 'test.promise', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/mwLoaderTestCallback.js' ) ] );
- mw.loader.implement( 'test.callback', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/mwLoaderTestCallback.js' ) ] );
-
- return mw.loader.using( 'test.callback', function () {
- assert.strictEqual( isAwesomeDone, true, 'test.callback module should\'ve caused isAwesomeDone to be true' );
- delete mw.loader.testCallback;
-
- }, function () {
- assert.ok( false, 'Error callback fired while loader.using "test.callback" module' );
+ return mw.loader.using( 'test.promise', function () {
+ callback++;
+ } ).then( function () {
+ assert.strictEqual( script, 1, 'module script ran' );
+ assert.strictEqual( callback, 1, 'using() callback ran' );
} );
} );
- QUnit.test( 'Object method as module name', function ( assert ) {
- var isAwesomeDone;
-
+ QUnit.test( 'Prototype method as module name', function ( assert ) {
+ var call = 0;
mw.loader.testCallback = function () {
- assert.strictEqual( isAwesomeDone, undefined, 'Implementing module hasOwnProperty: isAwesomeDone should still be undefined' );
- isAwesomeDone = true;
+ call++;
};
-
mw.loader.implement( 'hasOwnProperty', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/mwLoaderTestCallback.js' ) ], {}, {} );
return mw.loader.using( 'hasOwnProperty', function () {
- assert.strictEqual( isAwesomeDone, true, 'hasOwnProperty module should\'ve caused isAwesomeDone to be true' );
- delete mw.loader.testCallback;
-
- }, function () {
- assert.ok( false, 'Error callback fired while loader.using "hasOwnProperty" module' );
+ assert.strictEqual( call, 1, 'module script ran' );
} );
} );
- QUnit.test( '.using( .. ) Promise', function ( assert ) {
- var isAwesomeDone;
-
- mw.loader.testCallback = function () {
- assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined' );
- isAwesomeDone = true;
- };
-
- mw.loader.implement( 'test.promise', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/mwLoaderTestCallback.js' ) ] );
-
- return mw.loader.using( 'test.promise' )
- .done( function () {
- assert.strictEqual( isAwesomeDone, true, 'test.promise module should\'ve caused isAwesomeDone to be true' );
- delete mw.loader.testCallback;
- } )
- .fail( function () {
- assert.ok( false, 'Error callback fired while loader.using "test.promise" module' );
- } );
- } );
-
// Covers mw.loader#sortDependencies (with native Set if available)
- QUnit.test( '.using() Error: Circular dependency [StringSet default]', function ( assert ) {
+ QUnit.test( '.using() - Error: Circular dependency [StringSet default]', function ( assert ) {
var done = assert.async();
mw.loader.register( [
@@ -161,7 +139,7 @@
} );
// @covers mw.loader#sortDependencies (with fallback shim)
- QUnit.test( '.using() Error: Circular dependency [StringSet shim]', function ( assert ) {
+ QUnit.test( '.using() - Error: Circular dependency [StringSet shim]', function ( assert ) {
var done = assert.async();
if ( !window.Set ) {
@@ -425,23 +403,34 @@
mw.loader.load( 'test.implement.d' );
} );
+ QUnit.test( '.implement( messages before script )', function ( assert ) {
+ mw.loader.implement(
+ 'test.implement.order',
+ function () {
+ assert.equal( mw.loader.getState( 'test.implement.order' ), 'executing', 'state during script execution' );
+ assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'messages load before script execution' );
+ },
+ {},
+ {
+ 'test-foobar': 'Hello Foobar, $1!'
+ }
+ );
+
+ return mw.loader.using( 'test.implement.order' ).then( function () {
+ assert.equal( mw.loader.getState( 'test.implement.order' ), 'ready', 'final success state' );
+ } );
+ } );
+
// @import (T33676)
- QUnit.test( '.implement( styles has @import )', function ( assert ) {
- var isJsExecuted, $element,
+ QUnit.test( '.implement( styles with @import )', function ( assert ) {
+ var $element,
done = assert.async();
mw.loader.implement(
'test.implement.import',
function () {
- assert.strictEqual( isJsExecuted, undefined, 'script not executed multiple times' );
- isJsExecuted = true;
-
- assert.equal( mw.loader.getState( 'test.implement.import' ), 'executing', 'module state during implement() script execution' );
-
$element = $( '<div class="mw-test-implement-import">Foo bar</div>' ).appendTo( '#qunit-fixture' );
- assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'messages load before script execution' );
-
assertStyleAsync( assert, $element, 'float', 'right', function () {
assert.equal( $element.css( 'text-align' ), 'center',
'CSS styles after the @import rule are working'
@@ -457,16 +446,10 @@
+ '\');\n'
+ '.mw-test-implement-import { text-align: center; }'
]
- },
- {
- 'test-foobar': 'Hello Foobar, $1!'
}
);
- mw.loader.using( 'test.implement.import' ).always( function () {
- assert.strictEqual( isJsExecuted, true, 'script executed' );
- assert.equal( mw.loader.getState( 'test.implement.import' ), 'ready', 'module state after script execution' );
- } );
+ return mw.loader.using( 'test.implement.import' );
} );
QUnit.test( '.implement( dependency with styles )', function ( assert ) {
@@ -532,8 +515,6 @@
return mw.loader.using( 'test.implement.msgs', function () {
assert.ok( mw.messages.exists( 'T31107' ), 'T31107: messages-only module should implement ok' );
- }, function () {
- assert.ok( false, 'Error callback fired while implementing "test.implement.msgs" module' );
} );
} );
@@ -542,6 +523,73 @@
assert.strictEqual( mw.loader.getState( 'test.empty' ), 'ready' );
} );
+ // @covers mw.loader#batchRequest
+ // This is a regression test because in the past we called getCombinedVersion()
+ // for all requested modules, before url splitting took place.
+ // Discovered as part of T188076, but not directly related.
+ QUnit.test( 'Url composition (modules considered for version)', function ( assert ) {
+ mw.loader.register( [
+ // [module, version, dependencies, group, source]
+ [ 'testUrlInc', 'url', [], null, 'testloader' ],
+ [ 'testUrlIncDump', 'dump', [], null, 'testloader' ]
+ ] );
+
+ mw.config.set( 'wgResourceLoaderMaxQueryLength', 10 );
+
+ return mw.loader.using( [ 'testUrlIncDump', 'testUrlInc' ] ).then( function ( require ) {
+ assert.propEqual(
+ require( 'testUrlIncDump' ).query,
+ {
+ modules: 'testUrlIncDump',
+ // Expected: Wrapped hash just for this one module
+ // $hash = hash( 'fnv132', 'dump');
+ // base_convert( $hash, 16, 36 ); // "13e9zzn"
+ // Previously: Wrapped hash for both modules, despite being in separate requests
+ // $hash = hash( 'fnv132', 'urldump' );
+ // base_convert( $hash, 16, 36 ); // "18kz9ca"
+ version: '13e9zzn'
+ },
+ 'Query parameters'
+ );
+
+ assert.strictEqual( mw.loader.getState( 'testUrlInc' ), 'ready', 'testUrlInc also loaded' );
+ } );
+ } );
+
+ // @covers mw.loader#batchRequest
+ // @covers mw.loader#buildModulesString
+ QUnit.test( 'Url composition (order of modules for version) – T188076', function ( assert ) {
+ mw.loader.register( [
+ // [module, version, dependencies, group, source]
+ [ 'testUrlOrder', 'url', [], null, 'testloader' ],
+ [ 'testUrlOrder.a', '1', [], null, 'testloader' ],
+ [ 'testUrlOrder.b', '2', [], null, 'testloader' ],
+ [ 'testUrlOrderDump', 'dump', [], null, 'testloader' ]
+ ] );
+
+ return mw.loader.using( [
+ 'testUrlOrderDump',
+ 'testUrlOrder.b',
+ 'testUrlOrder.a',
+ 'testUrlOrder'
+ ] ).then( function ( require ) {
+ assert.propEqual(
+ require( 'testUrlOrderDump' ).query,
+ {
+ modules: 'testUrlOrder,testUrlOrderDump|testUrlOrder.a,b',
+ // Expected: Combined in order after string packing
+ // $hash = hash( 'fnv132', 'urldump12' );
+ // base_convert( $hash, 16, 36 ); // "1knqzan"
+ // Previously: Combined in order of before string packing
+ // $hash = hash( 'fnv132', 'url12dump' );
+ // base_convert( $hash, 16, 36 ); // "11eo3in"
+ version: '1knqzan'
+ },
+ 'Query parameters'
+ );
+ } );
+ } );
+
QUnit.test( 'Broken indirect dependency', function ( assert ) {
// don't emit an error event
this.sandbox.stub( mw, 'track' );
@@ -638,9 +686,9 @@
] );
function verifyModuleStates() {
- assert.equal( mw.loader.getState( 'testMissing' ), 'missing', 'Module not known to server must have state "missing"' );
- assert.equal( mw.loader.getState( 'testUsesMissing' ), 'error', 'Module with missing dependency must have state "error"' );
- assert.equal( mw.loader.getState( 'testUsesNestedMissing' ), 'error', 'Module with indirect missing dependency must have state "error"' );
+ assert.equal( mw.loader.getState( 'testMissing' ), 'missing', 'Module "testMissing" state' );
+ assert.equal( mw.loader.getState( 'testUsesMissing' ), 'error', 'Module "testUsesMissing" state' );
+ assert.equal( mw.loader.getState( 'testUsesNestedMissing' ), 'error', 'Module "testUsesNestedMissing" state' );
}
mw.loader.using( [ 'testUsesNestedMissing' ],
@@ -674,24 +722,16 @@
[ 'testUsesSkippable', '1', [ 'testSkipped', 'testNotSkipped' ], null, 'testloader' ]
] );
- function verifyModuleStates() {
- assert.equal( mw.loader.getState( 'testSkipped' ), 'ready', 'Module is ready when skipped' );
- assert.equal( mw.loader.getState( 'testNotSkipped' ), 'ready', 'Module is ready when not skipped but loaded' );
- assert.equal( mw.loader.getState( 'testUsesSkippable' ), 'ready', 'Module is ready when skippable dependencies are ready' );
- }
-
- return mw.loader.using( [ 'testUsesSkippable' ],
+ return mw.loader.using( [ 'testUsesSkippable' ] ).then(
function () {
- assert.ok( true, 'Success handler should be invoked.' );
- assert.ok( true ); // Dummy to match error handler and reach QUnit expect()
-
- verifyModuleStates();
+ assert.equal( mw.loader.getState( 'testSkipped' ), 'ready', 'Skipped module' );
+ assert.equal( mw.loader.getState( 'testNotSkipped' ), 'ready', 'Regular module' );
+ assert.equal( mw.loader.getState( 'testUsesSkippable' ), 'ready', 'Regular module with skippable dependency' );
},
function ( e, badmodules ) {
- assert.ok( false, 'Error handler should not be invoked.' );
- assert.deepEqual( badmodules, [], 'Bad modules as expected.' );
-
- verifyModuleStates();
+ // Should not fail and QUnit would already catch this,
+ // but add a handler anyway to report details from 'badmodules
+ assert.deepEqual( badmodules, [], 'Bad modules' );
}
);
} );
@@ -710,6 +750,7 @@
assert.equal( target.slice( 0, 2 ), '//', 'URL is protocol-relative' );
mw.loader.testCallback = function () {
+ // Ensure once, delete now
delete mw.loader.testCallback;
assert.ok( true, 'callback' );
done();
@@ -728,6 +769,7 @@
assert.equal( target.slice( 0, 1 ), '/', 'URL is relative to document root' );
mw.loader.testCallback = function () {
+ // Ensure once, delete now
delete mw.loader.testCallback;
assert.ok( true, 'callback' );
done();
@@ -812,18 +854,18 @@
} );
QUnit.test( 'Stale response caching - backcompat', function ( assert ) {
- var count = 0;
+ var script = 0;
mw.loader.store.enabled = true;
mw.loader.register( 'test.stalebc', 'v2' );
assert.strictEqual( mw.loader.store.get( 'test.stalebc' ), false, 'Not in store' );
mw.loader.implement( 'test.stalebc', function () {
- count++;
+ script++;
} );
return mw.loader.using( 'test.stalebc' )
.then( function () {
- assert.strictEqual( count, 1 );
+ assert.strictEqual( script, 1, 'module script ran' );
assert.strictEqual( mw.loader.getState( 'test.stalebc' ), 'ready' );
assert.ok( mw.loader.store.get( 'test.stalebc' ), 'In store' );
} )
@@ -902,37 +944,33 @@
} catch ( e ) {
assert.equal( null, String( e ), 'require works asynchrously in debug mode' );
}
- }, function () {
- assert.ok( false, 'Error callback fired while loader.using "test.require.callback" module' );
} );
} );
QUnit.test( 'Implicit dependencies', function ( assert ) {
- var ranUser = false,
- userSeesSite = false,
- ranSite = false;
+ var user = 0,
+ site = 0,
+ siteFromUser = 0;
mw.loader.implement(
'site',
function () {
- ranSite = true;
+ site++;
}
);
mw.loader.implement(
'user',
function () {
- userSeesSite = ranSite;
- ranUser = true;
+ user++;
+ siteFromUser = site;
}
);
- assert.strictEqual( ranSite, false, 'verify site module not yet loaded' );
- assert.strictEqual( ranUser, false, 'verify user module not yet loaded' );
return mw.loader.using( 'user', function () {
- assert.strictEqual( ranSite, true, 'ran site module' );
- assert.strictEqual( ranUser, true, 'ran user module' );
- assert.strictEqual( userSeesSite, true, 'ran site before user module' );
-
+ assert.strictEqual( site, 1, 'site module' );
+ assert.strictEqual( user, 1, 'user module' );
+ assert.strictEqual( siteFromUser, 1, 'site ran before user' );
+ } ).always( function () {
// Reset
mw.loader.moduleRegistry[ 'site' ].state = 'registered';
mw.loader.moduleRegistry[ 'user' ].state = 'registered';
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js
index b20b68f5..6a1b83cf 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js
@@ -12,7 +12,7 @@
assert.strictEqual( $( '.toc' ).length, 0, 'There is no table of contents on the page at the beginning' );
tocHtml = '<div id="toc" class="toc">' +
- '<div class="toctitle">' +
+ '<div class="toctitle" lang="en" dir="ltr">' +
'<h2>Contents</h2>' +
'</div>' +
'<ul><li></li></ul>' +
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js
index bc126429..814a2075 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js
@@ -97,6 +97,14 @@
assert.notEqual( result, result2, 'different when called multiple times' );
} );
+ QUnit.test( 'stickyRandomId', function ( assert ) {
+ var result = mw.user.stickyRandomId(),
+ result2 = mw.user.stickyRandomId();
+ assert.equal( typeof result, 'string', 'type' );
+ assert.strictEqual( /^[a-f0-9]{16}$/.test( result ), true, '16 HEX symbols string' );
+ assert.equal( result2, result, 'sticky' );
+ } );
+
QUnit.test( 'sessionId', function ( assert ) {
var result = mw.user.sessionId(),
result2 = mw.user.sessionId();
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js
index bb276265..b8464e99 100644
--- a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js
@@ -53,7 +53,7 @@
];
Array.prototype.push.apply( IPV6_CASES,
- $.map( [
+ [
'fc:100::',
'fc:100:a::',
'fc:100:a:d::',
@@ -69,8 +69,8 @@
'::fc:100:a:d:1:e',
'::fc:100:a:d:1:e:ac',
'fc:100:a:d:1:e:ac:0'
- ], function ( el ) {
- return [ [ true, el, el + ' is a valid IP' ] ];
+ ].map( function ( el ) {
+ return [ true, el, el + ' is a valid IP' ];
} )
);
@@ -132,7 +132,7 @@
newExperimental = [ 'html5', 'html5-legacy' ];
// Test cases are kept in sync with SanitizerTest.php
- $.each( [
+ [
// Pure legacy: how MW worked before 2017
[ legacy, text, legacyEncoded ],
// Transition to a new world: legacy links with HTML5 fallback
@@ -145,7 +145,7 @@
[ experimentalLegacy, text, html5Experimental ],
// Migration from $wgExperimentalHtmlIds to modern HTML5
[ newExperimental, text, html5Encoded ]
- ], function ( index, testCase ) {
+ ].forEach( function ( testCase ) {
mw.config.set( 'wgFragmentMode', testCase[ 0 ] );
assert.equal( util.escapeIdForAttribute( testCase[ 1 ] ), testCase[ 2 ] );
@@ -166,7 +166,7 @@
experimentalLegacy = [ 'html5-legacy', 'legacy' ],
newExperimental = [ 'html5', 'html5-legacy' ];
- $.each( [
+ [
// Pure legacy: how MW worked before 2017
[ legacy, text, legacyEncoded ],
// Transition to a new world: legacy links with HTML5 fallback
@@ -179,7 +179,7 @@
[ experimentalLegacy, text, html5Experimental ],
// Migration from wgExperimentalHtmlIds to modern HTML5
[ newExperimental, text, html5Encoded ]
- ], function ( index, testCase ) {
+ ].forEach( function ( testCase ) {
mw.config.set( 'wgFragmentMode', testCase[ 0 ] );
assert.equal( util.escapeIdForLink( testCase[ 1 ] ), testCase[ 2 ] );
@@ -246,16 +246,26 @@
assert.equal( href, '/wiki/#Fragment', 'empty title with fragment' );
href = util.getUrl( '#Fragment', { action: 'edit' } );
- assert.equal( href, '/w/index.php?action=edit#Fragment', 'epmty title with query string and fragment' );
+ assert.equal( href, '/w/index.php?action=edit#Fragment', 'empty title with query string and fragment' );
+ mw.config.set( 'wgFragmentMode', [ 'legacy' ] );
href = util.getUrl( 'Foo:Sandbox \xC4#Fragment \xC4', { action: 'edit' } );
assert.equal( href, '/w/index.php?title=Foo:Sandbox_%C3%84&action=edit#Fragment_.C3.84', 'title with query string, fragment, and special characters' );
+ mw.config.set( 'wgFragmentMode', [ 'html5' ] );
+ href = util.getUrl( 'Foo:Sandbox \xC4#Fragment \xC4', { action: 'edit' } );
+ assert.equal( href, '/w/index.php?title=Foo:Sandbox_%C3%84&action=edit#Fragment_Ä', 'title with query string, fragment, and special characters' );
+
href = util.getUrl( 'Foo:%23#Fragment', { action: 'edit' } );
assert.equal( href, '/w/index.php?title=Foo:%2523&action=edit#Fragment', 'title containing %23 (#), fragment, and a query string' );
+ mw.config.set( 'wgFragmentMode', [ 'legacy' ] );
href = util.getUrl( '#+&=:;@$-_.!*/[]<>\'§', { action: 'edit' } );
assert.equal( href, '/w/index.php?action=edit#.2B.26.3D:.3B.40.24-_..21.2A.2F.5B.5D.3C.3E.27.C2.A7', 'fragment with various characters' );
+
+ mw.config.set( 'wgFragmentMode', [ 'html5' ] );
+ href = util.getUrl( '#+&=:;@$-_.!*/[]<>\'§', { action: 'edit' } );
+ assert.equal( href, '/w/index.php?action=edit#+&=:;@$-_.!*/[]<>\'§', 'fragment with various characters' );
} );
QUnit.test( 'wikiScript', function ( assert ) {
@@ -432,23 +442,23 @@
} );
QUnit.test( 'isIPv6Address', function ( assert ) {
- $.each( IPV6_CASES, function ( i, ipCase ) {
+ IPV6_CASES.forEach( function ( ipCase ) {
assert.strictEqual( util.isIPv6Address( ipCase[ 1 ] ), ipCase[ 0 ], ipCase[ 2 ] );
} );
} );
QUnit.test( 'isIPv4Address', function ( assert ) {
- $.each( IPV4_CASES, function ( i, ipCase ) {
+ IPV4_CASES.forEach( function ( ipCase ) {
assert.strictEqual( util.isIPv4Address( ipCase[ 1 ] ), ipCase[ 0 ], ipCase[ 2 ] );
} );
} );
QUnit.test( 'isIPAddress', function ( assert ) {
- $.each( IPV4_CASES, function ( i, ipCase ) {
+ IPV4_CASES.forEach( function ( ipCase ) {
assert.strictEqual( util.isIPv4Address( ipCase[ 1 ] ), ipCase[ 0 ], ipCase[ 2 ] );
} );
- $.each( IPV6_CASES, function ( i, ipCase ) {
+ IPV6_CASES.forEach( function ( ipCase ) {
assert.strictEqual( util.isIPv6Address( ipCase[ 1 ] ), ipCase[ 0 ], ipCase[ 2 ] );
} );
} );
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.visibleTimeout.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.visibleTimeout.test.js
new file mode 100644
index 00000000..7f8819de
--- /dev/null
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki/mediawiki.visibleTimeout.test.js
@@ -0,0 +1,115 @@
+( function ( mw ) {
+
+ QUnit.module( 'mediawiki.visibleTimeout', QUnit.newMwEnvironment( {
+ setup: function () {
+ // Document with just enough stuff to make the tests work.
+ var listeners = [];
+ this.mockDocument = {
+ hidden: false,
+ addEventListener: function ( type, listener ) {
+ if ( type === 'visibilitychange' ) {
+ listeners.push( listener );
+ }
+ },
+ removeEventListener: function ( type, listener ) {
+ var i;
+ if ( type === 'visibilitychange' ) {
+ i = listeners.indexOf( listener );
+ if ( i >= 0 ) {
+ listeners.splice( i, 1 );
+ }
+ }
+ },
+ // Helper function to swap visibility and run listeners
+ toggleVisibility: function () {
+ var i;
+ this.hidden = !this.hidden;
+ for ( i = 0; i < listeners.length; i++ ) {
+ listeners[ i ]();
+ }
+ }
+ };
+ this.visibleTimeout = require( 'mediawiki.visibleTimeout' );
+ this.visibleTimeout.setDocument( this.mockDocument );
+
+ this.sandbox.useFakeTimers();
+ // mw.now() doesn't respect the fake clock injected by useFakeTimers
+ this.stub( mw, 'now', ( function () {
+ return this.sandbox.clock.now;
+ } ).bind( this ) );
+ }
+ } ) );
+
+ QUnit.test( 'basic usage', function ( assert ) {
+ var called = 0;
+
+ this.visibleTimeout.set( function () {
+ called++;
+ }, 0 );
+ assert.strictEqual( called, 0 );
+ this.sandbox.clock.tick( 1 );
+ assert.strictEqual( called, 1 );
+
+ this.sandbox.clock.tick( 100 );
+ assert.strictEqual( called, 1 );
+
+ this.visibleTimeout.set( function () {
+ called++;
+ }, 10 );
+ this.sandbox.clock.tick( 10 );
+ assert.strictEqual( called, 2 );
+ } );
+
+ QUnit.test( 'can cancel timeout', function ( assert ) {
+ var called = 0,
+ timeout = this.visibleTimeout.set( function () {
+ called++;
+ }, 0 );
+
+ this.visibleTimeout.clear( timeout );
+ this.sandbox.clock.tick( 10 );
+ assert.strictEqual( called, 0 );
+
+ timeout = this.visibleTimeout.set( function () {
+ called++;
+ }, 100 );
+ this.sandbox.clock.tick( 50 );
+ assert.strictEqual( called, 0 );
+ this.visibleTimeout.clear( timeout );
+ this.sandbox.clock.tick( 100 );
+ assert.strictEqual( called, 0 );
+ } );
+
+ QUnit.test( 'start hidden and become visible', function ( assert ) {
+ var called = 0;
+
+ this.mockDocument.hidden = true;
+ this.visibleTimeout.set( function () {
+ called++;
+ }, 0 );
+ this.sandbox.clock.tick( 10 );
+ assert.strictEqual( called, 0 );
+
+ this.mockDocument.toggleVisibility();
+ this.sandbox.clock.tick( 10 );
+ assert.strictEqual( called, 1 );
+ } );
+
+ QUnit.test( 'timeout is cumulative', function ( assert ) {
+ var called = 0;
+
+ this.visibleTimeout.set( function () {
+ called++;
+ }, 100 );
+ this.sandbox.clock.tick( 50 );
+ assert.strictEqual( called, 0 );
+
+ this.mockDocument.toggleVisibility();
+ this.sandbox.clock.tick( 1000 );
+ assert.strictEqual( called, 0 );
+
+ this.mockDocument.toggleVisibility();
+ this.sandbox.clock.tick( 50 );
+ assert.strictEqual( called, 1 );
+ } );
+}( mediaWiki ) );
diff --git a/www/wiki/tests/qunit/suites/resources/startup.test.js b/www/wiki/tests/qunit/suites/resources/startup.test.js
index ee1340d6..6a704b5a 100644
--- a/www/wiki/tests/qunit/suites/resources/startup.test.js
+++ b/www/wiki/tests/qunit/suites/resources/startup.test.js
@@ -1,5 +1,5 @@
/* global isCompatible: true */
-( function ( $ ) {
+( function () {
var testcases = {
tested: [
/* Grade A */
@@ -21,11 +21,8 @@
'Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36 OPR/15.0.1147.153',
'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.57 Safari/537.36 OPR/16.0.1196.62',
'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36 OPR/23.0.1522.75',
- // Internet Explorer 10+
- 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)',
+ // Internet Explorer 11
'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko',
- // IE Mobile
- 'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; NOKIA; Lumia 800)',
// Edge
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246',
// Edge Mobile
@@ -107,6 +104,10 @@
blacklisted: [
/* Grade C */
+ // Internet Explorer 10
+ 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)',
+ // IE Mobile 10
+ 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; HTC; Windows Phone 8X by HTC)',
// PlayStation
'Mozilla/5.0 (PLAYSTATION 3; 1.10)',
'Mozilla/5.0 (PLAYSTATION 3; 3.55)',
@@ -146,14 +147,14 @@
QUnit.module( 'startup', QUnit.newMwEnvironment() );
QUnit.test( 'isCompatible( featureTestable )', function ( assert ) {
- $.each( testcases.tested, function ( i, ua ) {
+ testcases.tested.forEach( function ( ua ) {
assert.strictEqual( isCompatible( ua ), true, ua );
} );
} );
QUnit.test( 'isCompatible( blacklisted )', function ( assert ) {
- $.each( testcases.blacklisted, function ( i, ua ) {
+ testcases.blacklisted.forEach( function ( ua ) {
assert.strictEqual( isCompatible( ua ), false, ua );
} );
} );
-}( jQuery ) );
+}() );
diff --git a/www/wiki/tests/selenium/README.md b/www/wiki/tests/selenium/README.md
index 0c8b4473..b15d4073 100644
--- a/www/wiki/tests/selenium/README.md
+++ b/www/wiki/tests/selenium/README.md
@@ -28,24 +28,28 @@ environment variable to any value:
To run only one file (for example page.js), you first need to spawn the chromedriver:
- chromedriver --url-base=/wd/hub --port=4444
+ chromedriver --url-base=wd/hub --port=4444
Then in another terminal:
cd tests/selenium
../../node_modules/.bin/wdio --spec specs/page.js
+To run only one test (name contains string 'preferences'):
+
+ ../../node_modules/.bin/wdio --spec specs/user.js --mochaOpts.grep preferences
+
The runner reads the config file `wdio.conf.js` and runs the spec listed in
`page.js`.
-The defaults in the configuration files aim are targetting a MediaWiki-Vagrant
-installation on installation on http://127.0.0.1:8080 with a user Admin and
-password 'vagrant'. Those settings can be overriden using environment
+The defaults in the configuration files aim are targeting a MediaWiki-Vagrant
+installation on http://127.0.0.1:8080 with a user Admin and
+password 'vagrant'. Those settings can be overridden using environment
variables:
`MW_SERVER`: to be set to the value of your $wgServer
-`MW_SCRIPT_PATH`: ditto with $wgScriptPath
-`MEDIAWIKI_USER`: username of an account that can create users on the wiki.
+`MW_SCRIPT_PATH`: ditto with $wgScriptPath
+`MEDIAWIKI_USER`: username of an account that can create users on the wiki
`MEDIAWIKI_PASSWORD`: password for above user
Example:
diff --git a/www/wiki/tests/selenium/pageobjects/createaccount.page.js b/www/wiki/tests/selenium/pageobjects/createaccount.page.js
index f54e31c8..105f4092 100644
--- a/www/wiki/tests/selenium/pageobjects/createaccount.page.js
+++ b/www/wiki/tests/selenium/pageobjects/createaccount.page.js
@@ -22,54 +22,26 @@ class CreateAccountPage extends Page {
}
apiCreateAccount( username, password ) {
- const url = require( 'url' ), // https://nodejs.org/docs/latest/api/url.html
- baseUrl = url.parse( browser.options.baseUrl ), // http://webdriver.io/guide/testrunner/browserobject.html
- Bot = require( 'nodemw' ), // https://github.com/macbre/nodemw
- client = new Bot( {
- protocol: baseUrl.protocol,
- server: baseUrl.hostname,
- port: baseUrl.port,
- path: baseUrl.path,
- debug: false
- } );
- return new Promise( ( resolve, reject ) => {
- client.api.call(
- {
- action: 'query',
- meta: 'tokens',
- type: 'createaccount'
- },
- /**
- * @param {Error|null} err
- * @param {Object} info Processed query result
- * @param {Object} next More results?
- * @param {Object} data Raw data
- */
- function ( err, info, next, data ) {
- if ( err ) {
- reject( err );
- return;
- }
- client.api.call( {
- action: 'createaccount',
- createreturnurl: browser.options.baseUrl,
- createtoken: data.query.tokens.createaccounttoken,
- username: username,
- password: password,
- retype: password
- }, function ( err ) {
- if ( err ) {
- reject( err );
- return;
- }
- resolve();
- }, 'POST' );
- },
- 'POST'
- );
+ const MWBot = require( 'mwbot' ), // https://github.com/Fannon/mwbot
+ Promise = require( 'bluebird' );
+ let bot = new MWBot();
- } );
+ return Promise.coroutine( function* () {
+ yield bot.loginGetCreateaccountToken( {
+ apiUrl: `${browser.options.baseUrl}/api.php`,
+ username: browser.options.username,
+ password: browser.options.password
+ } );
+ yield bot.request( {
+ action: 'createaccount',
+ createreturnurl: browser.options.baseUrl,
+ createtoken: bot.createaccountToken,
+ username: username,
+ password: password,
+ retype: password
+ } );
+ } ).call( this );
}
diff --git a/www/wiki/tests/selenium/pageobjects/delete.page.js b/www/wiki/tests/selenium/pageobjects/delete.page.js
new file mode 100644
index 00000000..d43cb9f6
--- /dev/null
+++ b/www/wiki/tests/selenium/pageobjects/delete.page.js
@@ -0,0 +1,39 @@
+'use strict';
+const Page = require( './page' );
+
+class DeletePage extends Page {
+
+ get reason() { return browser.element( '#wpReason' ); }
+ get watch() { return browser.element( '#wpWatch' ); }
+ get submit() { return browser.element( '#wpConfirmB' ); }
+ get displayedContent() { return browser.element( '#mw-content-text' ); }
+
+ open( name ) {
+ super.open( name + '&action=delete' );
+ }
+
+ delete( name, reason ) {
+ this.open( name );
+ this.reason.setValue( reason );
+ this.submit.click();
+ }
+
+ apiDelete( name, reason ) {
+
+ const MWBot = require( 'mwbot' ), // https://github.com/Fannon/mwbot
+ Promise = require( 'bluebird' );
+ let bot = new MWBot();
+
+ return Promise.coroutine( function* () {
+ yield bot.loginGetEditToken( {
+ apiUrl: `${browser.options.baseUrl}/api.php`,
+ username: browser.options.username,
+ password: browser.options.password
+ } );
+ yield bot.delete( name, reason );
+ } ).call( this );
+
+ }
+
+}
+module.exports = new DeletePage();
diff --git a/www/wiki/tests/selenium/pageobjects/edit.page.js b/www/wiki/tests/selenium/pageobjects/edit.page.js
index 25da8cb8..33a27f0f 100644
--- a/www/wiki/tests/selenium/pageobjects/edit.page.js
+++ b/www/wiki/tests/selenium/pageobjects/edit.page.js
@@ -19,25 +19,20 @@ class EditPage extends Page {
}
apiEdit( name, content ) {
- const url = require( 'url' ), // https://nodejs.org/docs/latest/api/url.html
- baseUrl = url.parse( browser.options.baseUrl ), // http://webdriver.io/guide/testrunner/browserobject.html
- Bot = require( 'nodemw' ), // https://github.com/macbre/nodemw
- client = new Bot( {
- protocol: baseUrl.protocol,
- server: baseUrl.hostname,
- port: baseUrl.port,
- path: baseUrl.path,
- debug: false
- } );
- return new Promise( ( resolve, reject ) => {
- client.edit( name, content, `Created page with "${content}"`, function ( err ) {
- if ( err ) {
- return reject( err );
- }
- resolve();
+ const MWBot = require( 'mwbot' ), // https://github.com/Fannon/mwbot
+ Promise = require( 'bluebird' );
+ let bot = new MWBot();
+
+ return Promise.coroutine( function* () {
+ yield bot.loginGetEditToken( {
+ apiUrl: `${browser.options.baseUrl}/api.php`,
+ username: browser.options.username,
+ password: browser.options.password
} );
- } );
+ yield bot.edit( name, content, `Created page with "${content}"` );
+ } ).call( this );
+
}
}
diff --git a/www/wiki/tests/selenium/pageobjects/page.js b/www/wiki/tests/selenium/pageobjects/page.js
index 864bdaea..77bb1f4e 100644
--- a/www/wiki/tests/selenium/pageobjects/page.js
+++ b/www/wiki/tests/selenium/pageobjects/page.js
@@ -1,11 +1,8 @@
// From http://webdriver.io/guide/testrunner/pageobjects.html
'use strict';
class Page {
- constructor() {
- this.title = 'My Page';
- }
open( path ) {
- browser.url( '/index.php?title=' + path );
+ browser.url( browser.options.baseUrl + '/index.php?title=' + path );
}
}
module.exports = Page;
diff --git a/www/wiki/tests/selenium/pageobjects/restore.page.js b/www/wiki/tests/selenium/pageobjects/restore.page.js
new file mode 100644
index 00000000..071f7f98
--- /dev/null
+++ b/www/wiki/tests/selenium/pageobjects/restore.page.js
@@ -0,0 +1,21 @@
+'use strict';
+const Page = require( './page' );
+
+class RestorePage extends Page {
+
+ get reason() { return browser.element( '#wpComment' ); }
+ get submit() { return browser.element( '#mw-undelete-submit' ); }
+ get displayedContent() { return browser.element( '#mw-content-text' ); }
+
+ open( name ) {
+ super.open( 'Special:Undelete/' + name );
+ }
+
+ restore( name, reason ) {
+ this.open( name );
+ this.reason.setValue( reason );
+ this.submit.click();
+ }
+
+}
+module.exports = new RestorePage();
diff --git a/www/wiki/tests/selenium/pageobjects/userlogin.page.js b/www/wiki/tests/selenium/pageobjects/userlogin.page.js
index bdbd41bc..0061d0c2 100644
--- a/www/wiki/tests/selenium/pageobjects/userlogin.page.js
+++ b/www/wiki/tests/selenium/pageobjects/userlogin.page.js
@@ -19,5 +19,9 @@ class UserLoginPage extends Page {
this.loginButton.click();
}
+ loginAdmin() {
+ this.login( browser.options.username, browser.options.password );
+ }
+
}
module.exports = new UserLoginPage();
diff --git a/www/wiki/tests/selenium/specs/page.js b/www/wiki/tests/selenium/specs/page.js
index 06d3d60a..376dce59 100644
--- a/www/wiki/tests/selenium/specs/page.js
+++ b/www/wiki/tests/selenium/specs/page.js
@@ -1,5 +1,7 @@
'use strict';
const assert = require( 'assert' ),
+ DeletePage = require( '../pageobjects/delete.page' ),
+ RestorePage = require( '../pageobjects/restore.page' ),
EditPage = require( '../pageobjects/edit.page' ),
HistoryPage = require( '../pageobjects/history.page' ),
UserLoginPage = require( '../pageobjects/userlogin.page' );
@@ -9,6 +11,10 @@ describe( 'Page', function () {
var content,
name;
+ function getTestString() {
+ return Math.random().toString() + '-öäü-♠♣♥♦';
+ }
+
before( function () {
// disable VisualEditor welcome dialog
UserLoginPage.open();
@@ -17,8 +23,8 @@ describe( 'Page', function () {
beforeEach( function () {
browser.deleteCookie();
- content = Math.random().toString();
- name = Math.random().toString();
+ content = getTestString();
+ name = getTestString();
} );
it( 'should be creatable', function () {
@@ -32,9 +38,29 @@ describe( 'Page', function () {
} );
- it( 'should be editable', function () {
+ it( 'should be re-creatable', function () {
+ let initialContent = getTestString();
+
+ // create
+ browser.call( function () {
+ return EditPage.apiEdit( name, initialContent );
+ } );
+
+ // delete
+ browser.call( function () {
+ return DeletePage.apiDelete( name, 'delete prior to recreate' );
+ } );
+
+ // create
+ EditPage.edit( name, content );
- var content2 = Math.random().toString();
+ // check
+ assert.equal( EditPage.heading.getText(), name );
+ assert.equal( EditPage.displayedContent.getText(), content );
+
+ } );
+
+ it( 'should be editable', function () {
// create
browser.call( function () {
@@ -42,11 +68,11 @@ describe( 'Page', function () {
} );
// edit
- EditPage.edit( name, content2 );
+ EditPage.edit( name, content );
// check
assert.equal( EditPage.heading.getText(), name );
- assert.equal( EditPage.displayedContent.getText(), content2 );
+ assert.equal( EditPage.displayedContent.getText(), content );
} );
@@ -63,4 +89,48 @@ describe( 'Page', function () {
} );
+ it( 'should be deletable', function () {
+
+ // login
+ UserLoginPage.loginAdmin();
+
+ // create
+ browser.call( function () {
+ return EditPage.apiEdit( name, content );
+ } );
+
+ // delete
+ DeletePage.delete( name, content + '-deletereason' );
+
+ // check
+ assert.equal(
+ DeletePage.displayedContent.getText(),
+ '"' + name + '" has been deleted. See deletion log for a record of recent deletions.\nReturn to Main Page.'
+ );
+
+ } );
+
+ it( 'should be restorable', function () {
+
+ // login
+ UserLoginPage.loginAdmin();
+
+ // create
+ browser.call( function () {
+ return EditPage.apiEdit( name, content );
+ } );
+
+ // delete
+ browser.call( function () {
+ return DeletePage.apiDelete( name, content + '-deletereason' );
+ } );
+
+ // restore
+ RestorePage.restore( name, content + '-restorereason' );
+
+ // check
+ assert.equal( RestorePage.displayedContent.getText(), name + ' has been restored\nConsult the deletion log for a record of recent deletions and restorations.' );
+
+ } );
+
} );
diff --git a/www/wiki/tests/selenium/wdio.conf.jenkins.js b/www/wiki/tests/selenium/wdio.conf.jenkins.js
deleted file mode 100644
index 6049eb25..00000000
--- a/www/wiki/tests/selenium/wdio.conf.jenkins.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/* eslint no-undef: "error" */
-/* eslint-env node */
-'use strict';
-var merge = require( 'deepmerge' ),
- wdioConf = require( './wdio.conf.js' );
-
-// Overwrite default settings
-exports.config = merge( wdioConf.config, {
- username: 'WikiAdmin',
- password: 'testpass',
- screenshotPath: '../log/',
- baseUrl: process.env.MW_SERVER + process.env.MW_SCRIPT_PATH,
-
- reporters: [ 'spec', 'junit' ],
- reporterOptions: {
- junit: {
- outputDir: '../log/'
- }
- }
-} );
diff --git a/www/wiki/tests/selenium/wdio.conf.js b/www/wiki/tests/selenium/wdio.conf.js
index 6b65034c..0930a0f1 100644
--- a/www/wiki/tests/selenium/wdio.conf.js
+++ b/www/wiki/tests/selenium/wdio.conf.js
@@ -1,20 +1,27 @@
-/* eslint-env node */
-/* eslint no-undef: "error" */
-/* eslint-disable no-console, comma-dangle */
'use strict';
const fs = require( 'fs' ),
path = require( 'path' );
+let logPath, password, username;
+
+// username and password will be used only if
+// MEDIAWIKI_USER or MEDIAWIKI_PASSWORD environment variables are not set
+if ( process.env.JENKINS_HOME ) {
+ logPath = '../log/';
+ password = 'testpass';
+ username = 'WikiAdmin';
+} else {
+ logPath = './log/';
+ password = 'vagrant';
+ username = 'Admin';
+}
+
function relPath( foo ) {
return path.resolve( __dirname, '../..', foo );
}
exports.config = {
-
- //
- // ======
- //
// ======
// Custom
// ======
@@ -24,10 +31,10 @@ exports.config = {
// Use if from tests with:
// browser.options.username
username: process.env.MEDIAWIKI_USER === undefined ?
- 'Admin' :
+ username :
process.env.MEDIAWIKI_USER,
password: process.env.MEDIAWIKI_PASSWORD === undefined ?
- 'vagrant' :
+ password :
process.env.MEDIAWIKI_PASSWORD,
//
// ======
@@ -50,11 +57,11 @@ exports.config = {
relPath( './tests/selenium/specs/**/*.js' ),
relPath( './extensions/*/tests/selenium/specs/**/*.js' ),
relPath( './extensions/VisualEditor/modules/ve-mw/tests/selenium/specs/**/*.js' ),
- relPath( './skins/*/tests/selenium/specs/**/*.js' ),
+ relPath( './skins/*/tests/selenium/specs/**/*.js' )
],
// Patterns to exclude.
exclude: [
- // 'path/to/excluded/files'
+ './extensions/CirrusSearch/tests/selenium/specs/**/*.js'
],
//
// ============
@@ -114,11 +121,20 @@ exports.config = {
// Enables colors for log output.
coloredLogs: true,
//
+ // Warns when a deprecated command is used
+ deprecationWarnings: true,
+ //
+ // If you only want to run your tests until a specific amount of tests have failed use
+ // bail (default is 0 - don't bail, run all tests).
+ bail: 0,
+ //
// Saves a screenshot to a given path if a command fails.
- screenshotPath: './log/',
+ screenshotPath: logPath,
//
- // Set a base URL in order to shorten url command calls. If your url parameter starts
- // with "/", then the base url gets prepended.
+ // Set a base URL in order to shorten url command calls. If your `url` parameter starts
+ // with `/`, the base url gets prepended, not including the path portion of your baseUrl.
+ // If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url
+ // gets prepended directly.
baseUrl: (
process.env.MW_SERVER === undefined ?
'http://127.0.0.1:8080' :
@@ -130,7 +146,7 @@ exports.config = {
),
//
// Default timeout for all waitFor* commands.
- waitforTimeout: 20000,
+ waitforTimeout: 10000,
//
// Default timeout in milliseconds for request
// if Selenium Grid doesn't send response
@@ -147,14 +163,14 @@ exports.config = {
// WebdriverRTC: https://github.com/webdriverio/webdriverrtc
// Browserevent: https://github.com/webdriverio/browserevent
// plugins: {
- // webdrivercss: {
- // screenshotRoot: 'my-shots',
- // failedComparisonsRoot: 'diffs',
- // misMatchTolerance: 0.05,
- // screenWidth: [320,480,640,1024]
- // },
- // webdriverrtc: {},
- // browserevent: {}
+ // webdrivercss: {
+ // screenshotRoot: 'my-shots',
+ // failedComparisonsRoot: 'diffs',
+ // misMatchTolerance: 0.05,
+ // screenWidth: [320,480,640,1024]
+ // },
+ // webdriverrtc: {},
+ // browserevent: {}
// },
//
// Test runner services
@@ -169,11 +185,16 @@ exports.config = {
// Make sure you have the wdio adapter package for the specific framework installed
// before running any tests.
framework: 'mocha',
-
+ //
// Test reporter for stdout.
// The only one supported by default is 'dot'
// see also: http://webdriver.io/guide/testrunner/reporters.html
- reporters: [ 'spec' ],
+ reporters: [ 'spec', 'junit' ],
+ reporterOptions: {
+ junit: {
+ outputDir: logPath
+ }
+ },
//
// Options to be passed to Mocha.
// See the full list at http://mochajs.org/
@@ -189,41 +210,65 @@ exports.config = {
// it and to build services around it. You can either apply a single function or an array of
// methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
// resolved to continue.
- //
- // Gets executed once before all workers get launched.
- // onPrepare: function ( config, capabilities ) {
- // }
- //
- // Gets executed before test execution begins. At this point you can access all global
- // variables, such as `browser`. It is the perfect place to define custom commands.
+ /**
+ * Gets executed once before all workers get launched.
+ * @param {Object} config wdio configuration object
+ * @param {Array.<Object>} capabilities list of capabilities details
+ */
+ // onPrepare: function (config, capabilities) {
+ // },
+ /**
+ * Gets executed just before initialising the webdriver session and test framework. It allows you
+ * to manipulate configurations depending on the capability or spec.
+ * @param {Object} config wdio configuration object
+ * @param {Array.<Object>} capabilities list of capabilities details
+ * @param {Array.<String>} specs List of spec file paths that are to be run
+ */
+ // beforeSession: function (config, capabilities, specs) {
+ // },
+ /**
+ * Gets executed before test execution begins. At this point you can access to all global
+ * variables like `browser`. It is the perfect place to define custom commands.
+ * @param {Array.<Object>} capabilities list of capabilities details
+ * @param {Array.<String>} specs List of spec file paths that are to be run
+ */
// before: function (capabilities, specs) {
// },
- //
- // Hook that gets executed before the suite starts
- // beforeSuite: function (suite) {
+ /**
+ * Runs before a WebdriverIO command gets executed.
+ * @param {String} commandName hook command name
+ * @param {Array} args arguments that command would receive
+ */
+ // beforeCommand: function (commandName, args) {
// },
- //
- // Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
- // beforeEach in Mocha)
- // beforeHook: function () {
+ /**
+ * Hook that gets executed before the suite starts
+ * @param {Object} suite suite details
+ */
+ // beforeSuite: function (suite) {
// },
- //
- // Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling
- // afterEach in Mocha)
- //
- // Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
+ /**
+ * Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
+ * @param {Object} test test details
+ */
// beforeTest: function (test) {
// },
- //
- // Runs before a WebdriverIO command gets executed.
- // beforeCommand: function (commandName, args) {
+ /**
+ * Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
+ * beforeEach in Mocha)
+ */
+ // beforeHook: function () {
// },
- //
- // Runs after a WebdriverIO command gets executed
- // afterCommand: function (commandName, args, result, error) {
+ /**
+ * Hook that gets executed _after_ a hook within the suite ends (e.g. runs after calling
+ * afterEach in Mocha)
+ */
+ // afterHook: function () {
// },
- //
- // Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
+ /**
+ * Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) ends.
+ * @param {Object} test test details
+ */
// from https://github.com/webdriverio/webdriverio/issues/269#issuecomment-306342170
afterTest: function ( test ) {
var filename, filePath;
@@ -238,19 +283,46 @@ exports.config = {
// save screenshot
browser.saveScreenshot( filePath );
console.log( '\n\tScreenshot location:', filePath, '\n' );
- },
+ }
//
- // Hook that gets executed after the suite has ended
+ /**
+ * Hook that gets executed after the suite has ended
+ * @param {Object} suite suite details
+ */
// afterSuite: function (suite) {
// },
- //
- // Gets executed after all tests are done. You still have access to all global variables from
- // the test.
+ /**
+ * Runs after a WebdriverIO command gets executed
+ * @param {String} commandName hook command name
+ * @param {Array} args arguments that command would receive
+ * @param {Number} result 0 - command success, 1 - command error
+ * @param {Object} error error object if any
+ */
+ // afterCommand: function (commandName, args, result, error) {
+ // },
+ /**
+ * Gets executed after all tests are done. You still have access to all global variables from
+ * the test.
+ * @param {Number} result 0 - test pass, 1 - test fail
+ * @param {Array.<Object>} capabilities list of capabilities details
+ * @param {Array.<String>} specs List of spec file paths that ran
+ */
// after: function (result, capabilities, specs) {
// },
- //
- // Gets executed after all workers got shut down and the process is about to exit. It is not
- // possible to defer the end of the process using a promise.
- // onComplete: function(exitCode) {
+ /**
+ * Gets executed right after terminating the webdriver session.
+ * @param {Object} config wdio configuration object
+ * @param {Array.<Object>} capabilities list of capabilities details
+ * @param {Array.<String>} specs List of spec file paths that ran
+ */
+ // afterSession: function (config, capabilities, specs) {
+ // },
+ /**
+ * Gets executed after all workers got shut down and the process is about to exit.
+ * @param {Object} exitCode 0 - success, 1 - fail
+ * @param {Object} config wdio configuration object
+ * @param {Array.<Object>} capabilities list of capabilities details
+ */
+ // onComplete: function(exitCode, config, capabilities) {
// }
};
diff --git a/www/wiki/tests/testHelpers.inc b/www/wiki/tests/testHelpers.inc
deleted file mode 100644
index d04e0fcb..00000000
--- a/www/wiki/tests/testHelpers.inc
+++ /dev/null
@@ -1,874 +0,0 @@
-<?php
-/**
- * Recording for passing/failing tests.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Testing
- */
-
-/**
- * Interface to record parser test results.
- *
- * The ITestRecorder is a very simple interface to record the result of
- * MediaWiki parser tests. One should call start() before running the
- * full parser tests and end() once all the tests have been finished.
- * After each test, you should use record() to keep track of your tests
- * results. Finally, report() is used to generate a summary of your
- * test run, one could dump it to the console for human consumption or
- * register the result in a database for tracking purposes.
- *
- * @since 1.22
- */
-interface ITestRecorder {
-
- /**
- * Called at beginning of the parser test run
- */
- public function start();
-
- /**
- * Called after each test
- * @param string $test
- * @param integer $subtest
- * @param bool $result
- */
- public function record( $test, $subtest, $result );
-
- /**
- * Called before finishing the test run
- */
- public function report();
-
- /**
- * Called at the end of the parser test run
- */
- public function end();
-
-}
-
-class TestRecorder implements ITestRecorder {
- public $parent;
- public $term;
-
- function __construct( $parent ) {
- $this->parent = $parent;
- $this->term = $parent->term;
- }
-
- function start() {
- $this->total = 0;
- $this->success = 0;
- }
-
- function record( $test, $subtest, $result ) {
- $this->total++;
- $this->success += ( $result ? 1 : 0 );
- }
-
- function end() {
- // dummy
- }
-
- function report() {
- if ( $this->total > 0 ) {
- $this->reportPercentage( $this->success, $this->total );
- } else {
- throw new MWException( "No tests found.\n" );
- }
- }
-
- function reportPercentage( $success, $total ) {
- $ratio = wfPercent( 100 * $success / $total );
- print $this->term->color( 1 ) . "Passed $success of $total tests ($ratio)... ";
-
- if ( $success == $total ) {
- print $this->term->color( 32 ) . "ALL TESTS PASSED!";
- } else {
- $failed = $total - $success;
- print $this->term->color( 31 ) . "$failed tests failed!";
- }
-
- print $this->term->reset() . "\n";
-
- return ( $success == $total );
- }
-}
-
-class DbTestPreviewer extends TestRecorder {
- protected $lb; // /< Database load balancer
- protected $db; // /< Database connection to the main DB
- protected $curRun; // /< run ID number for the current run
- protected $prevRun; // /< run ID number for the previous run, if any
- protected $results; // /< Result array
-
- /**
- * This should be called before the table prefix is changed
- * @param TestRecorder $parent
- */
- function __construct( $parent ) {
- parent::__construct( $parent );
-
- $this->lb = wfGetLBFactory()->newMainLB();
- // This connection will have the wiki's table prefix, not parsertest_
- $this->db = $this->lb->getConnection( DB_MASTER );
- }
-
- /**
- * Set up result recording; insert a record for the run with the date
- * and all that fun stuff
- */
- function start() {
- parent::start();
-
- if ( !$this->db->tableExists( 'testrun', __METHOD__ )
- || !$this->db->tableExists( 'testitem', __METHOD__ )
- ) {
- print "WARNING> `testrun` table not found in database.\n";
- $this->prevRun = false;
- } else {
- // We'll make comparisons against the previous run later...
- $this->prevRun = $this->db->selectField( 'testrun', 'MAX(tr_id)' );
- }
-
- $this->results = [];
- }
-
- function getName( $test, $subtest ) {
- if ( $subtest ) {
- return "$test subtest #$subtest";
- } else {
- return $test;
- }
- }
-
- function record( $test, $subtest, $result ) {
- parent::record( $test, $subtest, $result );
- $this->results[ $this->getName( $test, $subtest ) ] = $result;
- }
-
- function report() {
- if ( $this->prevRun ) {
- // f = fail, p = pass, n = nonexistent
- // codes show before then after
- $table = [
- 'fp' => 'previously failing test(s) now PASSING! :)',
- 'pn' => 'previously PASSING test(s) removed o_O',
- 'np' => 'new PASSING test(s) :)',
-
- 'pf' => 'previously passing test(s) now FAILING! :(',
- 'fn' => 'previously FAILING test(s) removed O_o',
- 'nf' => 'new FAILING test(s) :(',
- 'ff' => 'still FAILING test(s) :(',
- ];
-
- $prevResults = [];
-
- $res = $this->db->select( 'testitem', [ 'ti_name', 'ti_success' ],
- [ 'ti_run' => $this->prevRun ], __METHOD__ );
-
- foreach ( $res as $row ) {
- if ( !$this->parent->regex
- || preg_match( "/{$this->parent->regex}/i", $row->ti_name )
- ) {
- $prevResults[$row->ti_name] = $row->ti_success;
- }
- }
-
- $combined = array_keys( $this->results + $prevResults );
-
- # Determine breakdown by change type
- $breakdown = [];
- foreach ( $combined as $test ) {
- if ( !isset( $prevResults[$test] ) ) {
- $before = 'n';
- } elseif ( $prevResults[$test] == 1 ) {
- $before = 'p';
- } else /* if ( $prevResults[$test] == 0 )*/ {
- $before = 'f';
- }
-
- if ( !isset( $this->results[$test] ) ) {
- $after = 'n';
- } elseif ( $this->results[$test] == 1 ) {
- $after = 'p';
- } else /*if ( $this->results[$test] == 0 ) */ {
- $after = 'f';
- }
-
- $code = $before . $after;
-
- if ( isset( $table[$code] ) ) {
- $breakdown[$code][$test] = $this->getTestStatusInfo( $test, $after );
- }
- }
-
- # Write out results
- foreach ( $table as $code => $label ) {
- if ( !empty( $breakdown[$code] ) ) {
- $count = count( $breakdown[$code] );
- printf( "\n%4d %s\n", $count, $label );
-
- foreach ( $breakdown[$code] as $differing_test_name => $statusInfo ) {
- print " * $differing_test_name [$statusInfo]\n";
- }
- }
- }
- } else {
- print "No previous test runs to compare against.\n";
- }
-
- print "\n";
- parent::report();
- }
-
- /**
- * Returns a string giving information about when a test last had a status change.
- * Could help to track down when regressions were introduced, as distinct from tests
- * which have never passed (which are more change requests than regressions).
- * @param string $testname
- * @param string $after
- * @return string
- */
- private function getTestStatusInfo( $testname, $after ) {
- // If we're looking at a test that has just been removed, then say when it first appeared.
- if ( $after == 'n' ) {
- $changedRun = $this->db->selectField( 'testitem',
- 'MIN(ti_run)',
- [ 'ti_name' => $testname ],
- __METHOD__ );
- $appear = $this->db->selectRow( 'testrun',
- [ 'tr_date', 'tr_mw_version' ],
- [ 'tr_id' => $changedRun ],
- __METHOD__ );
-
- return "First recorded appearance: "
- . date( "d-M-Y H:i:s", strtotime( $appear->tr_date ) )
- . ", " . $appear->tr_mw_version;
- }
-
- // Otherwise, this test has previous recorded results.
- // See when this test last had a different result to what we're seeing now.
- $conds = [
- 'ti_name' => $testname,
- 'ti_success' => ( $after == 'f' ? "1" : "0" ) ];
-
- if ( $this->curRun ) {
- $conds[] = "ti_run != " . $this->db->addQuotes( $this->curRun );
- }
-
- $changedRun = $this->db->selectField( 'testitem', 'MAX(ti_run)', $conds, __METHOD__ );
-
- // If no record of ever having had a different result.
- if ( is_null( $changedRun ) ) {
- if ( $after == "f" ) {
- return "Has never passed";
- } else {
- return "Has never failed";
- }
- }
-
- // Otherwise, we're looking at a test whose status has changed.
- // (i.e. it used to work, but now doesn't; or used to fail, but is now fixed.)
- // In this situation, give as much info as we can as to when it changed status.
- $pre = $this->db->selectRow( 'testrun',
- [ 'tr_date', 'tr_mw_version' ],
- [ 'tr_id' => $changedRun ],
- __METHOD__ );
- $post = $this->db->selectRow( 'testrun',
- [ 'tr_date', 'tr_mw_version' ],
- [ "tr_id > " . $this->db->addQuotes( $changedRun ) ],
- __METHOD__,
- [ "LIMIT" => 1, "ORDER BY" => 'tr_id' ]
- );
-
- if ( $post ) {
- $postDate = date( "d-M-Y H:i:s", strtotime( $post->tr_date ) ) . ", {$post->tr_mw_version}";
- } else {
- $postDate = 'now';
- }
-
- return ( $after == "f" ? "Introduced" : "Fixed" ) . " between "
- . date( "d-M-Y H:i:s", strtotime( $pre->tr_date ) ) . ", " . $pre->tr_mw_version
- . " and $postDate";
- }
-
- /**
- * Close the DB connection
- */
- function end() {
- $this->lb->closeAll();
- parent::end();
- }
-}
-
-class DbTestRecorder extends DbTestPreviewer {
- public $version;
-
- /**
- * Set up result recording; insert a record for the run with the date
- * and all that fun stuff
- */
- function start() {
- $this->db->begin( __METHOD__ );
-
- if ( !$this->db->tableExists( 'testrun' )
- || !$this->db->tableExists( 'testitem' )
- ) {
- print "WARNING> `testrun` table not found in database. Trying to create table.\n";
- $this->db->sourceFile( $this->db->patchPath( 'patch-testrun.sql' ) );
- echo "OK, resuming.\n";
- }
-
- parent::start();
-
- $this->db->insert( 'testrun',
- [
- 'tr_date' => $this->db->timestamp(),
- 'tr_mw_version' => $this->version,
- 'tr_php_version' => PHP_VERSION,
- 'tr_db_version' => $this->db->getServerVersion(),
- 'tr_uname' => php_uname()
- ],
- __METHOD__ );
- if ( $this->db->getType() === 'postgres' ) {
- $this->curRun = $this->db->currentSequenceValue( 'testrun_id_seq' );
- } else {
- $this->curRun = $this->db->insertId();
- }
- }
-
- /**
- * Record an individual test item's success or failure to the db
- *
- * @param string $test
- * @param bool $result
- */
- function record( $test, $subtest, $result ) {
- parent::record( $test, $subtest, $result );
-
- $this->db->insert( 'testitem',
- [
- 'ti_run' => $this->curRun,
- 'ti_name' => $this->getName( $test, $subtest ),
- 'ti_success' => $result ? 1 : 0,
- ],
- __METHOD__ );
- }
-
- /**
- * Commit transaction and clean up for result recording
- */
- function end() {
- $this->db->commit( __METHOD__ );
- parent::end();
- }
-}
-
-class TestFileIterator implements Iterator {
- private $file;
- private $fh;
- /**
- * @var ParserTest|MediaWikiParserTest An instance of ParserTest (parserTests.php)
- * or MediaWikiParserTest (phpunit)
- */
- private $parserTest;
- private $index = 0;
- private $test;
- private $section = null;
- /** String|null: current test section being analyzed */
- private $sectionData = [];
- private $lineNum;
- private $eof;
- # Create a fake parser tests which never run anything unless
- # asked to do so. This will avoid running hooks for a disabled test
- private $delayedParserTest;
- private $nextSubTest = 0;
-
- function __construct( $file, $parserTest ) {
- $this->file = $file;
- $this->fh = fopen( $this->file, "rt" );
-
- if ( !$this->fh ) {
- throw new MWException( "Couldn't open file '$file'\n" );
- }
-
- $this->parserTest = $parserTest;
- $this->delayedParserTest = new DelayedParserTest();
-
- $this->lineNum = $this->index = 0;
- }
-
- function rewind() {
- if ( fseek( $this->fh, 0 ) ) {
- throw new MWException( "Couldn't fseek to the start of '$this->file'\n" );
- }
-
- $this->index = -1;
- $this->lineNum = 0;
- $this->eof = false;
- $this->next();
-
- return true;
- }
-
- function current() {
- return $this->test;
- }
-
- function key() {
- return $this->index;
- }
-
- function next() {
- if ( $this->readNextTest() ) {
- $this->index++;
- return true;
- } else {
- $this->eof = true;
- }
- }
-
- function valid() {
- return $this->eof != true;
- }
-
- function setupCurrentTest() {
- // "input" and "result" are old section names allowed
- // for backwards-compatibility.
- $input = $this->checkSection( [ 'wikitext', 'input' ], false );
- $result = $this->checkSection( [ 'html/php', 'html/*', 'html', 'result' ], false );
- // some tests have "with tidy" and "without tidy" variants
- $tidy = $this->checkSection( [ 'html/php+tidy', 'html+tidy' ], false );
- if ( $tidy != false ) {
- if ( $this->nextSubTest == 0 ) {
- if ( $result != false ) {
- $this->nextSubTest = 1; // rerun non-tidy variant later
- }
- $result = $tidy;
- } else {
- $this->nextSubTest = 0; // go on to next test after this
- $tidy = false;
- }
- }
-
- if ( !isset( $this->sectionData['options'] ) ) {
- $this->sectionData['options'] = '';
- }
-
- if ( !isset( $this->sectionData['config'] ) ) {
- $this->sectionData['config'] = '';
- }
-
- $isDisabled = preg_match( '/\\bdisabled\\b/i', $this->sectionData['options'] ) &&
- !$this->parserTest->runDisabled;
- $isParsoidOnly = preg_match( '/\\bparsoid\\b/i', $this->sectionData['options'] ) &&
- $result == 'html' &&
- !$this->parserTest->runParsoid;
- $isFiltered = !preg_match( "/" . $this->parserTest->regex . "/i", $this->sectionData['test'] );
- if ( $input == false || $result == false || $isDisabled || $isParsoidOnly || $isFiltered ) {
- # disabled test
- return false;
- }
-
- # We are really going to run the test, run pending hooks and hooks function
- wfDebug( __METHOD__ . " unleashing delayed test for: {$this->sectionData['test']}" );
- $hooksResult = $this->delayedParserTest->unleash( $this->parserTest );
- if ( !$hooksResult ) {
- # Some hook reported an issue. Abort.
- throw new MWException( "Problem running requested parser hook from the test file" );
- }
-
- $this->test = [
- 'test' => ParserTest::chomp( $this->sectionData['test'] ),
- 'subtest' => $this->nextSubTest,
- 'input' => ParserTest::chomp( $this->sectionData[$input] ),
- 'result' => ParserTest::chomp( $this->sectionData[$result] ),
- 'options' => ParserTest::chomp( $this->sectionData['options'] ),
- 'config' => ParserTest::chomp( $this->sectionData['config'] ),
- ];
- if ( $tidy != false ) {
- $this->test['options'] .= " tidy";
- }
- return true;
- }
-
- function readNextTest() {
- # Run additional subtests of previous test
- while ( $this->nextSubTest > 0 ) {
- if ( $this->setupCurrentTest() ) {
- return true;
- }
- }
-
- $this->clearSection();
- # Reset hooks for the delayed test object
- $this->delayedParserTest->reset();
-
- while ( false !== ( $line = fgets( $this->fh ) ) ) {
- $this->lineNum++;
- $matches = [];
-
- if ( preg_match( '/^!!\s*(\S+)/', $line, $matches ) ) {
- $this->section = strtolower( $matches[1] );
-
- if ( $this->section == 'endarticle' ) {
- $this->checkSection( 'text' );
- $this->checkSection( 'article' );
-
- $this->parserTest->addArticle(
- ParserTest::chomp( $this->sectionData['article'] ),
- $this->sectionData['text'], $this->lineNum );
-
- $this->clearSection();
-
- continue;
- }
-
- if ( $this->section == 'endhooks' ) {
- $this->checkSection( 'hooks' );
-
- foreach ( explode( "\n", $this->sectionData['hooks'] ) as $line ) {
- $line = trim( $line );
-
- if ( $line ) {
- $this->delayedParserTest->requireHook( $line );
- }
- }
-
- $this->clearSection();
-
- continue;
- }
-
- if ( $this->section == 'endfunctionhooks' ) {
- $this->checkSection( 'functionhooks' );
-
- foreach ( explode( "\n", $this->sectionData['functionhooks'] ) as $line ) {
- $line = trim( $line );
-
- if ( $line ) {
- $this->delayedParserTest->requireFunctionHook( $line );
- }
- }
-
- $this->clearSection();
-
- continue;
- }
-
- if ( $this->section == 'endtransparenthooks' ) {
- $this->checkSection( 'transparenthooks' );
-
- foreach ( explode( "\n", $this->sectionData['transparenthooks'] ) as $line ) {
- $line = trim( $line );
-
- if ( $line ) {
- $this->delayedParserTest->requireTransparentHook( $line );
- }
- }
-
- $this->clearSection();
-
- continue;
- }
-
- if ( $this->section == 'end' ) {
- $this->checkSection( 'test' );
- do {
- if ( $this->setupCurrentTest() ) {
- return true;
- }
- } while ( $this->nextSubTest > 0 );
- # go on to next test (since this was disabled)
- $this->clearSection();
- $this->delayedParserTest->reset();
- continue;
- }
-
- if ( isset( $this->sectionData[$this->section] ) ) {
- throw new MWException( "duplicate section '$this->section' "
- . "at line {$this->lineNum} of $this->file\n" );
- }
-
- $this->sectionData[$this->section] = '';
-
- continue;
- }
-
- if ( $this->section ) {
- $this->sectionData[$this->section] .= $line;
- }
- }
-
- return false;
- }
-
- /**
- * Clear section name and its data
- */
- private function clearSection() {
- $this->sectionData = [];
- $this->section = null;
-
- }
-
- /**
- * Verify the current section data has some value for the given token
- * name(s) (first parameter).
- * Throw an exception if it is not set, referencing current section
- * and adding the current file name and line number
- *
- * @param string|array $tokens Expected token(s) that should have been
- * mentioned before closing this section
- * @param bool $fatal True iff an exception should be thrown if
- * the section is not found.
- * @return bool|string
- * @throws MWException
- */
- private function checkSection( $tokens, $fatal = true ) {
- if ( is_null( $this->section ) ) {
- throw new MWException( __METHOD__ . " can not verify a null section!\n" );
- }
- if ( !is_array( $tokens ) ) {
- $tokens = [ $tokens ];
- }
- if ( count( $tokens ) == 0 ) {
- throw new MWException( __METHOD__ . " can not verify zero sections!\n" );
- }
-
- $data = $this->sectionData;
- $tokens = array_filter( $tokens, function ( $token ) use ( $data ) {
- return isset( $data[$token] );
- } );
-
- if ( count( $tokens ) == 0 ) {
- if ( !$fatal ) {
- return false;
- }
- throw new MWException( sprintf(
- "'%s' without '%s' at line %s of %s\n",
- $this->section,
- implode( ',', $tokens ),
- $this->lineNum,
- $this->file
- ) );
- }
- if ( count( $tokens ) > 1 ) {
- throw new MWException( sprintf(
- "'%s' with unexpected tokens '%s' at line %s of %s\n",
- $this->section,
- implode( ',', $tokens ),
- $this->lineNum,
- $this->file
- ) );
- }
-
- return array_values( $tokens )[0];
- }
-}
-
-/**
- * An iterator for use as a phpunit data provider. Provides the test arguments
- * in the order expected by NewParserTest::testParserTest().
- */
-class TestFileDataProvider extends TestFileIterator {
- function current() {
- $test = parent::current();
- if ( $test ) {
- return [
- $test['test'],
- $test['input'],
- $test['result'],
- $test['options'],
- $test['config'],
- ];
- } else {
- return $test;
- }
- }
-}
-
-/**
- * A class to delay execution of a parser test hooks.
- */
-class DelayedParserTest {
-
- /** Initialized on construction */
- private $hooks;
- private $fnHooks;
- private $transparentHooks;
-
- public function __construct() {
- $this->reset();
- }
-
- /**
- * Init/reset or forgot about the current delayed test.
- * Call to this will erase any hooks function that were pending.
- */
- public function reset() {
- $this->hooks = [];
- $this->fnHooks = [];
- $this->transparentHooks = [];
- }
-
- /**
- * Called whenever we actually want to run the hook.
- * Should be the case if we found the parserTest is not disabled
- * @param ParserTest|NewParserTest $parserTest
- * @return bool
- * @throws MWException
- */
- public function unleash( &$parserTest ) {
- if ( !( $parserTest instanceof ParserTest || $parserTest instanceof NewParserTest ) ) {
- throw new MWException( __METHOD__ . " must be passed an instance of ParserTest or "
- . "NewParserTest classes\n" );
- }
-
- # Trigger delayed hooks. Any failure will make us abort
- foreach ( $this->hooks as $hook ) {
- $ret = $parserTest->requireHook( $hook );
- if ( !$ret ) {
- return false;
- }
- }
-
- # Trigger delayed function hooks. Any failure will make us abort
- foreach ( $this->fnHooks as $fnHook ) {
- $ret = $parserTest->requireFunctionHook( $fnHook );
- if ( !$ret ) {
- return false;
- }
- }
-
- # Trigger delayed transparent hooks. Any failure will make us abort
- foreach ( $this->transparentHooks as $hook ) {
- $ret = $parserTest->requireTransparentHook( $hook );
- if ( !$ret ) {
- return false;
- }
- }
-
- # Delayed execution was successful.
- return true;
- }
-
- /**
- * Similar to ParserTest object but does not run anything
- * Use unleash() to really execute the hook
- * @param string $hook
- */
- public function requireHook( $hook ) {
- $this->hooks[] = $hook;
- }
-
- /**
- * Similar to ParserTest object but does not run anything
- * Use unleash() to really execute the hook function
- * @param string $fnHook
- */
- public function requireFunctionHook( $fnHook ) {
- $this->fnHooks[] = $fnHook;
- }
-
- /**
- * Similar to ParserTest object but does not run anything
- * Use unleash() to really execute the hook function
- * @param string $hook
- */
- public function requireTransparentHook( $hook ) {
- $this->transparentHooks[] = $hook;
- }
-
-}
-
-/**
- * Initialize and detect the DjVu files support
- */
-class DjVuSupport {
-
- /**
- * Initialises DjVu tools global with default values
- */
- public function __construct() {
- global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML, $wgFileExtensions, $wgDjvuTxt;
-
- $wgDjvuRenderer = $wgDjvuRenderer ? $wgDjvuRenderer : '/usr/bin/ddjvu';
- $wgDjvuDump = $wgDjvuDump ? $wgDjvuDump : '/usr/bin/djvudump';
- $wgDjvuToXML = $wgDjvuToXML ? $wgDjvuToXML : '/usr/bin/djvutoxml';
- $wgDjvuTxt = $wgDjvuTxt ? $wgDjvuTxt : '/usr/bin/djvutxt';
-
- if ( !in_array( 'djvu', $wgFileExtensions ) ) {
- $wgFileExtensions[] = 'djvu';
- }
- }
-
- /**
- * Returns true if the DjVu tools are usable
- *
- * @return bool
- */
- public function isEnabled() {
- global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML, $wgDjvuTxt;
-
- return is_executable( $wgDjvuRenderer )
- && is_executable( $wgDjvuDump )
- && is_executable( $wgDjvuToXML )
- && is_executable( $wgDjvuTxt );
- }
-}
-
-/**
- * Initialize and detect the tidy support
- */
-class TidySupport {
- private $internalTidy;
- private $externalTidy;
-
- /**
- * Determine if there is a usable tidy.
- */
- public function __construct() {
- global $wgTidyBin;
-
- $this->internalTidy = extension_loaded( 'tidy' ) &&
- class_exists( 'tidy' ) && !wfIsHHVM();
-
- $this->externalTidy = is_executable( $wgTidyBin ) ||
- Installer::locateExecutableInDefaultPaths( [ $wgTidyBin ] )
- !== false;
- }
-
- /**
- * Returns true if we should use internal tidy.
- *
- * @return bool
- */
- public function isInternal() {
- return $this->internalTidy;
- }
-
- /**
- * Returns true if tidy is usable
- *
- * @return bool
- */
- public function isEnabled() {
- return $this->internalTidy || $this->externalTidy;
- }
-}