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 no options (aka default setting)"
116 public function testRcNsFilterInversion() {
117 $this->assertConditions(
119 "rc_namespace != '0'",
122 'namespace' => NS_MAIN
,
125 "rc conditions with namespace inverted"
131 * @dataProvider provideNamespacesAssociations
133 public function testRcNsFilterAssociation( $ns1, $ns2 ) {
134 $this->assertConditions(
136 "(rc_namespace = '$ns1' OR rc_namespace = '$ns2')",
142 "rc conditions with namespace inverted"
148 * @dataProvider provideNamespacesAssociations
150 public function testRcNsFilterAssociationWithInversion( $ns1, $ns2 ) {
151 $this->assertConditions(
153 "(rc_namespace != '$ns1' AND rc_namespace != '$ns2')",
160 "rc conditions with namespace inverted"
165 * Provides associated namespaces to test recent changes
166 * namespaces association filtering.
168 public static function provideNamespacesAssociations() {
169 return [ # (NS => Associated_NS)
170 [ NS_MAIN
, NS_TALK
],
171 [ NS_TALK
, NS_MAIN
],
175 public function testRcHidemyselfFilter() {
176 $user = $this->getTestUser()->getUser();
177 $this->assertConditions(
179 "rc_user_text != '{$user->getName()}'",
184 "rc conditions: hidemyself=1 (logged in)",
188 $user = User
::newFromName( '10.11.12.13', false );
189 $this->assertConditions(
191 "rc_user_text != '10.11.12.13'",
196 "rc conditions: hidemyself=1 (anon)",
201 public function testRcHidebyothersFilter() {
202 $user = $this->getTestUser()->getUser();
203 $this->assertConditions(
205 "rc_user_text = '{$user->getName()}'",
210 "rc conditions: hidebyothers=1 (logged in)",
214 $user = User
::newFromName( '10.11.12.13', false );
215 $this->assertConditions(
217 "rc_user_text = '10.11.12.13'",
222 "rc conditions: hidebyothers=1 (anon)",
227 public function testRcHidemyselfHidebyothersFilter() {
228 $user = $this->getTestUser()->getUser();
229 $this->assertConditions(
231 "rc_user_text != '{$user->getName()}'",
232 "rc_user_text = '{$user->getName()}'",
238 "rc conditions: hidemyself=1 hidebyothers=1 (logged in)",
243 public function testRcHidepageedits() {
244 $this->assertConditions(
249 'hidepageedits' => 1,
251 "rc conditions: hidepageedits=1"
255 public function testRcHidenewpages() {
256 $this->assertConditions(
263 "rc conditions: hidenewpages=1"
267 public function testRcHidelog() {
268 $this->assertConditions(
275 "rc conditions: hidelog=1"
279 public function testRcHidehumans() {
280 $this->assertConditions(
288 "rc conditions: hidebots=0 hidehumans=1"
292 public function testRcHidepatrolledDisabledFilter() {
293 $user = $this->getTestUser()->getUser();
294 $this->assertConditions(
298 'hidepatrolled' => 1,
300 "rc conditions: hidepatrolled=1 (user not allowed)",
305 public function testRcHideunpatrolledDisabledFilter() {
306 $user = $this->getTestUser()->getUser();
307 $this->assertConditions(
311 'hideunpatrolled' => 1,
313 "rc conditions: hideunpatrolled=1 (user not allowed)",
317 public function testRcHidepatrolledFilter() {
318 $user = $this->getTestSysop()->getUser();
319 $this->assertConditions(
324 'hidepatrolled' => 1,
326 "rc conditions: hidepatrolled=1",
331 public function testRcHideunpatrolledFilter() {
332 $user = $this->getTestSysop()->getUser();
333 $this->assertConditions(
338 'hideunpatrolled' => 1,
340 "rc conditions: hideunpatrolled=1",
345 public function testRcHideminorFilter() {
346 $this->assertConditions(
353 "rc conditions: hideminor=1"
357 public function testRcHidemajorFilter() {
358 $this->assertConditions(
365 "rc conditions: hidemajor=1"
369 public function testRcHidepatrolledHideunpatrolledFilter() {
370 $user = $this->getTestSysop()->getUser();
371 $this->assertConditions(
377 'hidepatrolled' => 1,
378 'hideunpatrolled' => 1,
380 "rc conditions: hidepatrolled=1 hideunpatrolled=1",
385 public function testHideCategorization() {
386 $this->assertConditions(
392 'hidecategorization' => 1
394 "rc conditions: hidecategorization=1"
398 public function testFilterUserExpLevel() {
400 $this->setMwGlobals( [
401 'wgLearnerEdits' => 10,
402 'wgLearnerMemberSince' => 4,
403 'wgExperiencedUserEdits' => 500,
404 'wgExperiencedUserMemberSince' => 30,
407 $this->createUsers( [
408 'Newcomer1' => [ 'edits' => 2, 'days' => 2 ],
409 'Newcomer2' => [ 'edits' => 12, 'days' => 3 ],
410 'Newcomer3' => [ 'edits' => 8, 'days' => 5 ],
411 'Learner1' => [ 'edits' => 15, 'days' => 10 ],
412 'Learner2' => [ 'edits' => 450, 'days' => 20 ],
413 'Learner3' => [ 'edits' => 460, 'days' => 33 ],
414 'Learner4' => [ 'edits' => 525, 'days' => 28 ],
415 'Experienced1' => [ 'edits' => 538, 'days' => 33 ],
419 $this->assertArrayEquals(
420 [ 'Newcomer1', 'Newcomer2', 'Newcomer3' ],
421 $this->fetchUsers( [ 'newcomer' ], $now )
424 // newcomers and learner
425 $this->assertArrayEquals(
427 'Newcomer1', 'Newcomer2', 'Newcomer3',
428 'Learner1', 'Learner2', 'Learner3', 'Learner4',
430 $this->fetchUsers( [ 'newcomer', 'learner' ], $now )
433 // newcomers and more learner
434 $this->assertArrayEquals(
436 'Newcomer1', 'Newcomer2', 'Newcomer3',
439 $this->fetchUsers( [ 'newcomer', 'experienced' ], $now )
443 $this->assertArrayEquals(
444 [ 'Learner1', 'Learner2', 'Learner3', 'Learner4' ],
445 $this->fetchUsers( [ 'learner' ], $now )
448 // more experienced only
449 $this->assertArrayEquals(
451 $this->fetchUsers( [ 'experienced' ], $now )
454 // learner and more experienced
455 $this->assertArrayEquals(
457 'Learner1', 'Learner2', 'Learner3', 'Learner4',
460 $this->fetchUsers( [ 'learner', 'experienced' ], $now ),
461 'Learner and more experienced'
464 // newcomers, learner, and more experienced
465 // TOOD: Fix test. This needs to test that anons are excluded,
466 // and right now the join fails.
467 /* $this->assertArrayEquals( */
469 /* 'Newcomer1', 'Newcomer2', 'Newcomer3', */
470 /* 'Learner1', 'Learner2', 'Learner3', 'Learner4', */
471 /* 'Experienced1', */
473 /* $this->fetchUsers( [ 'newcomer', 'learner', 'experienced' ], $now ) */
477 private function createUsers( $specs, $now ) {
478 $dbw = wfGetDB( DB_MASTER
);
479 foreach ( $specs as $name => $spec ) {
483 'editcount' => $spec['edits'],
484 'registration' => $dbw->timestamp( $this->daysAgo( $spec['days'], $now ) ),
491 private function fetchUsers( $filters, $now ) {
500 call_user_func_array(
501 [ $this->changesListSpecialPage
, 'filterOnUserExperienceLevel' ],
503 get_class( $this->changesListSpecialPage
),
504 $this->changesListSpecialPage
->getContext(),
505 $this->changesListSpecialPage
->getDB(),
516 $result = wfGetDB( DB_MASTER
)->select(
519 array_filter( $conds ) +
[ 'user_email' => 'ut' ]
523 foreach ( $result as $row ) {
524 $usernames[] = $row->user_name
;
530 private function daysAgo( $days, $now ) {
531 $secondsPerDay = 86400;
532 return $now - $days * $secondsPerDay;
535 public function testGetFilterGroupDefinitionFromLegacyCustomFilters() {
538 'msg' => 'showhidefoo',
543 'msg' => 'showhidebar',
550 'name' => 'unstructured',
551 'class' => ChangesListBooleanFilterGroup
::class,
556 'showHide' => 'showhidefoo',
561 'showHide' => 'showhidebar',
566 $this->changesListSpecialPage
->getFilterGroupDefinitionFromLegacyCustomFilters(
572 public function testGetStructuredFilterJsData() {
575 'name' => 'gub-group',
576 'title' => 'gub-group-title',
577 'class' => ChangesListBooleanFilterGroup
::class,
581 'label' => 'foo-label',
582 'description' => 'foo-description',
584 'showHide' => 'showhidefoo',
589 'label' => 'bar-label',
590 'description' => 'bar-description',
598 'name' => 'des-group',
599 'title' => 'des-group-title',
600 'class' => ChangesListStringOptionsFilterGroup
::class,
601 'isFullCoverage' => true,
605 'label' => 'grault-label',
606 'description' => 'grault-description',
610 'label' => 'garply-label',
611 'description' => 'garply-description',
614 'queryCallable' => function () {
616 'default' => ChangesListStringOptionsFilterGroup
::NONE
,
620 'name' => 'unstructured',
621 'class' => ChangesListBooleanFilterGroup
::class,
624 'name' => 'hidethud',
625 'showHide' => 'showhidethud',
631 'showHide' => 'showhidemos',
639 $this->changesListSpecialPage
->registerFiltersFromDefinitions( $definition );
641 $this->assertArrayEquals(
643 // Filters that only display in the unstructured UI are
644 // are not included, and neither are groups that would
645 // be empty due to the above.
648 'name' => 'gub-group',
649 'title' => 'gub-group-title',
650 'type' => ChangesListBooleanFilterGroup
::TYPE
,
655 'label' => 'bar-label',
656 'description' => 'bar-description',
665 'label' => 'foo-label',
666 'description' => 'foo-description',
674 'fullCoverage' => true,
679 'name' => 'des-group',
680 'title' => 'des-group-title',
681 'type' => ChangesListStringOptionsFilterGroup
::TYPE
,
683 'fullCoverage' => true,
687 'label' => 'grault-label',
688 'description' => 'grault-description',
696 'label' => 'garply-label',
697 'description' => 'garply-description',
706 'default' => ChangesListStringOptionsFilterGroup
::NONE
,
717 'grault-description',
719 'garply-description',
722 $this->changesListSpecialPage
->getStructuredFilterJsData(),
723 /** ordered= */ false,
728 public function provideParseParameters() {
730 [ 'hidebots', [ 'hidebots' => true ] ],
732 [ 'bots', [ 'hidebots' => false ] ],
734 [ 'hideminor', [ 'hideminor' => true ] ],
736 [ 'minor', [ 'hideminor' => false ] ],
738 [ 'hidemajor', [ 'hidemajor' => true ] ],
740 [ 'hideliu', [ 'hideliu' => true ] ],
742 [ 'hidepatrolled', [ 'hidepatrolled' => true ] ],
744 [ 'hideunpatrolled', [ 'hideunpatrolled' => true ] ],
746 [ 'hideanons', [ 'hideanons' => true ] ],
748 [ 'hidemyself', [ 'hidemyself' => true ] ],
750 [ 'hidebyothers', [ 'hidebyothers' => true ] ],
752 [ 'hidehumans', [ 'hidehumans' => true ] ],
754 [ 'hidepageedits', [ 'hidepageedits' => true ] ],
756 [ 'pagedits', [ 'hidepageedits' => false ] ],
758 [ 'hidenewpages', [ 'hidenewpages' => true ] ],
760 [ 'hidecategorization', [ 'hidecategorization' => true ] ],
762 [ 'hidelog', [ 'hidelog' => true ] ],
765 'userExpLevel=learner;experienced',
767 'userExpLevel' => 'learner;experienced'
771 // A few random combos
773 'bots,hideliu,hidemyself',
777 'hidemyself' => true,
782 'minor,hideanons,categorization',
784 'hideminor' => false,
786 'hidecategorization' => false,
791 'hidehumans,bots,hidecategorization',
793 'hidehumans' => true,
795 'hidecategorization' => true,
800 'hidemyself,userExpLevel=newcomer;learner,hideminor',
802 'hidemyself' => true,
804 'userExpLevel' => 'newcomer;learner',
810 public function provideGetFilterConflicts() {
814 "expectedConflicts" => false,
819 "userExpLevel" => "newcomer",
821 "expectedConflicts" => true,
826 "userExpLevel" => "learner",
828 "expectedConflicts" => false,
833 "hidenewpages" => true,
834 "hidepageedits" => true,
835 "hidecategorization" => false,
837 "hideWikidata" => true,
839 "expectedConflicts" => true,
844 "hidenewpages" => false,
845 "hidepageedits" => true,
846 "hidecategorization" => false,
848 "hideWikidata" => true,
850 "expectedConflicts" => true,
855 "hidenewpages" => false,
856 "hidepageedits" => false,
857 "hidecategorization" => true,
859 "hideWikidata" => true,
861 "expectedConflicts" => false,
866 "hidenewpages" => true,
867 "hidepageedits" => true,
868 "hidecategorization" => false,
870 "hideWikidata" => true,
872 "expectedConflicts" => false,
878 * @dataProvider provideGetFilterConflicts
880 public function testGetFilterConflicts( $parameters, $expectedConflicts ) {
881 $context = new RequestContext
;
882 $context->setRequest( new FauxRequest( $parameters ) );
883 $this->changesListSpecialPage
->setContext( $context );
887 $this->changesListSpecialPage
->areFiltersInConflict()