3 * Test class for ChangesListSpecialPage class
5 * Copyright © 2011-, Antoine Musso, Stephane Bisson, Matthew Flaschen
7 * @author Antoine Musso
8 * @author Stephane Bisson
9 * @author Matthew Flaschen
12 * @covers ChangesListSpecialPage
14 class ChangesListSpecialPageTest
extends AbstractChangesListSpecialPageTestCase
{
15 protected function setUp() {
19 $this->changesListSpecialPage
= $this->getPage();
22 protected function getPage() {
23 return TestingAccessWrapper
::newFromObject(
24 $this->getMockForAbstractClass(
25 'ChangesListSpecialPage',
27 'ChangesListSpecialPage',
34 /** helper to test SpecialRecentchanges::buildMainQueryConds() */
35 private function assertConditions(
37 $requestOptions = null,
41 $context = new RequestContext
;
42 $context->setRequest( new FauxRequest( $requestOptions ) );
44 $context->setUser( $user );
47 $this->changesListSpecialPage
->setContext( $context );
48 $formOptions = $this->changesListSpecialPage
->setup( null );
50 # Filter out rc_timestamp conditions which depends on the test runtime
51 # This condition is not needed as of march 2, 2011 -- hashar
52 # @todo FIXME: Find a way to generate the correct rc_timestamp
56 $queryConditions = [];
61 [ $this->changesListSpecialPage
, 'buildQuery' ],
72 $queryConditions = array_filter(
74 'ChangesListSpecialPageTest::filterOutRcTimestampCondition'
78 self
::normalizeCondition( $expected ),
79 self
::normalizeCondition( $queryConditions ),
84 private static function normalizeCondition( $conds ) {
85 $normalized = array_map(
87 return is_numeric( $k ) ?
$v : "$k = $v";
96 /** return false if condition begin with 'rc_timestamp ' */
97 private static function filterOutRcTimestampCondition( $var ) {
98 return ( false === strpos( $var, 'rc_timestamp ' ) );
101 public function testRcNsFilter() {
102 $this->assertConditions(
104 "rc_namespace = '0'",
107 'namespace' => NS_MAIN
,
109 "rc conditions with no options (aka default setting)"
113 public function testRcNsFilterInversion() {
114 $this->assertConditions(
116 "rc_namespace != '0'",
119 'namespace' => NS_MAIN
,
122 "rc conditions with namespace inverted"
128 * @dataProvider provideNamespacesAssociations
130 public function testRcNsFilterAssociation( $ns1, $ns2 ) {
131 $this->assertConditions(
133 "(rc_namespace = '$ns1' OR rc_namespace = '$ns2')",
139 "rc conditions with namespace inverted"
145 * @dataProvider provideNamespacesAssociations
147 public function testRcNsFilterAssociationWithInversion( $ns1, $ns2 ) {
148 $this->assertConditions(
150 "(rc_namespace != '$ns1' AND rc_namespace != '$ns2')",
157 "rc conditions with namespace inverted"
162 * Provides associated namespaces to test recent changes
163 * namespaces association filtering.
165 public static function provideNamespacesAssociations() {
166 return [ # (NS => Associated_NS)
167 [ NS_MAIN
, NS_TALK
],
168 [ NS_TALK
, NS_MAIN
],
172 public function testRcHidemyselfFilter() {
173 $user = $this->getTestUser()->getUser();
174 $this->assertConditions(
176 "rc_user_text != '{$user->getName()}'",
181 "rc conditions: hidemyself=1 (logged in)",
185 $user = User
::newFromName( '10.11.12.13', false );
186 $this->assertConditions(
188 "rc_user_text != '10.11.12.13'",
193 "rc conditions: hidemyself=1 (anon)",
198 public function testRcHidebyothersFilter() {
199 $user = $this->getTestUser()->getUser();
200 $this->assertConditions(
202 "rc_user_text = '{$user->getName()}'",
207 "rc conditions: hidebyothers=1 (logged in)",
211 $user = User
::newFromName( '10.11.12.13', false );
212 $this->assertConditions(
214 "rc_user_text = '10.11.12.13'",
219 "rc conditions: hidebyothers=1 (anon)",
224 public function testRcHidemyselfHidebyothersFilter() {
225 $user = $this->getTestUser()->getUser();
226 $this->assertConditions(
228 "rc_user_text != '{$user->getName()}'",
229 "rc_user_text = '{$user->getName()}'",
235 "rc conditions: hidemyself=1 hidebyothers=1 (logged in)",
240 public function testRcHidepageedits() {
241 $this->assertConditions(
246 'hidepageedits' => 1,
248 "rc conditions: hidepageedits=1"
252 public function testRcHidenewpages() {
253 $this->assertConditions(
260 "rc conditions: hidenewpages=1"
264 public function testRcHidelog() {
265 $this->assertConditions(
272 "rc conditions: hidelog=1"
276 public function testRcHidehumans() {
277 $this->assertConditions(
285 "rc conditions: hidebots=0 hidehumans=1"
289 public function testRcHidepatrolledDisabledFilter() {
290 $user = $this->getTestUser()->getUser();
291 $this->assertConditions(
295 'hidepatrolled' => 1,
297 "rc conditions: hidepatrolled=1 (user not allowed)",
302 public function testRcHideunpatrolledDisabledFilter() {
303 $user = $this->getTestUser()->getUser();
304 $this->assertConditions(
308 'hideunpatrolled' => 1,
310 "rc conditions: hideunpatrolled=1 (user not allowed)",
314 public function testRcHidepatrolledFilter() {
315 $user = $this->getTestSysop()->getUser();
316 $this->assertConditions(
321 'hidepatrolled' => 1,
323 "rc conditions: hidepatrolled=1",
328 public function testRcHideunpatrolledFilter() {
329 $user = $this->getTestSysop()->getUser();
330 $this->assertConditions(
335 'hideunpatrolled' => 1,
337 "rc conditions: hideunpatrolled=1",
342 public function testRcHideminorFilter() {
343 $this->assertConditions(
350 "rc conditions: hideminor=1"
354 public function testRcHidemajorFilter() {
355 $this->assertConditions(
362 "rc conditions: hidemajor=1"
366 public function testRcHidepatrolledHideunpatrolledFilter() {
367 $user = $this->getTestSysop()->getUser();
368 $this->assertConditions(
374 'hidepatrolled' => 1,
375 'hideunpatrolled' => 1,
377 "rc conditions: hidepatrolled=1 hideunpatrolled=1",
382 public function testHideCategorization() {
383 $this->assertConditions(
389 'hidecategorization' => 1
391 "rc conditions: hidecategorization=1"
395 public function testFilterUserExpLevel() {
396 $this->setMwGlobals( [
397 'wgLearnerEdits' => 10,
398 'wgLearnerMemberSince' => 4,
399 'wgExperiencedUserEdits' => 500,
400 'wgExperiencedUserMemberSince' => 30,
403 $this->createUsers( [
404 'Newcomer1' => [ 'edits' => 2, 'days' => 2 ],
405 'Newcomer2' => [ 'edits' => 12, 'days' => 3 ],
406 'Newcomer3' => [ 'edits' => 8, 'days' => 5 ],
407 'Learner1' => [ 'edits' => 15, 'days' => 10 ],
408 'Learner2' => [ 'edits' => 450, 'days' => 20 ],
409 'Learner3' => [ 'edits' => 460, 'days' => 33 ],
410 'Learner4' => [ 'edits' => 525, 'days' => 28 ],
411 'Experienced1' => [ 'edits' => 538, 'days' => 33 ],
415 $this->assertArrayEquals(
416 [ 'Newcomer1', 'Newcomer2', 'Newcomer3' ],
417 $this->fetchUsers( [ 'newcomer' ] )
420 // newcomers and learner
421 $this->assertArrayEquals(
423 'Newcomer1', 'Newcomer2', 'Newcomer3',
424 'Learner1', 'Learner2', 'Learner3', 'Learner4',
426 $this->fetchUsers( [ 'newcomer', 'learner' ] )
429 // newcomers and more learner
430 $this->assertArrayEquals(
432 'Newcomer1', 'Newcomer2', 'Newcomer3',
435 $this->fetchUsers( [ 'newcomer', 'experienced' ] )
439 $this->assertArrayEquals(
440 [ 'Learner1', 'Learner2', 'Learner3', 'Learner4' ],
441 $this->fetchUsers( [ 'learner' ] )
444 // more experienced only
445 $this->assertArrayEquals(
447 $this->fetchUsers( [ 'experienced' ] )
450 // learner and more experienced
451 $this->assertArrayEquals(
453 'Learner1', 'Learner2', 'Learner3', 'Learner4',
456 $this->fetchUsers( [ 'learner', 'experienced' ] ),
457 'Learner and more experienced'
460 // newcomers, learner, and more experienced
461 // TOOD: Fix test. This needs to test that anons are excluded,
462 // and right now the join fails.
463 /* $this->assertArrayEquals( */
465 /* 'Newcomer1', 'Newcomer2', 'Newcomer3', */
466 /* 'Learner1', 'Learner2', 'Learner3', 'Learner4', */
467 /* 'Experienced1', */
469 /* $this->fetchUsers( [ 'newcomer', 'learner', 'experienced' ] ) */
473 private function createUsers( $specs ) {
474 $dbw = wfGetDB( DB_MASTER
);
475 foreach ( $specs as $name => $spec ) {
479 'editcount' => $spec['edits'],
480 'registration' => $dbw->timestamp( $this->daysAgo( $spec['days'] ) ),
487 private function fetchUsers( $filters ) {
496 call_user_func_array(
497 [ $this->changesListSpecialPage
, 'filterOnUserExperienceLevel' ],
499 get_class( $this->changesListSpecialPage
),
500 $this->changesListSpecialPage
->getContext(),
501 $this->changesListSpecialPage
->getDB(),
511 $result = wfGetDB( DB_MASTER
)->select(
514 array_filter( $conds ) +
[ 'user_email' => 'ut' ]
518 foreach ( $result as $row ) {
519 $usernames[] = $row->user_name
;
525 private function daysAgo( $days ) {
526 $secondsPerDay = 86400;
527 return time() - $days * $secondsPerDay;
530 public function testGetFilterGroupDefinitionFromLegacyCustomFilters() {
533 'msg' => 'showhidefoo',
538 'msg' => 'showhidebar',
545 'name' => 'unstructured',
546 'class' => ChangesListBooleanFilterGroup
::class,
551 'showHide' => 'showhidefoo',
556 'showHide' => 'showhidebar',
561 $this->changesListSpecialPage
->getFilterGroupDefinitionFromLegacyCustomFilters(
567 public function testGetStructuredFilterJsData() {
570 'name' => 'gub-group',
571 'title' => 'gub-group-title',
572 'class' => ChangesListBooleanFilterGroup
::class,
576 'label' => 'foo-label',
577 'description' => 'foo-description',
579 'showHide' => 'showhidefoo',
584 'label' => 'bar-label',
585 'description' => 'bar-description',
593 'name' => 'des-group',
594 'title' => 'des-group-title',
595 'class' => ChangesListStringOptionsFilterGroup
::class,
596 'isFullCoverage' => true,
600 'label' => 'grault-label',
601 'description' => 'grault-description',
605 'label' => 'garply-label',
606 'description' => 'garply-description',
609 'queryCallable' => function () {
611 'default' => ChangesListStringOptionsFilterGroup
::NONE
,
615 'name' => 'unstructured',
616 'class' => ChangesListBooleanFilterGroup
::class,
619 'name' => 'hidethud',
620 'showHide' => 'showhidethud',
626 'showHide' => 'showhidemos',
634 $this->changesListSpecialPage
->registerFiltersFromDefinitions( $definition );
636 $this->assertArrayEquals(
638 // Filters that only display in the unstructured UI are
639 // are not included, and neither are groups that would
640 // be empty due to the above.
643 'name' => 'gub-group',
644 'title' => 'gub-group-title',
645 'type' => ChangesListBooleanFilterGroup
::TYPE
,
650 'label' => 'bar-label',
651 'description' => 'bar-description',
660 'label' => 'foo-label',
661 'description' => 'foo-description',
669 'fullCoverage' => true,
674 'name' => 'des-group',
675 'title' => 'des-group-title',
676 'type' => ChangesListStringOptionsFilterGroup
::TYPE
,
678 'fullCoverage' => true,
682 'label' => 'grault-label',
683 'description' => 'grault-description',
691 'label' => 'garply-label',
692 'description' => 'garply-description',
701 'default' => ChangesListStringOptionsFilterGroup
::NONE
,
712 'grault-description',
714 'garply-description',
717 $this->changesListSpecialPage
->getStructuredFilterJsData(),
718 /** ordered= */ false,
723 public function provideParseParameters() {
725 [ 'hidebots', [ 'hidebots' => true ] ],
727 [ 'bots', [ 'hidebots' => false ] ],
729 [ 'hideminor', [ 'hideminor' => true ] ],
731 [ 'minor', [ 'hideminor' => false ] ],
733 [ 'hidemajor', [ 'hidemajor' => true ] ],
735 [ 'hideliu', [ 'hideliu' => true ] ],
737 [ 'hidepatrolled', [ 'hidepatrolled' => true ] ],
739 [ 'hideunpatrolled', [ 'hideunpatrolled' => true ] ],
741 [ 'hideanons', [ 'hideanons' => true ] ],
743 [ 'hidemyself', [ 'hidemyself' => true ] ],
745 [ 'hidebyothers', [ 'hidebyothers' => true ] ],
747 [ 'hidehumans', [ 'hidehumans' => true ] ],
749 [ 'hidepageedits', [ 'hidepageedits' => true ] ],
751 [ 'pagedits', [ 'hidepageedits' => false ] ],
753 [ 'hidenewpages', [ 'hidenewpages' => true ] ],
755 [ 'hidecategorization', [ 'hidecategorization' => true ] ],
757 [ 'hidelog', [ 'hidelog' => true ] ],
760 'userExpLevel=learner;experienced',
762 'userExpLevel' => 'learner;experienced'
766 // A few random combos
768 'bots,hideliu,hidemyself',
772 'hidemyself' => true,
777 'minor,hideanons,categorization',
779 'hideminor' => false,
781 'hidecategorization' => false,
786 'hidehumans,bots,hidecategorization',
788 'hidehumans' => true,
790 'hidecategorization' => true,
795 'hidemyself,userExpLevel=newcomer;learner,hideminor',
797 'hidemyself' => true,
799 'userExpLevel' => 'newcomer;learner',