Merge "Drop cache interwiki"
[mediawiki.git] / tests / phpunit / includes / specials / SpecialNewPagesTest.php
blobfcd661c373f4139322166c5c3bad59a67c7e79b5
1 <?php
3 namespace MediaWiki\Tests\Specials;
5 use DOMElement;
6 use MediaWiki\Context\RequestContext;
7 use MediaWiki\MainConfigNames;
8 use MediaWiki\Request\FauxRequest;
9 use MediaWiki\Revision\RevisionRecord;
10 use MediaWiki\Tests\User\TempUser\TempUserTestTrait;
11 use MediaWiki\Title\Title;
12 use MediaWiki\User\UserFactory;
13 use MediaWiki\User\UserIdentity;
14 use SpecialPageTestBase;
15 use Wikimedia\Parsoid\DOM\Document;
16 use Wikimedia\Parsoid\DOM\Element;
17 use Wikimedia\Parsoid\Utils\DOMCompat;
18 use Wikimedia\Parsoid\Utils\DOMUtils;
20 /**
21 * @group Database
22 * @covers \MediaWiki\Specials\SpecialNewPages
23 * @covers \MediaWiki\Pager\NewPagesPager
25 class SpecialNewPagesTest extends SpecialPageTestBase {
27 use TempUserTestTrait;
29 private static UserIdentity $testUser1;
31 /** @var Title[] */
32 private static array $testUser1Pages;
34 /** @var Title[] */
35 private static array $allPages;
37 private static int $editRevId;
39 protected function newSpecialPage() {
40 return $this->getServiceContainer()->getSpecialPageFactory()->getPage( 'Newpages' );
43 /**
44 * Asserts that the form fields for the Special:NewPages page are present.
46 * @param string $html The HTML returned by the special page
47 * @param bool $canAnonUsersCreatePages Whether anonymous users should be able to create pages
49 private function verifyFormFieldsArePresent(
50 string $html, bool $canAnonUsersCreatePages
51 ) {
52 // Verify that the form labels are present. This is a good way to check that the form fields are present,
53 // since the form labels should be generated by the form field definitions.
54 $this->assertStringContainsString( '(namespace)', $html, 'Namespace filter not added to form' );
55 $this->assertStringContainsString( '(newpages-username)', $html, 'Username filter not added to form' );
56 $this->assertStringContainsString(
57 '(namespace_association)', $html, 'Associated namespace filter not added to form'
59 $this->assertStringContainsString( '(tag-filter)', $html, 'Tag filter not added to form' );
60 $this->assertStringContainsString( '(invert)', $html, 'Invert checkbox not added to form' );
61 $this->assertStringContainsString( '(minimum-size)', $html, 'Size filter not added to form' );
62 // Verify that the filter links are present in the form
63 if ( $canAnonUsersCreatePages ) {
64 $this->assertStringContainsString(
65 '(newpages-showhide-registered', $html, 'Registered filter should be present'
67 } else {
68 $this->assertStringNotContainsString(
69 '(newpages-showhide-registered', $html, 'Registered filter should not be present'
72 $this->assertStringContainsString( '(newpages-showhide-bots', $html, 'Missing bots filter' );
73 $this->assertStringContainsString( '(newpages-showhide-redirect', $html, 'Missing redirect filter' );
75 $this->assertStringContainsString( '(newpages-submit)', $html, 'Submit button text not as expected' );
78 /**
79 * @param string $group The group to allow or disallow creating pages
80 * @param bool $state Whether to allow or disallow the given $group from creating pages
82 private function setGroupHasRightsToCreatePages( string $group, bool $state ) {
83 // Remove the 'createtalk' and 'createpage' rights from the '*' group if they are present for the test.
84 $groupPermissionsValue = $this->getServiceContainer()->getMainConfig()
85 ->get( MainConfigNames::GroupPermissions );
86 if ( $state ) {
87 $groupPermissionsValue[$group]['createtalk'] = true;
88 $groupPermissionsValue[$group]['createpage'] = true;
89 } else {
90 unset( $groupPermissionsValue[$group]['createtalk'] );
91 unset( $groupPermissionsValue[$group]['createpage'] );
93 $this->overrideConfigValue( MainConfigNames::GroupPermissions, $groupPermissionsValue );
96 /**
97 * Helper method used to expect that one element matches the given selector inside the given parent element.
99 * @param DOMElement|Document $document The element to search through
100 * @param string $selector The CSS selector which should match only one element
101 * @return DOMElement|Element The matched element
103 private function getAndExpectSingleMatchingElement( $document, string $selector ) {
104 $matchingClass = DOMCompat::querySelectorAll( $document, $selector );
105 $this->assertCount( 1, $matchingClass, "One element was expected to match $selector" );
106 return $matchingClass[0];
110 * Verifies that the given new pages line has the expected elements.
112 * @param DOMElement|Element $line The line element to verify
113 * @param RevisionRecord $firstRevision The first revision of the page
115 private function verifyLineHasExpectedElements( $line, RevisionRecord $firstRevision ) {
116 // Verify the timestamp element is present
117 $this->getAndExpectSingleMatchingElement( $line, ".mw-newpages-time" );
118 // Verify that the page name is as expected.
119 $pageNameElement = $this->getAndExpectSingleMatchingElement(
120 $line, ".mw-newpages-pagename"
122 $this->assertSame(
123 $this->getServiceContainer()->getTitleFormatter()->getPrefixedText( $firstRevision->getPage() ),
124 $pageNameElement->textContent
126 // Verify that the edit page and page history links are there
127 $editLinkElement = $this->getAndExpectSingleMatchingElement( $line, ".mw-newpages-edit" );
128 $this->assertSame( '(editlink)', $editLinkElement->textContent );
129 $pageHistoryLinkElement = $this->getAndExpectSingleMatchingElement(
130 $line, ".mw-newpages-history"
132 $this->assertSame( '(hist)', $pageHistoryLinkElement->textContent );
133 // Verify that the user link is present and correct, including that the username is hidden if the current
134 // authority cannot see it.
135 $authority = RequestContext::getMain()->getAuthority();
136 $userNameElement = $this->getAndExpectSingleMatchingElement( $line, ".mw-userlink" );
137 if ( $firstRevision->userCan( RevisionRecord::DELETED_USER, $authority ) ) {
138 $expectedUserText = $firstRevision->getUser( RevisionRecord::RAW )->getName();
139 } else {
140 $expectedUserText = '(rev-deleted-user)';
142 $this->assertSame( $expectedUserText, $userNameElement->textContent );
143 // Verify that the comment is present if visible or hidden if not
144 $commentElement = $this->getAndExpectSingleMatchingElement( $line, ".comment" );
145 if ( $firstRevision->userCan( RevisionRecord::DELETED_COMMENT, $authority ) ) {
146 $this->assertStringContainsString( $firstRevision->getComment()->text, $commentElement->textContent );
147 } else {
148 $this->assertStringContainsString( '(rev-deleted-comment)', $commentElement->textContent );
153 * Perform testing steps that are common to all of the tests in this file.
155 * @param Title[] $expectedPages A list of Title objects for pages that should appear in the results
156 * @param Title[] $expectedPagesNotShown A list of Title objects for pages that should not appear in the results
157 * @param ?FauxRequest $fauxRequest A fake request to use for the test, null just uses the main request
158 * @param bool $canAnonUsersCreatePages Whether IP addresses can create pages
159 * @param ?bool $canTempUsersCreatePages Null if temporary accounts are disabled and not known about.
160 * A boolean if temporary accounts are enabled, and the boolean is whether temporary accounts can create pages.
161 * @return string
163 private function testLoadPage(
164 array $expectedPages, array $expectedPagesNotShown, ?FauxRequest $fauxRequest = null,
165 bool $canAnonUsersCreatePages = false, ?bool $canTempUsersCreatePages = false
166 ): string {
167 $this->setGroupHasRightsToCreatePages( '*', $canAnonUsersCreatePages );
168 if ( $canTempUsersCreatePages !== null ) {
169 // If the $canTempUsersCreatePages is set to a boolean, then enable temp users as temporary users are
170 // being used in the test.
171 $this->enableAutoCreateTempUser();
172 $this->setGroupHasRightsToCreatePages( 'temp', $canTempUsersCreatePages );
174 $this->overrideConfigValues( [
175 MainConfigNames::UseNPPatrol => true,
176 MainConfigNames::UseRCPatrol => true,
177 ] );
178 // This is explicitly needed because the HTMLSizeFilterField uses the user's language and not the language
179 // set by ::executeSpecialPage.
180 $this->setUserLang( 'qqx' );
181 // Call the special page and verify that the form fields are as expected.
182 [ $html ] = $this->executeSpecialPage( '', $fauxRequest );
183 $this->verifyFormFieldsArePresent( $html, $canAnonUsersCreatePages );
184 // Verify that the pages which should be there are present in the page.
185 $contributionsList = $this->getAndExpectSingleMatchingElement(
186 DOMUtils::parseHTML( $html ), '.mw-contributions-list'
188 foreach ( $expectedPages as $page ) {
189 // Find the line with the matching revision ID
190 $firstRevision = $this->getServiceContainer()->getRevisionStore()->getFirstRevision( $page );
191 $matchingLine = $this->getAndExpectSingleMatchingElement(
192 $contributionsList, "li[data-mw-revid=\"{$firstRevision->getId()}\"]"
194 // Check that this matching line has the expected structure.
195 $this->verifyLineHasExpectedElements( $matchingLine, $firstRevision );
197 // Check that the pages which shouldn't be there are not added to the page.
198 foreach ( $expectedPagesNotShown as $page ) {
199 $firstRevId = $this->getServiceContainer()->getRevisionStore()->getFirstRevision( $page )->getId();
200 $matchingLines = DOMCompat::querySelectorAll( $contributionsList, "[data-mw-revid=\"$firstRevId\"]" );
201 $this->assertCount(
202 0, $matchingLines, "New page entry for revision $firstRevId was not expected"
205 // Verify that the edit is never shown
206 $matchingLines = DOMCompat::querySelectorAll(
207 $contributionsList, '[data-mw-revid="' . self::$editRevId . '"]'
209 $this->assertCount(
210 0, $matchingLines,
211 'A revision ID which is not associated with a new page creation is present in Special:NewPages.'
213 // Return the HTML to allow further custom testing by the methods which called this method.
214 return $html;
217 public function testLoadWithNoOptionsSpecified() {
218 // Expect that by default all new main space page creations are shown, but no other pages.
219 $expectedPages = [];
220 $expectedPagesNotShown = [];
221 foreach ( self::$allPages as $page ) {
222 if ( $page->getNamespace() === NS_MAIN ) {
223 $expectedPages[] = $page;
224 } else {
225 $expectedPagesNotShown[] = $page;
228 $this->testLoadPage( $expectedPages, $expectedPagesNotShown );
231 public function testWhenFilteredToJustTestUser1Pages() {
232 // Filter for all page creations by the first test user.
233 $this->testLoadPage(
234 self::$testUser1Pages, array_diff( self::$allPages, self::$testUser1Pages ),
235 new FauxRequest( [ 'username' => self::$testUser1->getName(), 'namespace' => 'all' ] )
239 public function testWhenFilteredToJustAnonCreations() {
240 // Filter for all page creations by anon users in any namespace.
241 $fauxRequest = new FauxRequest( [ 'hideliu' => true, 'namespace' => '' ] );
242 $this->testLoadPage(
243 array_diff( self::$allPages, self::$testUser1Pages ), self::$testUser1Pages, $fauxRequest,
244 true, true
248 public function testWhenFilteredToJustAnonCreationsWhenTemporaryAccountsAreDisabled() {
249 // Filter for all page creations by anon users in any namespace.
250 $fauxRequest = new FauxRequest( [ 'hideliu' => true, 'namespace' => '' ] );
251 // The expected pages should only be creations where the author is not an IP address.
252 $expectedPages = array_filter( self::$allPages, function ( $page ) {
253 $firstRev = $this->getServiceContainer()->getRevisionStore()->getFirstRevision( $page );
254 return !$firstRev->getUser()->isRegistered();
255 } );
256 $this->disableAutoCreateTempUser();
257 $this->testLoadPage(
258 $expectedPages, array_diff( self::$allPages, $expectedPages ), $fauxRequest,
259 true, null
263 public function addDBDataOnce() {
264 // Create some pages so that there will be some entries in Special:NewPages.
265 $testUser1 = $this->getMutableTestUser()->getUser();
266 // Get the first test user to create a page and it's associated talk page in mainspace.
267 $firstPage = $this->insertPage( 'SpecialNewPagesTest1', 'test', NS_MAIN, $testUser1 );
268 $secondPage = $this->insertPage( 'SpecialNewPagesTest1', 'talk', NS_TALK, $testUser1 );
269 // Get the first test user to create it's userpage
270 $thirdPage = $this->insertPage( $testUser1->getName(), 'userpage', NS_USER, $testUser1 );
271 // Get an anon user to create a page in the template namespace.
272 $this->disableAutoCreateTempUser();
273 $fourthPage = $this->insertPage(
274 'SpecialNewPagesTest2', 'test', NS_TEMPLATE,
275 $this->getServiceContainer()->getUserFactory()->newFromName( '127.0.0.1', UserFactory::RIGOR_NONE )
277 // Get a temporary account to create a page in the project namespace.
278 $this->enableAutoCreateTempUser();
279 $testTempUser = $this->getServiceContainer()->getTempUserCreator()
280 ->create( null, RequestContext::getMain()->getRequest() );
281 $this->assertStatusGood( $testTempUser );
282 $fifthPage = $this->insertPage(
283 'SpecialNewPagesTest3', 'test', NS_PROJECT, $testTempUser->getUser()
285 // Get the sysop test user to make an edit, to test it won't appear in Special:NewPages.
286 $editStatus = $this->editPage( $firstPage['title'], 'testing1234', 'test edit' );
287 $this->assertStatusGood( $editStatus );
288 self::$testUser1 = $testUser1;
289 self::$testUser1Pages = [ $firstPage['title'], $secondPage['title'], $thirdPage['title'], ];
290 self::$allPages = array_merge( self::$testUser1Pages, [ $fourthPage['title'], $fifthPage['title'] ] );
291 self::$editRevId = $editStatus->getNewRevision()->getId();