Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4088969
ApiParseTest.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
29 KB
Referenced Files
None
Subscribers
None
ApiParseTest.php
View Options
<?php
/**
* ApiParse check functions
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace
MediaWiki\Tests\Api
;
use
MediaWiki\Api\ApiUsageException
;
use
MediaWiki\Context\RequestContext
;
use
MediaWiki\MainConfigNames
;
use
MediaWiki\Revision\RevisionRecord
;
use
MediaWiki\Tests\Unit\DummyServicesTrait
;
use
MediaWiki\Title\TitleValue
;
use
MockPoolCounterFailing
;
use
SkinFactory
;
use
SkinFallback
;
/**
* @group API
* @group Database
* @group medium
*
* @covers MediaWiki\Api\ApiParse
*/
class
ApiParseTest
extends
ApiTestCase
{
use
DummyServicesTrait
;
/** @var int */
protected
static
$pageId
;
/** @var int[] */
protected
static
$revIds
=
[];
public
function
addDBDataOnce
()
{
$page
=
$this
->
getServiceContainer
()->
getWikiPageFactory
()
->
newFromLinkTarget
(
new
TitleValue
(
NS_MAIN
,
__CLASS__
)
);
$status
=
$this
->
editPage
(
$page
,
'Test for revdel'
);
self
::
$pageId
=
$status
->
getNewRevision
()->
getPageId
();
self
::
$revIds
[
'revdel'
]
=
$status
->
getNewRevision
()->
getId
();
$status
=
$this
->
editPage
(
$page
,
'Test for suppressed'
);
self
::
$revIds
[
'suppressed'
]
=
$status
->
getNewRevision
()->
getId
();
$status
=
$this
->
editPage
(
$page
,
'Test for oldid'
);
self
::
$revIds
[
'oldid'
]
=
$status
->
getNewRevision
()->
getId
();
$status
=
$this
->
editPage
(
$page
,
'Test for latest'
);
self
::
$revIds
[
'latest'
]
=
$status
->
getNewRevision
()->
getId
();
// Set a user for modifying the visibility, this is needed because
// setVisibility generates a log, which cannot be an anonymous user actor
// when temporary accounts are enabled.
RequestContext
::
getMain
()->
setUser
(
$this
->
getTestUser
()->
getUser
()
);
$this
->
revisionDelete
(
self
::
$revIds
[
'revdel'
]
);
$this
->
revisionDelete
(
self
::
$revIds
[
'suppressed'
],
[
RevisionRecord
::
DELETED_TEXT
=>
1
,
RevisionRecord
::
DELETED_RESTRICTED
=>
1
]
);
}
/**
* 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 $expected 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 $expected 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
,
'assertMatchesRegularExpression'
]
);
}
private
function
doAssertParsedTo
(
$expected
,
array
$res
,
$warnings
,
callable
$callback
)
{
$html
=
$res
[
0
][
'parse'
][
'text'
];
$expectedStart
=
'<div class="mw-content-ltr mw-parser-output" lang="en" dir="ltr"'
;
$this
->
assertSame
(
$expectedStart
,
substr
(
$html
,
0
,
strlen
(
$expectedStart
)
)
);
$html
=
substr
(
$html
,
strlen
(
$expectedStart
)
);
# Parsoid-based transformations may add ID and data-mw-parsoid-version
# attributes to the wrapper div
$possibleIdAttr
=
'/^( (id|data-mw[^=]*)="[^"]+")*>/'
;
$html
=
preg_replace
(
$possibleIdAttr
,
''
,
$html
);
$possibleParserCache
=
'/
\n
<!-- Saved in (?>parser cache|RevisionOutputCache) (?>.*?
\n
-->)
\n
/'
;
$html
=
preg_replace
(
$possibleParserCache
,
''
,
$html
);
if
(
$res
[
1
]->
getBool
(
'disablelimitreport'
)
)
{
$expectedEnd
=
"</div>"
;
$this
->
assertSame
(
$expectedEnd
,
substr
(
$html
,
-
strlen
(
$expectedEnd
)
)
);
$unexpectedEnd
=
'#<!--
\n
NewPP limit report|'
.
'<!--
\n
Transclusion expansion time report#'
;
$this
->
assertDoesNotMatchRegularExpression
(
$unexpectedEnd
,
$html
);
$html
=
substr
(
$html
,
0
,
strlen
(
$html
)
-
strlen
(
$expectedEnd
)
);
}
else
{
$expectedEnd
=
'#
\n
<!--
\n
NewPP limit report
\n
(?>.+?
\n
-->)
\n
'
.
'<!--
\n
Transclusion expansion time report
\(
%,ms,calls,template
\)\n
(?>.*?
\n
-->)
\n
'
.
'</div>$#s'
;
$this
->
assertMatchesRegularExpression
(
$expectedEnd
,
$html
);
$html
=
preg_replace
(
$expectedEnd
,
''
,
$html
);
}
$callback
(
$expected
,
$html
);
if
(
$warnings
===
null
)
{
$this
->
assertCount
(
1
,
$res
[
0
]
);
}
else
{
$this
->
assertCount
(
2
,
$res
[
0
]
);
$this
->
assertSame
(
[
'warnings'
=>
$warnings
],
$res
[
0
][
'warnings'
][
'parse'
]
);
}
}
/**
* Set up an interwiki entry for testing.
*/
protected
function
setupInterwiki
()
{
$this
->
getDb
()->
newInsertQueryBuilder
()
->
insertInto
(
'interwiki'
)
->
ignore
()
->
row
(
[
'iw_prefix'
=>
'madeuplanguage'
,
'iw_url'
=>
"https://example.com/wiki/$1"
,
'iw_api'
=>
''
,
'iw_wikiid'
=>
''
,
'iw_local'
=>
false
,
]
)
// This deliberately conflicts with the Talk namespace
// (T204792/T363538)
->
row
(
[
'iw_prefix'
=>
'talk'
,
'iw_url'
=>
"https://talk.example.com/wiki/$1"
,
'iw_api'
=>
''
,
'iw_wikiid'
=>
''
,
'iw_local'
=>
false
,
]
)
->
caller
(
__METHOD__
)
->
execute
();
$this
->
overrideConfigValue
(
MainConfigNames
::
ExtraInterlanguageLinkPrefixes
,
[
'madeuplanguage'
,
'talk'
]
);
}
/**
* Set up a skin for testing.
*
* @todo Should this code be in MediaWikiIntegrationTestCase or something?
*/
protected
function
setupSkin
()
{
$factory
=
new
SkinFactory
(
$this
->
getDummyObjectFactory
(),
[]
);
$factory
->
register
(
'testing'
,
'Testing'
,
function
()
{
$skin
=
$this
->
getMockBuilder
(
SkinFallback
::
class
)
->
onlyMethods
(
[
'getDefaultModules'
]
)
->
getMock
();
$skin
->
expects
(
$this
->
once
()
)->
method
(
'getDefaultModules'
)
->
willReturn
(
[
'styles'
=>
[
'core'
=>
[
'quux.styles'
]
],
'core'
=>
[
'foo'
,
'bar'
],
'content'
=>
[
'baz'
]
]
);
return
$skin
;
}
);
$this
->
setService
(
'SkinFactory'
,
$factory
);
}
public
function
testParseByName
()
{
$res
=
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'page'
=>
__CLASS__
,
]
);
$this
->
assertParsedTo
(
"<p>Test for latest
\n
</p>"
,
$res
);
$res
=
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'page'
=>
__CLASS__
,
'disablelimitreport'
=>
1
,
]
);
$this
->
assertParsedTo
(
"<p>Test for latest
\n
</p>"
,
$res
);
}
public
function
testParseById
()
{
$res
=
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'pageid'
=>
self
::
$pageId
,
]
);
$this
->
assertParsedTo
(
"<p>Test for latest
\n
</p>"
,
$res
);
}
public
function
testParseByOldId
()
{
$res
=
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'oldid'
=>
self
::
$revIds
[
'oldid'
],
]
);
$this
->
assertParsedTo
(
"<p>Test for oldid
\n
</p>"
,
$res
);
$this
->
assertArrayNotHasKey
(
'textdeleted'
,
$res
[
0
][
'parse'
]
);
$this
->
assertArrayNotHasKey
(
'textsuppressed'
,
$res
[
0
][
'parse'
]
);
}
public
function
testRevDel
()
{
$res
=
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'oldid'
=>
self
::
$revIds
[
'revdel'
],
]
);
$this
->
assertParsedTo
(
"<p>Test for revdel
\n
</p>"
,
$res
);
$this
->
assertArrayHasKey
(
'textdeleted'
,
$res
[
0
][
'parse'
]
);
$this
->
assertArrayNotHasKey
(
'textsuppressed'
,
$res
[
0
][
'parse'
]
);
}
public
function
testRevDelNoPermission
()
{
$this
->
expectApiErrorCode
(
'permissiondenied'
);
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'oldid'
=>
self
::
$revIds
[
'revdel'
],
],
null
,
null
,
static
::
getTestUser
()->
getAuthority
()
);
}
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'
,
'page'
=>
'DoesNotExist'
,
]
);
$this
->
fail
(
"API did not return an error when parsing a nonexistent page"
);
}
catch
(
ApiUsageException
$ex
)
{
$this
->
assertApiErrorCode
(
'missingtitle'
,
$ex
);
}
}
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\n
Content 1
\n\n
== Section 2 ==
\n\n
Content 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
->
expectApiErrorCode
(
'invalidsection'
);
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'section'
=>
'T-new'
,
]
);
}
public
function
testSectionNoContent
()
{
$name
=
ucfirst
(
__FUNCTION__
);
$status
=
$this
->
editPage
(
$name
,
"Intro
\n\n
== Section 1 ==
\n\n
Content 1
\n\n
== Section 2 ==
\n\n
Content 2"
);
$this
->
expectApiErrorCode
(
'missingcontent-pageid'
);
$this
->
getDb
()->
newDeleteQueryBuilder
()
->
deleteFrom
(
'revision'
)
->
where
(
[
'rev_id'
=>
$status
->
getNewRevision
()->
getId
()
]
)
->
caller
(
__METHOD__
)
->
execute
();
// Ignore warning from WikiPage::getContentModel
@
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'page'
=>
$name
,
'section'
=>
1
,
]
);
}
public
function
testNewSectionWithPage
()
{
$this
->
expectApiErrorCode
(
'invalidparammix'
);
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'page'
=>
__CLASS__
,
'section'
=>
'new'
,
]
);
}
public
function
testNonexistentOldId
()
{
$this
->
expectApiErrorCode
(
'nosuchrevid'
);
$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
->
assertMatchesRegularExpression
(
"/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]]"
)
->
getNewRevision
()->
getPageId
();
$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
testNonRedirectOk
()
{
$name
=
ucfirst
(
__FUNCTION__
);
$this
->
editPage
(
$name
,
"Some ''text''"
);
$res
=
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'page'
=>
$name
,
'redirects'
=>
true
,
]
);
$this
->
assertParsedTo
(
"<p>Some <i>text</i>
\n
</p>"
,
$res
);
}
public
function
testNonRedirectByIdOk
()
{
$name
=
ucfirst
(
__FUNCTION__
);
$id
=
$this
->
editPage
(
$name
,
"Some ''text''"
)->
getNewRevision
()->
getPageId
();
$res
=
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'pageid'
=>
$id
,
'redirects'
=>
true
,
]
);
$this
->
assertParsedTo
(
"<p>Some <i>text</i>
\n
</p>"
,
$res
);
}
public
function
testInvalidTitle
()
{
$this
->
expectApiErrorCode
(
'invalidtitle'
);
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'title'
=>
'|'
,
]
);
}
public
function
testTitleWithNonexistentRevId
()
{
$this
->
expectApiErrorCode
(
'nosuchrevid'
);
$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
->
expectApiErrorCode
(
'parseerror'
);
$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\n
Content
\n\n
== Section 2 ==
\n\n
More 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
]
);
}
/** @dataProvider providerTestParsoid */
public
function
testParsoid
(
$parsoid
,
$existing
,
$expected
)
{
# For simplicity, ensure that [[Foo]] isn't a redlink.
$this
->
editPage
(
"Foo"
,
__FUNCTION__
);
$res
=
$this
->
doApiRequest
(
[
# check that we're using the contents of 'text' not the contents of
# [[<title>]] by using pre-existing title __CLASS__ sometimes
'title'
=>
$existing
?
__CLASS__
:
'Bar'
,
'action'
=>
'parse'
,
'text'
=>
"[[Foo]]"
,
'contentmodel'
=>
'wikitext'
,
'parsoid'
=>
$parsoid
?:
null
,
'disablelimitreport'
=>
true
,
]
);
$this
->
assertParsedToRegexp
(
$expected
,
$res
);
}
public
static
function
providerTestParsoid
()
{
// Legacy parses, with and without pre-existing content.
$expected
=
'!^<p><a href="[^"]*" title="Foo">Foo</a>
\n
</p>$!'
;
yield
[
false
,
false
,
$expected
];
yield
[
false
,
true
,
$expected
];
// Parsoid parses, with and without pre-existing content.
$expected
=
'!^<section[^>]*><p[^>]*><a rel="mw:WikiLink" href="[^"]*Foo" title="Foo"[^>]*>Foo</a></p></section>!'
;
yield
[
true
,
false
,
$expected
];
yield
[
true
,
true
,
$expected
];
}
/** @dataProvider providerTestParsoid */
public
function
testUseArticle
(
$parsoid
,
$existing
,
$expected
)
{
# For simplicity, ensure that [[Foo]] isn't a redlink.
$this
->
editPage
(
"Foo"
,
__FUNCTION__
);
# Use an ArticleParserOptions hook to set the useParsoid option
$this
->
setTemporaryHook
(
'ArticleParserOptions'
,
static
function
(
$unused
,
$po
)
use
(
$parsoid
)
{
if
(
$parsoid
)
{
$po
->
setUseParsoid
();
}
}
);
$res
=
$this
->
doApiRequest
(
[
# check that we're using the contents of 'text' not the contents of
# [[<title>]] by using pre-existing title __CLASS__ sometimes
'title'
=>
$existing
?
__CLASS__
:
'Bar'
,
'action'
=>
'parse'
,
'text'
=>
"[[Foo]]"
,
'contentmodel'
=>
'wikitext'
,
'usearticle'
=>
true
,
# Note that we're not passing the 'parsoid' parameter here.
'disablelimitreport'
=>
true
,
]
);
$this
->
assertParsedToRegexp
(
$expected
,
$res
);
}
public
function
testHeadHtml
()
{
$res
=
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'page'
=>
__CLASS__
,
'prop'
=>
'headhtml'
,
]
);
// Just do a rough check
$this
->
assertMatchesRegularExpression
(
'#<!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
->
assertMatchesRegularExpression
(
"#Category.*Category:$name.*$name#"
,
$res
[
0
][
'parse'
][
'categorieshtml'
]
);
$this
->
assertArrayNotHasKey
(
'warnings'
,
$res
[
0
]
);
}
public
function
testEffectiveLangLinks
()
{
$hookRan
=
false
;
$this
->
setTemporaryHook
(
'LanguageLinks'
,
static
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
->
setTemporaryHook
(
'ParserAfterParse'
,
static
function
(
$parser
)
{
$parserOutput
=
$parser
->
getOutput
();
$parserOutput
->
addLanguageLink
(
'talk:Page'
);
// T363538
}
);
$res
=
$this
->
doApiRequest
(
array_merge
(
[
'action'
=>
'parse'
,
'title'
=>
'Omelette'
,
'text'
=>
'[[madeuplanguage:Omelette]]'
,
'prop'
=>
'langlinks'
,
],
$arr
)
);
$langLinks
=
$res
[
0
][
'parse'
][
'langlinks'
];
$this
->
assertCount
(
2
,
$langLinks
);
$this
->
assertSame
(
'madeuplanguage'
,
$langLinks
[
0
][
'lang'
]
);
$this
->
assertSame
(
'Omelette'
,
$langLinks
[
0
][
'title'
]
);
$this
->
assertSame
(
'https://example.com/wiki/Omelette'
,
$langLinks
[
0
][
'url'
]
);
$this
->
assertSame
(
'talk'
,
$langLinks
[
1
][
'lang'
]
);
$this
->
assertSame
(
'Page'
,
$langLinks
[
1
][
'title'
]
);
$this
->
assertSame
(
'https://talk.example.com/wiki/Page'
,
$langLinks
[
1
][
'url'
]
);
$this
->
assertArrayNotHasKey
(
'warnings'
,
$res
[
0
]
);
}
public
function
testLangLinks
()
{
$this
->
setupInterwiki
();
$this
->
doTestLangLinks
();
}
public
function
testLangLinksWithSkin
()
{
$this
->
setupInterwiki
();
$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'
,
static
function
(
$parser
)
{
$parserOutput
=
$parser
->
getOutput
();
$parserOutput
->
addModules
(
[
'foo'
,
'bar'
]
);
$parserOutput
->
addModuleStyles
(
[
'aaa'
,
'zzz'
]
);
$parserOutput
->
setJsConfigVar
(
'x'
,
'y'
);
$parserOutput
->
setJsConfigVar
(
'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
(
[],
$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
(
[
'quux.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'
=>
'<div class="mw-parser-output">aaa</div>'
,
'b'
=>
'<div class="mw-parser-output">BBB!</div>'
,
],
$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
->
assertIsArray
(
$res
[
0
][
'parse'
][
'limitreportdata'
]
);
$this
->
assertIsString
(
$res
[
0
][
'parse'
][
'limitreporthtml'
]
);
$this
->
assertArrayNotHasKey
(
'warnings'
,
$res
[
0
]
);
}
public
function
testParseTreeNonWikitext
()
{
$this
->
expectApiErrorCode
(
'notwikitext'
);
$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'
,
]
);
$this
->
assertEquals
(
'<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
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
]
);
}
public
function
testConcurrentLimitPageParse
()
{
$this
->
overrideConfigValue
(
MainConfigNames
::
PoolCounterConf
,
[
'ApiParser'
=>
[
'class'
=>
MockPoolCounterFailing
::
class
,
]
]
);
try
{
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'page'
=>
__CLASS__
,
]
);
$this
->
fail
(
"API did not return an error when concurrency exceeded"
);
}
catch
(
ApiUsageException
$ex
)
{
$this
->
assertApiErrorCode
(
'concurrency-limit'
,
$ex
);
}
}
public
function
testConcurrentLimitContentParse
()
{
$this
->
overrideConfigValue
(
MainConfigNames
::
PoolCounterConf
,
[
'ApiParser'
=>
[
'class'
=>
MockPoolCounterFailing
::
class
,
]
]
);
try
{
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'oldid'
=>
self
::
$revIds
[
'revdel'
],
]
);
$this
->
fail
(
"API did not return an error when concurrency exceeded"
);
}
catch
(
ApiUsageException
$ex
)
{
$this
->
assertApiErrorCode
(
'concurrency-limit'
,
$ex
);
}
}
public
function
testDisplayTitle
()
{
$res
=
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'title'
=>
'Art©'
,
'text'
=>
'{{DISPLAYTITLE:art©}}foo'
,
'prop'
=>
'displaytitle'
,
]
);
$this
->
assertSame
(
'art&copy'
,
$res
[
0
][
'parse'
][
'displaytitle'
]
);
$res
=
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'title'
=>
'Art©'
,
'text'
=>
'foo'
,
'prop'
=>
'displaytitle'
,
]
);
$this
->
assertSame
(
'<span class="mw-page-title-main">Art&copy</span>'
,
$res
[
0
][
'parse'
][
'displaytitle'
]
);
}
public
function
testIncompatFormat
()
{
$this
->
expectApiErrorCode
(
'badformat-generic'
);
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'prop'
=>
'categories'
,
'title'
=>
__CLASS__
,
'text'
=>
''
,
'contentformat'
=>
'application/json'
,
]
);
}
public
function
testIgnoreFormatUsingPage
()
{
$res
=
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'page'
=>
__CLASS__
,
'prop'
=>
'wikitext'
,
'contentformat'
=>
'text/plain'
,
]
);
$this
->
assertArrayHasKey
(
'wikitext'
,
$res
[
0
][
'parse'
]
);
}
public
function
testShouldCastNumericImageLinksToString
():
void
{
$res
=
$this
->
doApiRequest
(
[
'action'
=>
'parse'
,
'title'
=>
__CLASS__
,
'prop'
=>
'images'
,
'text'
=>
'[[File:1]]'
,
]
);
$this
->
assertSame
(
[
'1'
],
$res
[
0
][
'parse'
][
'images'
]
);
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Aug 18 2026, 10:17 (6 w, 13 h ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
26/33/61cf3102ef09670f517d5d94912f
Default Alt Text
ApiParseTest.php (29 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment