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() {
399 $this->setMwGlobals( [
400 'wgLearnerEdits' => 10,
401 'wgLearnerMemberSince' => 4,
402 'wgExperiencedUserEdits' => 500,
403 'wgExperiencedUserMemberSince' => 30,
406 $this->createUsers( [
407 'Newcomer1' => [ 'edits' => 2, 'days' => 2 ],
408 'Newcomer2' => [ 'edits' => 12, 'days' => 3 ],
409 'Newcomer3' => [ 'edits' => 8, 'days' => 5 ],
410 'Learner1' => [ 'edits' => 15, 'days' => 10 ],
411 'Learner2' => [ 'edits' => 450, 'days' => 20 ],
412 'Learner3' => [ 'edits' => 460, 'days' => 33 ],
413 'Learner4' => [ 'edits' => 525, 'days' => 28 ],
414 'Experienced1' => [ 'edits' => 538, 'days' => 33 ],
418 $this->assertArrayEquals(
419 [ 'Newcomer1', 'Newcomer2', 'Newcomer3' ],
420 $this->fetchUsers( [ 'newcomer' ] )
423 // newcomers and learner
424 $this->assertArrayEquals(
426 'Newcomer1', 'Newcomer2', 'Newcomer3',
427 'Learner1', 'Learner2', 'Learner3', 'Learner4',
429 $this->fetchUsers( [ 'newcomer', 'learner' ] )
432 // newcomers and more learner
433 $this->assertArrayEquals(
435 'Newcomer1', 'Newcomer2', 'Newcomer3',
438 $this->fetchUsers( [ 'newcomer', 'experienced' ] )
442 $this->assertArrayEquals(
443 [ 'Learner1', 'Learner2', 'Learner3', 'Learner4' ],
444 $this->fetchUsers( [ 'learner' ] )
447 // more experienced only
448 $this->assertArrayEquals(
450 $this->fetchUsers( [ 'experienced' ] )
453 // learner and more experienced
454 $this->assertArrayEquals(
456 'Learner1', 'Learner2', 'Learner3', 'Learner4',
459 $this->fetchUsers( [ 'learner', 'experienced' ] ),
460 'Learner and more experienced'
463 // newcomers, learner, and more experienced
464 // TOOD: Fix test. This needs to test that anons are excluded,
465 // and right now the join fails.
466 /* $this->assertArrayEquals( */
468 /* 'Newcomer1', 'Newcomer2', 'Newcomer3', */
469 /* 'Learner1', 'Learner2', 'Learner3', 'Learner4', */
470 /* 'Experienced1', */
472 /* $this->fetchUsers( [ 'newcomer', 'learner', 'experienced' ] ) */
476 private function createUsers( $specs ) {
477 $dbw = wfGetDB( DB_MASTER
);
478 foreach ( $specs as $name => $spec ) {
482 'editcount' => $spec['edits'],
483 'registration' => $dbw->timestamp( $this->daysAgo( $spec['days'] ) ),
490 private function fetchUsers( $filters ) {
499 call_user_func_array(
500 [ $this->changesListSpecialPage
, 'filterOnUserExperienceLevel' ],
502 get_class( $this->changesListSpecialPage
),
503 $this->changesListSpecialPage
->getContext(),
504 $this->changesListSpecialPage
->getDB(),
514 $result = wfGetDB( DB_MASTER
)->select(
517 array_filter( $conds ) +
[ 'user_email' => 'ut' ]
521 foreach ( $result as $row ) {
522 $usernames[] = $row->user_name
;
528 private function daysAgo( $days ) {
529 $secondsPerDay = 86400;
530 return time() - $days * $secondsPerDay;
533 public function testGetFilterGroupDefinitionFromLegacyCustomFilters() {
536 'msg' => 'showhidefoo',
541 'msg' => 'showhidebar',
548 'name' => 'unstructured',
549 'class' => ChangesListBooleanFilterGroup
::class,
554 'showHide' => 'showhidefoo',
559 'showHide' => 'showhidebar',
564 $this->changesListSpecialPage
->getFilterGroupDefinitionFromLegacyCustomFilters(
570 public function testGetStructuredFilterJsData() {
573 'name' => 'gub-group',
574 'title' => 'gub-group-title',
575 'class' => ChangesListBooleanFilterGroup
::class,
579 'label' => 'foo-label',
580 'description' => 'foo-description',
582 'showHide' => 'showhidefoo',
587 'label' => 'bar-label',
588 'description' => 'bar-description',
596 'name' => 'des-group',
597 'title' => 'des-group-title',
598 'class' => ChangesListStringOptionsFilterGroup
::class,
599 'isFullCoverage' => true,
603 'label' => 'grault-label',
604 'description' => 'grault-description',
608 'label' => 'garply-label',
609 'description' => 'garply-description',
612 'queryCallable' => function () {
614 'default' => ChangesListStringOptionsFilterGroup
::NONE
,
618 'name' => 'unstructured',
619 'class' => ChangesListBooleanFilterGroup
::class,
622 'name' => 'hidethud',
623 'showHide' => 'showhidethud',
629 'showHide' => 'showhidemos',
637 $this->changesListSpecialPage
->registerFiltersFromDefinitions( $definition );
639 $this->assertArrayEquals(
641 // Filters that only display in the unstructured UI are
642 // are not included, and neither are groups that would
643 // be empty due to the above.
646 'name' => 'gub-group',
647 'title' => 'gub-group-title',
648 'type' => ChangesListBooleanFilterGroup
::TYPE
,
653 'label' => 'bar-label',
654 'description' => 'bar-description',
663 'label' => 'foo-label',
664 'description' => 'foo-description',
672 'fullCoverage' => true,
677 'name' => 'des-group',
678 'title' => 'des-group-title',
679 'type' => ChangesListStringOptionsFilterGroup
::TYPE
,
681 'fullCoverage' => true,
685 'label' => 'grault-label',
686 'description' => 'grault-description',
694 'label' => 'garply-label',
695 'description' => 'garply-description',
704 'default' => ChangesListStringOptionsFilterGroup
::NONE
,
715 'grault-description',
717 'garply-description',
720 $this->changesListSpecialPage
->getStructuredFilterJsData(),
721 /** ordered= */ false,
726 public function provideParseParameters() {
728 [ 'hidebots', [ 'hidebots' => true ] ],
730 [ 'bots', [ 'hidebots' => false ] ],
732 [ 'hideminor', [ 'hideminor' => true ] ],
734 [ 'minor', [ 'hideminor' => false ] ],
736 [ 'hidemajor', [ 'hidemajor' => true ] ],
738 [ 'hideliu', [ 'hideliu' => true ] ],
740 [ 'hidepatrolled', [ 'hidepatrolled' => true ] ],
742 [ 'hideunpatrolled', [ 'hideunpatrolled' => true ] ],
744 [ 'hideanons', [ 'hideanons' => true ] ],
746 [ 'hidemyself', [ 'hidemyself' => true ] ],
748 [ 'hidebyothers', [ 'hidebyothers' => true ] ],
750 [ 'hidehumans', [ 'hidehumans' => true ] ],
752 [ 'hidepageedits', [ 'hidepageedits' => true ] ],
754 [ 'pagedits', [ 'hidepageedits' => false ] ],
756 [ 'hidenewpages', [ 'hidenewpages' => true ] ],
758 [ 'hidecategorization', [ 'hidecategorization' => true ] ],
760 [ 'hidelog', [ 'hidelog' => true ] ],
763 'userExpLevel=learner;experienced',
765 'userExpLevel' => 'learner;experienced'
769 // A few random combos
771 'bots,hideliu,hidemyself',
775 'hidemyself' => true,
780 'minor,hideanons,categorization',
782 'hideminor' => false,
784 'hidecategorization' => false,
789 'hidehumans,bots,hidecategorization',
791 'hidehumans' => true,
793 'hidecategorization' => true,
798 'hidemyself,userExpLevel=newcomer;learner,hideminor',
800 'hidemyself' => true,
802 'userExpLevel' => 'newcomer;learner',
808 public function provideGetFilterConflicts() {
812 "expectedConflicts" => false,
817 "userExpLevel" => "newcomer",
819 "expectedConflicts" => true,
824 "userExpLevel" => "learner",
826 "expectedConflicts" => false,
831 "hidenewpages" => true,
832 "hidepageedits" => true,
833 "hidecategorization" => false,
835 "hideWikidata" => true,
837 "expectedConflicts" => true,
842 "hidenewpages" => false,
843 "hidepageedits" => true,
844 "hidecategorization" => false,
846 "hideWikidata" => true,
848 "expectedConflicts" => true,
853 "hidenewpages" => false,
854 "hidepageedits" => false,
855 "hidecategorization" => true,
857 "hideWikidata" => true,
859 "expectedConflicts" => false,
864 "hidenewpages" => true,
865 "hidepageedits" => true,
866 "hidecategorization" => false,
868 "hideWikidata" => true,
870 "expectedConflicts" => false,
876 * @dataProvider provideGetFilterConflicts
878 public function testGetFilterConflicts( $parameters, $expectedConflicts ) {
879 $context = new RequestContext
;
880 $context->setRequest( new FauxRequest( $parameters ) );
881 $this->changesListSpecialPage
->setContext( $context );
885 $this->changesListSpecialPage
->areFiltersInConflict()