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 /** helper to test SpecialRecentchanges::buildMainQueryConds() */
41 private function assertConditions(
43 $requestOptions = null,
47 $context = new RequestContext
;
48 $context->setRequest( new FauxRequest( $requestOptions ) );
50 $context->setUser( $user );
53 $this->changesListSpecialPage
->setContext( $context );
54 $this->changesListSpecialPage
->filterGroups
= [];
55 $formOptions = $this->changesListSpecialPage
->setup( null );
57 # Filter out rc_timestamp conditions which depends on the test runtime
58 # This condition is not needed as of march 2, 2011 -- hashar
59 # @todo FIXME: Find a way to generate the correct rc_timestamp
63 $queryConditions = [];
68 [ $this->changesListSpecialPage
, 'buildQuery' ],
79 $queryConditions = array_filter(
81 'ChangesListSpecialPageTest::filterOutRcTimestampCondition'
85 self
::normalizeCondition( $expected ),
86 self
::normalizeCondition( $queryConditions ),
91 private static function normalizeCondition( $conds ) {
92 $normalized = array_map(
94 return is_numeric( $k ) ?
$v : "$k = $v";
103 /** return false if condition begin with 'rc_timestamp ' */
104 private static function filterOutRcTimestampCondition( $var ) {
105 return ( false === strpos( $var, 'rc_timestamp ' ) );
108 public function testRcNsFilter() {
109 $this->assertConditions(
111 "rc_namespace = '0'",
114 'namespace' => NS_MAIN
,
116 "rc conditions with one namespace"
120 public function testRcNsFilterInversion() {
121 $this->assertConditions(
123 "rc_namespace != '0'",
126 'namespace' => NS_MAIN
,
129 "rc conditions with namespace inverted"
133 public function testRcNsFilterMultiple() {
134 $this->assertConditions(
136 "rc_namespace IN ('1','2','3')",
139 'namespace' => '1;2;3',
141 "rc conditions with multiple namespaces"
145 public function testRcNsFilterMultipleAssociated() {
146 $this->assertConditions(
148 "rc_namespace IN ('0','1','4','5','6','7')",
151 'namespace' => '1;4;7',
154 "rc conditions with multiple namespaces and associated"
158 public function testRcNsFilterMultipleAssociatedInvert() {
159 $this->assertConditions(
161 "rc_namespace NOT IN ('2','3','8','9')",
164 'namespace' => '2;3;9',
168 "rc conditions with multiple namespaces, associated and inverted"
172 public function testRcNsFilterMultipleInvert() {
173 $this->assertConditions(
175 "rc_namespace NOT IN ('1','2','3')",
178 'namespace' => '1;2;3',
181 "rc conditions with multiple namespaces inverted"
185 public function testRcHidemyselfFilter() {
186 $user = $this->getTestUser()->getUser();
187 $this->assertConditions(
189 "rc_user_text != '{$user->getName()}'",
194 "rc conditions: hidemyself=1 (logged in)",
198 $user = User
::newFromName( '10.11.12.13', false );
199 $this->assertConditions(
201 "rc_user_text != '10.11.12.13'",
206 "rc conditions: hidemyself=1 (anon)",
211 public function testRcHidebyothersFilter() {
212 $user = $this->getTestUser()->getUser();
213 $this->assertConditions(
215 "rc_user_text = '{$user->getName()}'",
220 "rc conditions: hidebyothers=1 (logged in)",
224 $user = User
::newFromName( '10.11.12.13', false );
225 $this->assertConditions(
227 "rc_user_text = '10.11.12.13'",
232 "rc conditions: hidebyothers=1 (anon)",
237 public function testRcHidepageedits() {
238 $this->assertConditions(
243 'hidepageedits' => 1,
245 "rc conditions: hidepageedits=1"
249 public function testRcHidenewpages() {
250 $this->assertConditions(
257 "rc conditions: hidenewpages=1"
261 public function testRcHidelog() {
262 $this->assertConditions(
269 "rc conditions: hidelog=1"
273 public function testRcHidehumans() {
274 $this->assertConditions(
282 "rc conditions: hidebots=0 hidehumans=1"
286 public function testRcHidepatrolledDisabledFilter() {
287 $user = $this->getTestUser()->getUser();
288 $this->assertConditions(
292 'hidepatrolled' => 1,
294 "rc conditions: hidepatrolled=1 (user not allowed)",
299 public function testRcHideunpatrolledDisabledFilter() {
300 $user = $this->getTestUser()->getUser();
301 $this->assertConditions(
305 'hideunpatrolled' => 1,
307 "rc conditions: hideunpatrolled=1 (user not allowed)",
311 public function testRcHidepatrolledFilter() {
312 $user = $this->getTestSysop()->getUser();
313 $this->assertConditions(
318 'hidepatrolled' => 1,
320 "rc conditions: hidepatrolled=1",
325 public function testRcHideunpatrolledFilter() {
326 $user = $this->getTestSysop()->getUser();
327 $this->assertConditions(
332 'hideunpatrolled' => 1,
334 "rc conditions: hideunpatrolled=1",
339 public function testRcHideminorFilter() {
340 $this->assertConditions(
347 "rc conditions: hideminor=1"
351 public function testRcHidemajorFilter() {
352 $this->assertConditions(
359 "rc conditions: hidemajor=1"
363 public function testHideCategorization() {
364 $this->assertConditions(
370 'hidecategorization' => 1
372 "rc conditions: hidecategorization=1"
376 public function testFilterUserExpLevel() {
378 $this->setMwGlobals( [
379 'wgLearnerEdits' => 10,
380 'wgLearnerMemberSince' => 4,
381 'wgExperiencedUserEdits' => 500,
382 'wgExperiencedUserMemberSince' => 30,
385 $this->createUsers( [
386 'Newcomer1' => [ 'edits' => 2, 'days' => 2 ],
387 'Newcomer2' => [ 'edits' => 12, 'days' => 3 ],
388 'Newcomer3' => [ 'edits' => 8, 'days' => 5 ],
389 'Learner1' => [ 'edits' => 15, 'days' => 10 ],
390 'Learner2' => [ 'edits' => 450, 'days' => 20 ],
391 'Learner3' => [ 'edits' => 460, 'days' => 33 ],
392 'Learner4' => [ 'edits' => 525, 'days' => 28 ],
393 'Experienced1' => [ 'edits' => 538, 'days' => 33 ],
397 $this->assertArrayEquals(
398 [ 'Newcomer1', 'Newcomer2', 'Newcomer3' ],
399 $this->fetchUsers( [ 'newcomer' ], $now )
402 // newcomers and learner
403 $this->assertArrayEquals(
405 'Newcomer1', 'Newcomer2', 'Newcomer3',
406 'Learner1', 'Learner2', 'Learner3', 'Learner4',
408 $this->fetchUsers( [ 'newcomer', 'learner' ], $now )
411 // newcomers and more learner
412 $this->assertArrayEquals(
414 'Newcomer1', 'Newcomer2', 'Newcomer3',
417 $this->fetchUsers( [ 'newcomer', 'experienced' ], $now )
421 $this->assertArrayEquals(
422 [ 'Learner1', 'Learner2', 'Learner3', 'Learner4' ],
423 $this->fetchUsers( [ 'learner' ], $now )
426 // more experienced only
427 $this->assertArrayEquals(
429 $this->fetchUsers( [ 'experienced' ], $now )
432 // learner and more experienced
433 $this->assertArrayEquals(
435 'Learner1', 'Learner2', 'Learner3', 'Learner4',
438 $this->fetchUsers( [ 'learner', 'experienced' ], $now ),
439 'Learner and more experienced'
442 // newcomers, learner, and more experienced
443 // TOOD: Fix test. This needs to test that anons are excluded,
444 // and right now the join fails.
445 /* $this->assertArrayEquals( */
447 /* 'Newcomer1', 'Newcomer2', 'Newcomer3', */
448 /* 'Learner1', 'Learner2', 'Learner3', 'Learner4', */
449 /* 'Experienced1', */
451 /* $this->fetchUsers( [ 'newcomer', 'learner', 'experienced' ], $now ) */
455 private function createUsers( $specs, $now ) {
456 $dbw = wfGetDB( DB_MASTER
);
457 foreach ( $specs as $name => $spec ) {
461 'editcount' => $spec['edits'],
462 'registration' => $dbw->timestamp( $this->daysAgo( $spec['days'], $now ) ),
469 private function fetchUsers( $filters, $now ) {
478 call_user_func_array(
479 [ $this->changesListSpecialPage
, 'filterOnUserExperienceLevel' ],
481 get_class( $this->changesListSpecialPage
),
482 $this->changesListSpecialPage
->getContext(),
483 $this->changesListSpecialPage
->getDB(),
494 $result = wfGetDB( DB_MASTER
)->select(
497 array_filter( $conds ) +
[ 'user_email' => 'ut' ]
501 foreach ( $result as $row ) {
502 $usernames[] = $row->user_name
;
508 private function daysAgo( $days, $now ) {
509 $secondsPerDay = 86400;
510 return $now - $days * $secondsPerDay;
513 public function testGetFilterGroupDefinitionFromLegacyCustomFilters() {
516 'msg' => 'showhidefoo',
521 'msg' => 'showhidebar',
528 'name' => 'unstructured',
529 'class' => ChangesListBooleanFilterGroup
::class,
534 'showHide' => 'showhidefoo',
539 'showHide' => 'showhidebar',
544 $this->changesListSpecialPage
->getFilterGroupDefinitionFromLegacyCustomFilters(
550 public function testGetStructuredFilterJsData() {
551 $this->changesListSpecialPage
->filterGroups
= [];
555 'name' => 'gub-group',
556 'title' => 'gub-group-title',
557 'class' => ChangesListBooleanFilterGroup
::class,
561 'label' => 'foo-label',
562 'description' => 'foo-description',
564 'showHide' => 'showhidefoo',
569 'label' => 'bar-label',
570 'description' => 'bar-description',
578 'name' => 'des-group',
579 'title' => 'des-group-title',
580 'class' => ChangesListStringOptionsFilterGroup
::class,
581 'isFullCoverage' => true,
585 'label' => 'grault-label',
586 'description' => 'grault-description',
590 'label' => 'garply-label',
591 'description' => 'garply-description',
594 'queryCallable' => function () {
596 'default' => ChangesListStringOptionsFilterGroup
::NONE
,
600 'name' => 'unstructured',
601 'class' => ChangesListBooleanFilterGroup
::class,
604 'name' => 'hidethud',
605 'showHide' => 'showhidethud',
611 'showHide' => 'showhidemos',
619 $this->changesListSpecialPage
->registerFiltersFromDefinitions( $definition );
621 $this->assertArrayEquals(
623 // Filters that only display in the unstructured UI are
624 // are not included, and neither are groups that would
625 // be empty due to the above.
628 'name' => 'gub-group',
629 'title' => 'gub-group-title',
630 'type' => ChangesListBooleanFilterGroup
::TYPE
,
635 'label' => 'bar-label',
636 'description' => 'bar-description',
645 'label' => 'foo-label',
646 'description' => 'foo-description',
654 'fullCoverage' => true,
659 'name' => 'des-group',
660 'title' => 'des-group-title',
661 'type' => ChangesListStringOptionsFilterGroup
::TYPE
,
663 'fullCoverage' => true,
667 'label' => 'grault-label',
668 'description' => 'grault-description',
676 'label' => 'garply-label',
677 'description' => 'garply-description',
686 'default' => ChangesListStringOptionsFilterGroup
::NONE
,
697 'grault-description',
699 'garply-description',
702 $this->changesListSpecialPage
->getStructuredFilterJsData(),
703 /** ordered= */ false,
708 public function provideParseParameters() {
710 [ 'hidebots', [ 'hidebots' => true ] ],
712 [ 'bots', [ 'hidebots' => false ] ],
714 [ 'hideminor', [ 'hideminor' => true ] ],
716 [ 'minor', [ 'hideminor' => false ] ],
718 [ 'hidemajor', [ 'hidemajor' => true ] ],
720 [ 'hideliu', [ 'hideliu' => true ] ],
722 [ 'hidepatrolled', [ 'hidepatrolled' => true ] ],
724 [ 'hideunpatrolled', [ 'hideunpatrolled' => true ] ],
726 [ 'hideanons', [ 'hideanons' => true ] ],
728 [ 'hidemyself', [ 'hidemyself' => true ] ],
730 [ 'hidebyothers', [ 'hidebyothers' => true ] ],
732 [ 'hidehumans', [ 'hidehumans' => true ] ],
734 [ 'hidepageedits', [ 'hidepageedits' => true ] ],
736 [ 'pagedits', [ 'hidepageedits' => false ] ],
738 [ 'hidenewpages', [ 'hidenewpages' => true ] ],
740 [ 'hidecategorization', [ 'hidecategorization' => true ] ],
742 [ 'hidelog', [ 'hidelog' => true ] ],
745 'userExpLevel=learner;experienced',
747 'userExpLevel' => 'learner;experienced'
751 // A few random combos
753 'bots,hideliu,hidemyself',
757 'hidemyself' => true,
762 'minor,hideanons,categorization',
764 'hideminor' => false,
766 'hidecategorization' => false,
771 'hidehumans,bots,hidecategorization',
773 'hidehumans' => true,
775 'hidecategorization' => true,
780 'hidemyself,userExpLevel=newcomer;learner,hideminor',
782 'hidemyself' => true,
784 'userExpLevel' => 'newcomer;learner',
790 public function provideGetFilterConflicts() {
794 "expectedConflicts" => false,
799 "userExpLevel" => "newcomer",
801 "expectedConflicts" => true,
806 "userExpLevel" => "learner",
808 "expectedConflicts" => false,
813 "hidenewpages" => true,
814 "hidepageedits" => true,
815 "hidecategorization" => false,
817 "hideWikidata" => true,
819 "expectedConflicts" => true,
824 "hidenewpages" => false,
825 "hidepageedits" => true,
826 "hidecategorization" => false,
828 "hideWikidata" => true,
830 "expectedConflicts" => true,
835 "hidenewpages" => false,
836 "hidepageedits" => false,
837 "hidecategorization" => true,
839 "hideWikidata" => true,
841 "expectedConflicts" => false,
846 "hidenewpages" => true,
847 "hidepageedits" => true,
848 "hidecategorization" => false,
850 "hideWikidata" => true,
852 "expectedConflicts" => false,
858 * @dataProvider provideGetFilterConflicts
860 public function testGetFilterConflicts( $parameters, $expectedConflicts ) {
861 $context = new RequestContext
;
862 $context->setRequest( new FauxRequest( $parameters ) );
863 $this->changesListSpecialPage
->setContext( $context );
867 $this->changesListSpecialPage
->areFiltersInConflict()
871 public function validateOptionsProvider() {
874 [ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 1 ],
876 [ 'hideliu' => 1, 'hidebots' => 1, ],
880 [ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 0 ],
882 [ 'hidebots' => 0, 'hidehumans' => 1 ],
886 [ 'hidemyself' => 1, 'hidebyothers' => 1 ],
891 [ 'hidebots' => 1, 'hidehumans' => 1 ],
896 [ 'hidepatrolled' => 1, 'hideunpatrolled' => 1 ],
901 [ 'hideminor' => 1, 'hidemajor' => 1 ],
907 [ 'hidepageedits' => 1, 'hidenewpages' => 1, 'hidecategorization' => 1, 'hidelog' => 1, ],