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 $user = $this->getTestUser()->getUser();
297 $this->assertConditions(
301 'hidepatrolled' => 1,
303 "rc conditions: hidepatrolled=1 (user not allowed)",
308 public function testRcHideunpatrolledDisabledFilter() {
309 $user = $this->getTestUser()->getUser();
310 $this->assertConditions(
314 'hideunpatrolled' => 1,
316 "rc conditions: hideunpatrolled=1 (user not allowed)",
320 public function testRcHidepatrolledFilter() {
321 $user = $this->getTestSysop()->getUser();
322 $this->assertConditions(
327 'hidepatrolled' => 1,
329 "rc conditions: hidepatrolled=1",
334 public function testRcHideunpatrolledFilter() {
335 $user = $this->getTestSysop()->getUser();
336 $this->assertConditions(
341 'hideunpatrolled' => 1,
343 "rc conditions: hideunpatrolled=1",
348 public function testRcHideminorFilter() {
349 $this->assertConditions(
356 "rc conditions: hideminor=1"
360 public function testRcHidemajorFilter() {
361 $this->assertConditions(
368 "rc conditions: hidemajor=1"
372 public function testHideCategorization() {
373 $this->assertConditions(
379 'hidecategorization' => 1
381 "rc conditions: hidecategorization=1"
385 public function testFilterUserExpLevelAll() {
386 $this->assertConditions(
391 'userExpLevel' => 'registered;unregistered;newcomer;learner;experienced',
393 "rc conditions: userExpLevel=registered;unregistered;newcomer;learner;experienced"
397 public function testFilterUserExpLevelRegisteredUnregistered() {
398 $this->assertConditions(
403 'userExpLevel' => 'registered;unregistered',
405 "rc conditions: userExpLevel=registered;unregistered"
409 public function testFilterUserExpLevelRegisteredUnregisteredLearner() {
410 $this->assertConditions(
415 'userExpLevel' => 'registered;unregistered;learner',
417 "rc conditions: userExpLevel=registered;unregistered;learner"
421 public function testFilterUserExpLevelAllExperienceLevels() {
422 $this->assertConditions(
428 'userExpLevel' => 'newcomer;learner;experienced',
430 "rc conditions: userExpLevel=newcomer;learner;experienced"
434 public function testFilterUserExpLevelRegistrered() {
435 $this->assertConditions(
441 'userExpLevel' => 'registered',
443 "rc conditions: userExpLevel=registered"
447 public function testFilterUserExpLevelUnregistrered() {
448 $this->assertConditions(
454 'userExpLevel' => 'unregistered',
456 "rc conditions: userExpLevel=unregistered"
460 public function testFilterUserExpLevelRegistreredOrLearner() {
461 $this->assertConditions(
467 'userExpLevel' => 'registered;learner',
469 "rc conditions: userExpLevel=registered;learner"
473 public function testFilterUserExpLevelUnregistreredOrExperienced() {
474 $conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
477 '/\(rc_user = 0\) OR \(\(user_editcount >= 500\) AND \(user_registration <= \'[^\']+\'\)\)/',
479 "rc conditions: userExpLevel=unregistered;experienced"
483 public function testFilterUserExpLevel() {
485 $this->setMwGlobals( [
486 'wgLearnerEdits' => 10,
487 'wgLearnerMemberSince' => 4,
488 'wgExperiencedUserEdits' => 500,
489 'wgExperiencedUserMemberSince' => 30,
492 $this->createUsers( [
493 'Newcomer1' => [ 'edits' => 2, 'days' => 2 ],
494 'Newcomer2' => [ 'edits' => 12, 'days' => 3 ],
495 'Newcomer3' => [ 'edits' => 8, 'days' => 5 ],
496 'Learner1' => [ 'edits' => 15, 'days' => 10 ],
497 'Learner2' => [ 'edits' => 450, 'days' => 20 ],
498 'Learner3' => [ 'edits' => 460, 'days' => 33 ],
499 'Learner4' => [ 'edits' => 525, 'days' => 28 ],
500 'Experienced1' => [ 'edits' => 538, 'days' => 33 ],
504 $this->assertArrayEquals(
505 [ 'Newcomer1', 'Newcomer2', 'Newcomer3' ],
506 $this->fetchUsers( [ 'newcomer' ], $now )
509 // newcomers and learner
510 $this->assertArrayEquals(
512 'Newcomer1', 'Newcomer2', 'Newcomer3',
513 'Learner1', 'Learner2', 'Learner3', 'Learner4',
515 $this->fetchUsers( [ 'newcomer', 'learner' ], $now )
518 // newcomers and more learner
519 $this->assertArrayEquals(
521 'Newcomer1', 'Newcomer2', 'Newcomer3',
524 $this->fetchUsers( [ 'newcomer', 'experienced' ], $now )
528 $this->assertArrayEquals(
529 [ 'Learner1', 'Learner2', 'Learner3', 'Learner4' ],
530 $this->fetchUsers( [ 'learner' ], $now )
533 // more experienced only
534 $this->assertArrayEquals(
536 $this->fetchUsers( [ 'experienced' ], $now )
539 // learner and more experienced
540 $this->assertArrayEquals(
542 'Learner1', 'Learner2', 'Learner3', 'Learner4',
545 $this->fetchUsers( [ 'learner', 'experienced' ], $now ),
546 'Learner and more experienced'
550 private function createUsers( $specs, $now ) {
551 $dbw = wfGetDB( DB_MASTER
);
552 foreach ( $specs as $name => $spec ) {
556 'editcount' => $spec['edits'],
557 'registration' => $dbw->timestamp( $this->daysAgo( $spec['days'], $now ) ),
564 private function fetchUsers( $filters, $now ) {
573 call_user_func_array(
574 [ $this->changesListSpecialPage
, 'filterOnUserExperienceLevel' ],
576 get_class( $this->changesListSpecialPage
),
577 $this->changesListSpecialPage
->getContext(),
578 $this->changesListSpecialPage
->getDB(),
589 $result = wfGetDB( DB_MASTER
)->select(
592 array_filter( $conds ) +
[ 'user_email' => 'ut' ]
596 foreach ( $result as $row ) {
597 $usernames[] = $row->user_name
;
603 private function daysAgo( $days, $now ) {
604 $secondsPerDay = 86400;
605 return $now - $days * $secondsPerDay;
608 public function testGetFilterGroupDefinitionFromLegacyCustomFilters() {
611 'msg' => 'showhidefoo',
616 'msg' => 'showhidebar',
623 'name' => 'unstructured',
624 'class' => ChangesListBooleanFilterGroup
::class,
629 'showHide' => 'showhidefoo',
634 'showHide' => 'showhidebar',
639 $this->changesListSpecialPage
->getFilterGroupDefinitionFromLegacyCustomFilters(
645 public function testGetStructuredFilterJsData() {
646 $this->changesListSpecialPage
->filterGroups
= [];
650 'name' => 'gub-group',
651 'title' => 'gub-group-title',
652 'class' => ChangesListBooleanFilterGroup
::class,
656 'label' => 'foo-label',
657 'description' => 'foo-description',
659 'showHide' => 'showhidefoo',
664 'label' => 'bar-label',
665 'description' => 'bar-description',
673 'name' => 'des-group',
674 'title' => 'des-group-title',
675 'class' => ChangesListStringOptionsFilterGroup
::class,
676 'isFullCoverage' => true,
680 'label' => 'grault-label',
681 'description' => 'grault-description',
685 'label' => 'garply-label',
686 'description' => 'garply-description',
689 'queryCallable' => function () {
691 'default' => ChangesListStringOptionsFilterGroup
::NONE
,
695 'name' => 'unstructured',
696 'class' => ChangesListBooleanFilterGroup
::class,
699 'name' => 'hidethud',
700 'showHide' => 'showhidethud',
706 'showHide' => 'showhidemos',
714 $this->changesListSpecialPage
->registerFiltersFromDefinitions( $definition );
716 $this->assertArrayEquals(
718 // Filters that only display in the unstructured UI are
719 // are not included, and neither are groups that would
720 // be empty due to the above.
723 'name' => 'gub-group',
724 'title' => 'gub-group-title',
725 'type' => ChangesListBooleanFilterGroup
::TYPE
,
730 'label' => 'bar-label',
731 'description' => 'bar-description',
740 'label' => 'foo-label',
741 'description' => 'foo-description',
749 'fullCoverage' => true,
754 'name' => 'des-group',
755 'title' => 'des-group-title',
756 'type' => ChangesListStringOptionsFilterGroup
::TYPE
,
758 'fullCoverage' => true,
762 'label' => 'grault-label',
763 'description' => 'grault-description',
771 'label' => 'garply-label',
772 'description' => 'garply-description',
781 'default' => ChangesListStringOptionsFilterGroup
::NONE
,
792 'grault-description',
794 'garply-description',
797 $this->changesListSpecialPage
->getStructuredFilterJsData(),
798 /** ordered= */ false,
803 public function provideParseParameters() {
805 [ 'hidebots', [ 'hidebots' => true ] ],
807 [ 'bots', [ 'hidebots' => false ] ],
809 [ 'hideminor', [ 'hideminor' => true ] ],
811 [ 'minor', [ 'hideminor' => false ] ],
813 [ 'hidemajor', [ 'hidemajor' => true ] ],
815 [ 'hideliu', [ 'hideliu' => true ] ],
817 [ 'hidepatrolled', [ 'hidepatrolled' => true ] ],
819 [ 'hideunpatrolled', [ 'hideunpatrolled' => true ] ],
821 [ 'hideanons', [ 'hideanons' => true ] ],
823 [ 'hidemyself', [ 'hidemyself' => true ] ],
825 [ 'hidebyothers', [ 'hidebyothers' => true ] ],
827 [ 'hidehumans', [ 'hidehumans' => true ] ],
829 [ 'hidepageedits', [ 'hidepageedits' => true ] ],
831 [ 'pagedits', [ 'hidepageedits' => false ] ],
833 [ 'hidenewpages', [ 'hidenewpages' => true ] ],
835 [ 'hidecategorization', [ 'hidecategorization' => true ] ],
837 [ 'hidelog', [ 'hidelog' => true ] ],
840 'userExpLevel=learner;experienced',
842 'userExpLevel' => 'learner;experienced'
846 // A few random combos
848 'bots,hideliu,hidemyself',
852 'hidemyself' => true,
857 'minor,hideanons,categorization',
859 'hideminor' => false,
861 'hidecategorization' => false,
866 'hidehumans,bots,hidecategorization',
868 'hidehumans' => true,
870 'hidecategorization' => true,
875 'hidemyself,userExpLevel=newcomer;learner,hideminor',
877 'hidemyself' => true,
879 'userExpLevel' => 'newcomer;learner',
885 public function provideGetFilterConflicts() {
889 "expectedConflicts" => false,
894 "userExpLevel" => "newcomer",
896 "expectedConflicts" => false,
901 "userExpLevel" => "learner",
903 "expectedConflicts" => false,
908 "hidenewpages" => true,
909 "hidepageedits" => true,
910 "hidecategorization" => false,
912 "hideWikidata" => true,
914 "expectedConflicts" => true,
919 "hidenewpages" => false,
920 "hidepageedits" => true,
921 "hidecategorization" => false,
923 "hideWikidata" => true,
925 "expectedConflicts" => true,
930 "hidenewpages" => false,
931 "hidepageedits" => false,
932 "hidecategorization" => true,
934 "hideWikidata" => true,
936 "expectedConflicts" => false,
941 "hidenewpages" => true,
942 "hidepageedits" => true,
943 "hidecategorization" => false,
945 "hideWikidata" => true,
947 "expectedConflicts" => false,
953 * @dataProvider provideGetFilterConflicts
955 public function testGetFilterConflicts( $parameters, $expectedConflicts ) {
956 $context = new RequestContext
;
957 $context->setRequest( new FauxRequest( $parameters ) );
958 $this->changesListSpecialPage
->setContext( $context );
962 $this->changesListSpecialPage
->areFiltersInConflict()
966 public function validateOptionsProvider() {
969 [ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 1 ],
971 [ 'hideliu' => 1, 'hidebots' => 1, ],
975 [ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 0 ],
977 [ 'hidebots' => 0, 'hidehumans' => 1 ],
981 [ 'hidemyself' => 1, 'hidebyothers' => 1 ],
986 [ 'hidebots' => 1, 'hidehumans' => 1 ],
991 [ 'hidepatrolled' => 1, 'hideunpatrolled' => 1 ],
996 [ 'hideminor' => 1, 'hidemajor' => 1 ],
1002 [ 'hidepageedits' => 1, 'hidenewpages' => 1, 'hidecategorization' => 1, 'hidelog' => 1, ],