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 setUp() {
22 $this->changesListSpecialPage
= $this->getPage();
25 protected function getPage() {
26 return TestingAccessWrapper
::newFromObject(
27 $this->getMockForAbstractClass(
28 'ChangesListSpecialPage',
30 'ChangesListSpecialPage',
37 /** helper to test SpecialRecentchanges::buildMainQueryConds() */
38 private function assertConditions(
40 $requestOptions = null,
44 $context = new RequestContext
;
45 $context->setRequest( new FauxRequest( $requestOptions ) );
47 $context->setUser( $user );
50 $this->changesListSpecialPage
->setContext( $context );
51 $formOptions = $this->changesListSpecialPage
->setup( null );
53 # Filter out rc_timestamp conditions which depends on the test runtime
54 # This condition is not needed as of march 2, 2011 -- hashar
55 # @todo FIXME: Find a way to generate the correct rc_timestamp
59 $queryConditions = [];
64 [ $this->changesListSpecialPage
, 'buildQuery' ],
75 $queryConditions = array_filter(
77 'ChangesListSpecialPageTest::filterOutRcTimestampCondition'
81 self
::normalizeCondition( $expected ),
82 self
::normalizeCondition( $queryConditions ),
87 private static function normalizeCondition( $conds ) {
88 $normalized = array_map(
90 return is_numeric( $k ) ?
$v : "$k = $v";
99 /** return false if condition begin with 'rc_timestamp ' */
100 private static function filterOutRcTimestampCondition( $var ) {
101 return ( false === strpos( $var, 'rc_timestamp ' ) );
104 public function testRcNsFilter() {
105 $this->assertConditions(
107 "rc_namespace = '0'",
110 'namespace' => NS_MAIN
,
112 "rc conditions with one namespace"
116 public function testRcNsFilterInversion() {
117 $this->assertConditions(
119 "rc_namespace != '0'",
122 'namespace' => NS_MAIN
,
125 "rc conditions with namespace inverted"
129 public function testRcNsFilterMultiple() {
130 $this->assertConditions(
132 "rc_namespace IN ('1','2','3')",
135 'namespace' => '1,2,3',
137 "rc conditions with multiple namespaces"
141 public function testRcNsFilterMultipleAssociated() {
142 $this->assertConditions(
144 "rc_namespace IN ('0','1','4','5','6','7')",
147 'namespace' => '1,4,7',
150 "rc conditions with multiple namespaces and associated"
154 public function testRcNsFilterMultipleAssociatedInvert() {
155 $this->assertConditions(
157 "rc_namespace NOT IN ('2','3','8','9')",
160 'namespace' => '2,3,9',
164 "rc conditions with multiple namespaces, associated and inverted"
168 public function testRcNsFilterMultipleInvert() {
169 $this->assertConditions(
171 "rc_namespace NOT IN ('1','2','3')",
174 'namespace' => '1,2,3',
177 "rc conditions with multiple namespaces inverted"
181 public function testRcHidemyselfFilter() {
182 $user = $this->getTestUser()->getUser();
183 $this->assertConditions(
185 "rc_user_text != '{$user->getName()}'",
190 "rc conditions: hidemyself=1 (logged in)",
194 $user = User
::newFromName( '10.11.12.13', false );
195 $this->assertConditions(
197 "rc_user_text != '10.11.12.13'",
202 "rc conditions: hidemyself=1 (anon)",
207 public function testRcHidebyothersFilter() {
208 $user = $this->getTestUser()->getUser();
209 $this->assertConditions(
211 "rc_user_text = '{$user->getName()}'",
216 "rc conditions: hidebyothers=1 (logged in)",
220 $user = User
::newFromName( '10.11.12.13', false );
221 $this->assertConditions(
223 "rc_user_text = '10.11.12.13'",
228 "rc conditions: hidebyothers=1 (anon)",
233 public function testRcHidemyselfHidebyothersFilter() {
234 $user = $this->getTestUser()->getUser();
235 $this->assertConditions(
237 "rc_user_text != '{$user->getName()}'",
238 "rc_user_text = '{$user->getName()}'",
244 "rc conditions: hidemyself=1 hidebyothers=1 (logged in)",
249 public function testRcHidepageedits() {
250 $this->assertConditions(
255 'hidepageedits' => 1,
257 "rc conditions: hidepageedits=1"
261 public function testRcHidenewpages() {
262 $this->assertConditions(
269 "rc conditions: hidenewpages=1"
273 public function testRcHidelog() {
274 $this->assertConditions(
281 "rc conditions: hidelog=1"
285 public function testRcHidehumans() {
286 $this->assertConditions(
294 "rc conditions: hidebots=0 hidehumans=1"
298 public function testRcHidepatrolledDisabledFilter() {
299 $user = $this->getTestUser()->getUser();
300 $this->assertConditions(
304 'hidepatrolled' => 1,
306 "rc conditions: hidepatrolled=1 (user not allowed)",
311 public function testRcHideunpatrolledDisabledFilter() {
312 $user = $this->getTestUser()->getUser();
313 $this->assertConditions(
317 'hideunpatrolled' => 1,
319 "rc conditions: hideunpatrolled=1 (user not allowed)",
323 public function testRcHidepatrolledFilter() {
324 $user = $this->getTestSysop()->getUser();
325 $this->assertConditions(
330 'hidepatrolled' => 1,
332 "rc conditions: hidepatrolled=1",
337 public function testRcHideunpatrolledFilter() {
338 $user = $this->getTestSysop()->getUser();
339 $this->assertConditions(
344 'hideunpatrolled' => 1,
346 "rc conditions: hideunpatrolled=1",
351 public function testRcHideminorFilter() {
352 $this->assertConditions(
359 "rc conditions: hideminor=1"
363 public function testRcHidemajorFilter() {
364 $this->assertConditions(
371 "rc conditions: hidemajor=1"
375 public function testRcHidepatrolledHideunpatrolledFilter() {
376 $user = $this->getTestSysop()->getUser();
377 $this->assertConditions(
383 'hidepatrolled' => 1,
384 'hideunpatrolled' => 1,
386 "rc conditions: hidepatrolled=1 hideunpatrolled=1",
391 public function testHideCategorization() {
392 $this->assertConditions(
398 'hidecategorization' => 1
400 "rc conditions: hidecategorization=1"
404 public function testFilterUserExpLevel() {
406 $this->setMwGlobals( [
407 'wgLearnerEdits' => 10,
408 'wgLearnerMemberSince' => 4,
409 'wgExperiencedUserEdits' => 500,
410 'wgExperiencedUserMemberSince' => 30,
413 $this->createUsers( [
414 'Newcomer1' => [ 'edits' => 2, 'days' => 2 ],
415 'Newcomer2' => [ 'edits' => 12, 'days' => 3 ],
416 'Newcomer3' => [ 'edits' => 8, 'days' => 5 ],
417 'Learner1' => [ 'edits' => 15, 'days' => 10 ],
418 'Learner2' => [ 'edits' => 450, 'days' => 20 ],
419 'Learner3' => [ 'edits' => 460, 'days' => 33 ],
420 'Learner4' => [ 'edits' => 525, 'days' => 28 ],
421 'Experienced1' => [ 'edits' => 538, 'days' => 33 ],
425 $this->assertArrayEquals(
426 [ 'Newcomer1', 'Newcomer2', 'Newcomer3' ],
427 $this->fetchUsers( [ 'newcomer' ], $now )
430 // newcomers and learner
431 $this->assertArrayEquals(
433 'Newcomer1', 'Newcomer2', 'Newcomer3',
434 'Learner1', 'Learner2', 'Learner3', 'Learner4',
436 $this->fetchUsers( [ 'newcomer', 'learner' ], $now )
439 // newcomers and more learner
440 $this->assertArrayEquals(
442 'Newcomer1', 'Newcomer2', 'Newcomer3',
445 $this->fetchUsers( [ 'newcomer', 'experienced' ], $now )
449 $this->assertArrayEquals(
450 [ 'Learner1', 'Learner2', 'Learner3', 'Learner4' ],
451 $this->fetchUsers( [ 'learner' ], $now )
454 // more experienced only
455 $this->assertArrayEquals(
457 $this->fetchUsers( [ 'experienced' ], $now )
460 // learner and more experienced
461 $this->assertArrayEquals(
463 'Learner1', 'Learner2', 'Learner3', 'Learner4',
466 $this->fetchUsers( [ 'learner', 'experienced' ], $now ),
467 'Learner and more experienced'
470 // newcomers, learner, and more experienced
471 // TOOD: Fix test. This needs to test that anons are excluded,
472 // and right now the join fails.
473 /* $this->assertArrayEquals( */
475 /* 'Newcomer1', 'Newcomer2', 'Newcomer3', */
476 /* 'Learner1', 'Learner2', 'Learner3', 'Learner4', */
477 /* 'Experienced1', */
479 /* $this->fetchUsers( [ 'newcomer', 'learner', 'experienced' ], $now ) */
483 private function createUsers( $specs, $now ) {
484 $dbw = wfGetDB( DB_MASTER
);
485 foreach ( $specs as $name => $spec ) {
489 'editcount' => $spec['edits'],
490 'registration' => $dbw->timestamp( $this->daysAgo( $spec['days'], $now ) ),
497 private function fetchUsers( $filters, $now ) {
506 call_user_func_array(
507 [ $this->changesListSpecialPage
, 'filterOnUserExperienceLevel' ],
509 get_class( $this->changesListSpecialPage
),
510 $this->changesListSpecialPage
->getContext(),
511 $this->changesListSpecialPage
->getDB(),
522 $result = wfGetDB( DB_MASTER
)->select(
525 array_filter( $conds ) +
[ 'user_email' => 'ut' ]
529 foreach ( $result as $row ) {
530 $usernames[] = $row->user_name
;
536 private function daysAgo( $days, $now ) {
537 $secondsPerDay = 86400;
538 return $now - $days * $secondsPerDay;
541 public function testGetFilterGroupDefinitionFromLegacyCustomFilters() {
544 'msg' => 'showhidefoo',
549 'msg' => 'showhidebar',
556 'name' => 'unstructured',
557 'class' => ChangesListBooleanFilterGroup
::class,
562 'showHide' => 'showhidefoo',
567 'showHide' => 'showhidebar',
572 $this->changesListSpecialPage
->getFilterGroupDefinitionFromLegacyCustomFilters(
578 public function testGetStructuredFilterJsData() {
581 'name' => 'gub-group',
582 'title' => 'gub-group-title',
583 'class' => ChangesListBooleanFilterGroup
::class,
587 'label' => 'foo-label',
588 'description' => 'foo-description',
590 'showHide' => 'showhidefoo',
595 'label' => 'bar-label',
596 'description' => 'bar-description',
604 'name' => 'des-group',
605 'title' => 'des-group-title',
606 'class' => ChangesListStringOptionsFilterGroup
::class,
607 'isFullCoverage' => true,
611 'label' => 'grault-label',
612 'description' => 'grault-description',
616 'label' => 'garply-label',
617 'description' => 'garply-description',
620 'queryCallable' => function () {
622 'default' => ChangesListStringOptionsFilterGroup
::NONE
,
626 'name' => 'unstructured',
627 'class' => ChangesListBooleanFilterGroup
::class,
630 'name' => 'hidethud',
631 'showHide' => 'showhidethud',
637 'showHide' => 'showhidemos',
645 $this->changesListSpecialPage
->registerFiltersFromDefinitions( $definition );
647 $this->assertArrayEquals(
649 // Filters that only display in the unstructured UI are
650 // are not included, and neither are groups that would
651 // be empty due to the above.
654 'name' => 'gub-group',
655 'title' => 'gub-group-title',
656 'type' => ChangesListBooleanFilterGroup
::TYPE
,
661 'label' => 'bar-label',
662 'description' => 'bar-description',
671 'label' => 'foo-label',
672 'description' => 'foo-description',
680 'fullCoverage' => true,
685 'name' => 'des-group',
686 'title' => 'des-group-title',
687 'type' => ChangesListStringOptionsFilterGroup
::TYPE
,
689 'fullCoverage' => true,
693 'label' => 'grault-label',
694 'description' => 'grault-description',
702 'label' => 'garply-label',
703 'description' => 'garply-description',
712 'default' => ChangesListStringOptionsFilterGroup
::NONE
,
723 'grault-description',
725 'garply-description',
728 $this->changesListSpecialPage
->getStructuredFilterJsData(),
729 /** ordered= */ false,
734 public function provideParseParameters() {
736 [ 'hidebots', [ 'hidebots' => true ] ],
738 [ 'bots', [ 'hidebots' => false ] ],
740 [ 'hideminor', [ 'hideminor' => true ] ],
742 [ 'minor', [ 'hideminor' => false ] ],
744 [ 'hidemajor', [ 'hidemajor' => true ] ],
746 [ 'hideliu', [ 'hideliu' => true ] ],
748 [ 'hidepatrolled', [ 'hidepatrolled' => true ] ],
750 [ 'hideunpatrolled', [ 'hideunpatrolled' => true ] ],
752 [ 'hideanons', [ 'hideanons' => true ] ],
754 [ 'hidemyself', [ 'hidemyself' => true ] ],
756 [ 'hidebyothers', [ 'hidebyothers' => true ] ],
758 [ 'hidehumans', [ 'hidehumans' => true ] ],
760 [ 'hidepageedits', [ 'hidepageedits' => true ] ],
762 [ 'pagedits', [ 'hidepageedits' => false ] ],
764 [ 'hidenewpages', [ 'hidenewpages' => true ] ],
766 [ 'hidecategorization', [ 'hidecategorization' => true ] ],
768 [ 'hidelog', [ 'hidelog' => true ] ],
771 'userExpLevel=learner;experienced',
773 'userExpLevel' => 'learner;experienced'
777 // A few random combos
779 'bots,hideliu,hidemyself',
783 'hidemyself' => true,
788 'minor,hideanons,categorization',
790 'hideminor' => false,
792 'hidecategorization' => false,
797 'hidehumans,bots,hidecategorization',
799 'hidehumans' => true,
801 'hidecategorization' => true,
806 'hidemyself,userExpLevel=newcomer;learner,hideminor',
808 'hidemyself' => true,
810 'userExpLevel' => 'newcomer;learner',
816 public function provideGetFilterConflicts() {
820 "expectedConflicts" => false,
825 "userExpLevel" => "newcomer",
827 "expectedConflicts" => true,
832 "userExpLevel" => "learner",
834 "expectedConflicts" => false,
839 "hidenewpages" => true,
840 "hidepageedits" => true,
841 "hidecategorization" => false,
843 "hideWikidata" => true,
845 "expectedConflicts" => true,
850 "hidenewpages" => false,
851 "hidepageedits" => true,
852 "hidecategorization" => false,
854 "hideWikidata" => true,
856 "expectedConflicts" => true,
861 "hidenewpages" => false,
862 "hidepageedits" => false,
863 "hidecategorization" => true,
865 "hideWikidata" => true,
867 "expectedConflicts" => false,
872 "hidenewpages" => true,
873 "hidepageedits" => true,
874 "hidecategorization" => false,
876 "hideWikidata" => true,
878 "expectedConflicts" => false,
884 * @dataProvider provideGetFilterConflicts
886 public function testGetFilterConflicts( $parameters, $expectedConflicts ) {
887 $context = new RequestContext
;
888 $context->setRequest( new FauxRequest( $parameters ) );
889 $this->changesListSpecialPage
->setContext( $context );
893 $this->changesListSpecialPage
->areFiltersInConflict()