3 use Wikimedia\TestingAccessWrapper
;
6 * Test class for ChangesListSpecialPage class
8 * Copyright © 2011-, Antoine Musso, Stephane Bisson, Matthew Flaschen
10 * @author Antoine Musso
11 * @author Stephane Bisson
12 * @author Matthew Flaschen
15 * @covers ChangesListSpecialPage
17 class ChangesListSpecialPageTest
extends AbstractChangesListSpecialPageTestCase
{
18 protected function getPage() {
19 $mock = $this->getMockBuilder( ChangesListSpecialPage
::class )
22 'ChangesListSpecialPage',
26 ->setMethods( [ 'getPageTitle' ] )
27 ->getMockForAbstractClass();
29 $mock->method( 'getPageTitle' )->willReturn(
30 Title
::makeTitle( NS_SPECIAL
, 'ChangesListSpecialPage' )
33 $mock = TestingAccessWrapper
::newFromObject(
40 private function buildQuery(
41 $requestOptions = null,
44 $context = new RequestContext
;
45 $context->setRequest( new FauxRequest( $requestOptions ) );
47 $context->setUser( $user );
50 $this->changesListSpecialPage
->setContext( $context );
51 $this->changesListSpecialPage
->filterGroups
= [];
52 $formOptions = $this->changesListSpecialPage
->setup( null );
54 # Filter out rc_timestamp conditions which depends on the test runtime
55 # This condition is not needed as of march 2, 2011 -- hashar
56 # @todo FIXME: Find a way to generate the correct rc_timestamp
60 $queryConditions = [];
65 [ $this->changesListSpecialPage
, 'buildQuery' ],
76 $queryConditions = array_filter(
78 'ChangesListSpecialPageTest::filterOutRcTimestampCondition'
81 return $queryConditions;
84 /** helper to test SpecialRecentchanges::buildQuery() */
85 private function assertConditions(
87 $requestOptions = null,
91 $queryConditions = $this->buildQuery( $requestOptions, $user );
94 self
::normalizeCondition( $expected ),
95 self
::normalizeCondition( $queryConditions ),
100 private static function normalizeCondition( $conds ) {
101 $normalized = array_map(
102 function ( $k, $v ) {
103 return is_numeric( $k ) ?
$v : "$k = $v";
105 array_keys( $conds ),
112 /** return false if condition begin with 'rc_timestamp ' */
113 private static function filterOutRcTimestampCondition( $var ) {
114 return ( false === strpos( $var, 'rc_timestamp ' ) );
117 public function testRcNsFilter() {
118 $this->assertConditions(
120 "rc_namespace = '0'",
123 'namespace' => NS_MAIN
,
125 "rc conditions with one namespace"
129 public function testRcNsFilterInversion() {
130 $this->assertConditions(
132 "rc_namespace != '0'",
135 'namespace' => NS_MAIN
,
138 "rc conditions with namespace inverted"
142 public function testRcNsFilterMultiple() {
143 $this->assertConditions(
145 "rc_namespace IN ('1','2','3')",
148 'namespace' => '1;2;3',
150 "rc conditions with multiple namespaces"
154 public function testRcNsFilterMultipleAssociated() {
155 $this->assertConditions(
157 "rc_namespace IN ('0','1','4','5','6','7')",
160 'namespace' => '1;4;7',
163 "rc conditions with multiple namespaces and associated"
167 public function testRcNsFilterMultipleAssociatedInvert() {
168 $this->assertConditions(
170 "rc_namespace NOT IN ('2','3','8','9')",
173 'namespace' => '2;3;9',
177 "rc conditions with multiple namespaces, associated and inverted"
181 public function testRcNsFilterMultipleInvert() {
182 $this->assertConditions(
184 "rc_namespace NOT IN ('1','2','3')",
187 'namespace' => '1;2;3',
190 "rc conditions with multiple namespaces inverted"
194 public function testRcHidemyselfFilter() {
195 $user = $this->getTestUser()->getUser();
196 $this->assertConditions(
198 "rc_user_text != '{$user->getName()}'",
203 "rc conditions: hidemyself=1 (logged in)",
207 $user = User
::newFromName( '10.11.12.13', false );
208 $this->assertConditions(
210 "rc_user_text != '10.11.12.13'",
215 "rc conditions: hidemyself=1 (anon)",
220 public function testRcHidebyothersFilter() {
221 $user = $this->getTestUser()->getUser();
222 $this->assertConditions(
224 "rc_user_text = '{$user->getName()}'",
229 "rc conditions: hidebyothers=1 (logged in)",
233 $user = User
::newFromName( '10.11.12.13', false );
234 $this->assertConditions(
236 "rc_user_text = '10.11.12.13'",
241 "rc conditions: hidebyothers=1 (anon)",
246 public function testRcHidepageedits() {
247 $this->assertConditions(
252 'hidepageedits' => 1,
254 "rc conditions: hidepageedits=1"
258 public function testRcHidenewpages() {
259 $this->assertConditions(
266 "rc conditions: hidenewpages=1"
270 public function testRcHidelog() {
271 $this->assertConditions(
278 "rc conditions: hidelog=1"
282 public function testRcHidehumans() {
283 $this->assertConditions(
291 "rc conditions: hidebots=0 hidehumans=1"
295 public function testRcHidepatrolledDisabledFilter() {
296 $this->setMwGlobals( 'wgUseRCPatrol', false );
297 $user = $this->getTestUser()->getUser();
298 $this->assertConditions(
302 'hidepatrolled' => 1,
304 "rc conditions: hidepatrolled=1 (user not allowed)",
309 public function testRcHideunpatrolledDisabledFilter() {
310 $this->setMwGlobals( 'wgUseRCPatrol', false );
311 $user = $this->getTestUser()->getUser();
312 $this->assertConditions(
316 'hideunpatrolled' => 1,
318 "rc conditions: hideunpatrolled=1 (user not allowed)",
322 public function testRcHidepatrolledFilter() {
323 $user = $this->getTestSysop()->getUser();
324 $this->assertConditions(
329 'hidepatrolled' => 1,
331 "rc conditions: hidepatrolled=1",
336 public function testRcHideunpatrolledFilter() {
337 $user = $this->getTestSysop()->getUser();
338 $this->assertConditions(
343 'hideunpatrolled' => 1,
345 "rc conditions: hideunpatrolled=1",
350 public function testRcHideminorFilter() {
351 $this->assertConditions(
358 "rc conditions: hideminor=1"
362 public function testRcHidemajorFilter() {
363 $this->assertConditions(
370 "rc conditions: hidemajor=1"
374 public function testHideCategorization() {
375 $this->assertConditions(
381 'hidecategorization' => 1
383 "rc conditions: hidecategorization=1"
387 public function testFilterUserExpLevelAll() {
388 $this->assertConditions(
393 'userExpLevel' => 'registered;unregistered;newcomer;learner;experienced',
395 "rc conditions: userExpLevel=registered;unregistered;newcomer;learner;experienced"
399 public function testFilterUserExpLevelRegisteredUnregistered() {
400 $this->assertConditions(
405 'userExpLevel' => 'registered;unregistered',
407 "rc conditions: userExpLevel=registered;unregistered"
411 public function testFilterUserExpLevelRegisteredUnregisteredLearner() {
412 $this->assertConditions(
417 'userExpLevel' => 'registered;unregistered;learner',
419 "rc conditions: userExpLevel=registered;unregistered;learner"
423 public function testFilterUserExpLevelAllExperienceLevels() {
424 $this->assertConditions(
430 'userExpLevel' => 'newcomer;learner;experienced',
432 "rc conditions: userExpLevel=newcomer;learner;experienced"
436 public function testFilterUserExpLevelRegistrered() {
437 $this->assertConditions(
443 'userExpLevel' => 'registered',
445 "rc conditions: userExpLevel=registered"
449 public function testFilterUserExpLevelUnregistrered() {
450 $this->assertConditions(
456 'userExpLevel' => 'unregistered',
458 "rc conditions: userExpLevel=unregistered"
462 public function testFilterUserExpLevelRegistreredOrLearner() {
463 $this->assertConditions(
469 'userExpLevel' => 'registered;learner',
471 "rc conditions: userExpLevel=registered;learner"
475 public function testFilterUserExpLevelUnregistreredOrExperienced() {
476 $conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
479 '/\(rc_user = 0\) OR \(\(user_editcount >= 500\) AND \(user_registration <= \'[^\']+\'\)\)/',
481 "rc conditions: userExpLevel=unregistered;experienced"
485 public function testFilterUserExpLevel() {
487 $this->setMwGlobals( [
488 'wgLearnerEdits' => 10,
489 'wgLearnerMemberSince' => 4,
490 'wgExperiencedUserEdits' => 500,
491 'wgExperiencedUserMemberSince' => 30,
494 $this->createUsers( [
495 'Newcomer1' => [ 'edits' => 2, 'days' => 2 ],
496 'Newcomer2' => [ 'edits' => 12, 'days' => 3 ],
497 'Newcomer3' => [ 'edits' => 8, 'days' => 5 ],
498 'Learner1' => [ 'edits' => 15, 'days' => 10 ],
499 'Learner2' => [ 'edits' => 450, 'days' => 20 ],
500 'Learner3' => [ 'edits' => 460, 'days' => 33 ],
501 'Learner4' => [ 'edits' => 525, 'days' => 28 ],
502 'Experienced1' => [ 'edits' => 538, 'days' => 33 ],
506 $this->assertArrayEquals(
507 [ 'Newcomer1', 'Newcomer2', 'Newcomer3' ],
508 $this->fetchUsers( [ 'newcomer' ], $now )
511 // newcomers and learner
512 $this->assertArrayEquals(
514 'Newcomer1', 'Newcomer2', 'Newcomer3',
515 'Learner1', 'Learner2', 'Learner3', 'Learner4',
517 $this->fetchUsers( [ 'newcomer', 'learner' ], $now )
520 // newcomers and more learner
521 $this->assertArrayEquals(
523 'Newcomer1', 'Newcomer2', 'Newcomer3',
526 $this->fetchUsers( [ 'newcomer', 'experienced' ], $now )
530 $this->assertArrayEquals(
531 [ 'Learner1', 'Learner2', 'Learner3', 'Learner4' ],
532 $this->fetchUsers( [ 'learner' ], $now )
535 // more experienced only
536 $this->assertArrayEquals(
538 $this->fetchUsers( [ 'experienced' ], $now )
541 // learner and more experienced
542 $this->assertArrayEquals(
544 'Learner1', 'Learner2', 'Learner3', 'Learner4',
547 $this->fetchUsers( [ 'learner', 'experienced' ], $now ),
548 'Learner and more experienced'
552 private function createUsers( $specs, $now ) {
553 $dbw = wfGetDB( DB_MASTER
);
554 foreach ( $specs as $name => $spec ) {
558 'editcount' => $spec['edits'],
559 'registration' => $dbw->timestamp( $this->daysAgo( $spec['days'], $now ) ),
566 private function fetchUsers( $filters, $now ) {
575 call_user_func_array(
576 [ $this->changesListSpecialPage
, 'filterOnUserExperienceLevel' ],
578 get_class( $this->changesListSpecialPage
),
579 $this->changesListSpecialPage
->getContext(),
580 $this->changesListSpecialPage
->getDB(),
591 $result = wfGetDB( DB_MASTER
)->select(
594 array_filter( $conds ) +
[ 'user_email' => 'ut' ]
598 foreach ( $result as $row ) {
599 $usernames[] = $row->user_name
;
605 private function daysAgo( $days, $now ) {
606 $secondsPerDay = 86400;
607 return $now - $days * $secondsPerDay;
610 public function testGetFilterGroupDefinitionFromLegacyCustomFilters() {
613 'msg' => 'showhidefoo',
618 'msg' => 'showhidebar',
625 'name' => 'unstructured',
626 'class' => ChangesListBooleanFilterGroup
::class,
631 'showHide' => 'showhidefoo',
636 'showHide' => 'showhidebar',
641 $this->changesListSpecialPage
->getFilterGroupDefinitionFromLegacyCustomFilters(
647 public function testGetStructuredFilterJsData() {
648 $this->changesListSpecialPage
->filterGroups
= [];
652 'name' => 'gub-group',
653 'title' => 'gub-group-title',
654 'class' => ChangesListBooleanFilterGroup
::class,
658 'label' => 'foo-label',
659 'description' => 'foo-description',
661 'showHide' => 'showhidefoo',
666 'label' => 'bar-label',
667 'description' => 'bar-description',
675 'name' => 'des-group',
676 'title' => 'des-group-title',
677 'class' => ChangesListStringOptionsFilterGroup
::class,
678 'isFullCoverage' => true,
682 'label' => 'grault-label',
683 'description' => 'grault-description',
687 'label' => 'garply-label',
688 'description' => 'garply-description',
691 'queryCallable' => function () {
693 'default' => ChangesListStringOptionsFilterGroup
::NONE
,
697 'name' => 'unstructured',
698 'class' => ChangesListBooleanFilterGroup
::class,
701 'name' => 'hidethud',
702 'showHide' => 'showhidethud',
708 'showHide' => 'showhidemos',
716 $this->changesListSpecialPage
->registerFiltersFromDefinitions( $definition );
718 $this->assertArrayEquals(
720 // Filters that only display in the unstructured UI are
721 // are not included, and neither are groups that would
722 // be empty due to the above.
725 'name' => 'gub-group',
726 'title' => 'gub-group-title',
727 'type' => ChangesListBooleanFilterGroup
::TYPE
,
732 'label' => 'bar-label',
733 'description' => 'bar-description',
739 'defaultHighlightColor' => null
743 'label' => 'foo-label',
744 'description' => 'foo-description',
750 'defaultHighlightColor' => null
753 'fullCoverage' => true,
758 'name' => 'des-group',
759 'title' => 'des-group-title',
760 'type' => ChangesListStringOptionsFilterGroup
::TYPE
,
762 'fullCoverage' => true,
766 'label' => 'grault-label',
767 'description' => 'grault-description',
772 'defaultHighlightColor' => null
776 'label' => 'garply-label',
777 'description' => 'garply-description',
782 'defaultHighlightColor' => null
787 'default' => ChangesListStringOptionsFilterGroup
::NONE
,
798 'grault-description',
800 'garply-description',
803 $this->changesListSpecialPage
->getStructuredFilterJsData(),
804 /** ordered= */ false,
809 public function provideParseParameters() {
811 [ 'hidebots', [ 'hidebots' => true ] ],
813 [ 'bots', [ 'hidebots' => false ] ],
815 [ 'hideminor', [ 'hideminor' => true ] ],
817 [ 'minor', [ 'hideminor' => false ] ],
819 [ 'hidemajor', [ 'hidemajor' => true ] ],
821 [ 'hideliu', [ 'hideliu' => true ] ],
823 [ 'hidepatrolled', [ 'hidepatrolled' => true ] ],
825 [ 'hideunpatrolled', [ 'hideunpatrolled' => true ] ],
827 [ 'hideanons', [ 'hideanons' => true ] ],
829 [ 'hidemyself', [ 'hidemyself' => true ] ],
831 [ 'hidebyothers', [ 'hidebyothers' => true ] ],
833 [ 'hidehumans', [ 'hidehumans' => true ] ],
835 [ 'hidepageedits', [ 'hidepageedits' => true ] ],
837 [ 'pagedits', [ 'hidepageedits' => false ] ],
839 [ 'hidenewpages', [ 'hidenewpages' => true ] ],
841 [ 'hidecategorization', [ 'hidecategorization' => true ] ],
843 [ 'hidelog', [ 'hidelog' => true ] ],
846 'userExpLevel=learner;experienced',
848 'userExpLevel' => 'learner;experienced'
852 // A few random combos
854 'bots,hideliu,hidemyself',
858 'hidemyself' => true,
863 'minor,hideanons,categorization',
865 'hideminor' => false,
867 'hidecategorization' => false,
872 'hidehumans,bots,hidecategorization',
874 'hidehumans' => true,
876 'hidecategorization' => true,
881 'hidemyself,userExpLevel=newcomer;learner,hideminor',
883 'hidemyself' => true,
885 'userExpLevel' => 'newcomer;learner',
891 public function provideGetFilterConflicts() {
895 "expectedConflicts" => false,
900 "userExpLevel" => "newcomer",
902 "expectedConflicts" => false,
907 "userExpLevel" => "learner",
909 "expectedConflicts" => false,
914 "hidenewpages" => true,
915 "hidepageedits" => true,
916 "hidecategorization" => false,
918 "hideWikidata" => true,
920 "expectedConflicts" => true,
925 "hidenewpages" => false,
926 "hidepageedits" => true,
927 "hidecategorization" => false,
929 "hideWikidata" => true,
931 "expectedConflicts" => true,
936 "hidenewpages" => false,
937 "hidepageedits" => false,
938 "hidecategorization" => true,
940 "hideWikidata" => true,
942 "expectedConflicts" => false,
947 "hidenewpages" => true,
948 "hidepageedits" => true,
949 "hidecategorization" => false,
951 "hideWikidata" => true,
953 "expectedConflicts" => false,
959 * @dataProvider provideGetFilterConflicts
961 public function testGetFilterConflicts( $parameters, $expectedConflicts ) {
962 $context = new RequestContext
;
963 $context->setRequest( new FauxRequest( $parameters ) );
964 $this->changesListSpecialPage
->setContext( $context );
968 $this->changesListSpecialPage
->areFiltersInConflict()
972 public function validateOptionsProvider() {
975 [ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 1 ],
977 [ 'hideliu' => 1, 'hidebots' => 1, ],
981 [ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 0 ],
983 [ 'hidebots' => 0, 'hidehumans' => 1 ],
987 [ 'hidemyself' => 1, 'hidebyothers' => 1 ],
992 [ 'hidebots' => 1, 'hidehumans' => 1 ],
997 [ 'hidepatrolled' => 1, 'hideunpatrolled' => 1 ],
1002 [ 'hideminor' => 1, 'hidemajor' => 1 ],
1008 [ 'hidepageedits' => 1, 'hidenewpages' => 1, 'hidecategorization' => 1, 'hidelog' => 1, ],