Merge "RedirectSpecialArticle: Fix PHP notice about undefined index"
[lhc/web/wiklou.git] / tests / phpunit / includes / specialpage / ChangesListSpecialPageTest.php
1 <?php
2
3 use MediaWiki\MediaWikiServices;
4 use Wikimedia\TestingAccessWrapper;
5
6 /**
7 * Test class for ChangesListSpecialPage class
8 *
9 * Copyright © 2011-, Antoine Musso, Stephane Bisson, Matthew Flaschen
10 *
11 * @author Antoine Musso
12 * @author Stephane Bisson
13 * @author Matthew Flaschen
14 * @group Database
15 *
16 * @covers ChangesListSpecialPage
17 */
18 class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase {
19 protected function getPage() {
20 $mock = $this->getMockBuilder( ChangesListSpecialPage::class )
21 ->setConstructorArgs(
22 [
23 'ChangesListSpecialPage',
24 ''
25 ]
26 )
27 ->setMethods( [ 'getPageTitle' ] )
28 ->getMockForAbstractClass();
29
30 $mock->method( 'getPageTitle' )->willReturn(
31 Title::makeTitle( NS_SPECIAL, 'ChangesListSpecialPage' )
32 );
33
34 $mock = TestingAccessWrapper::newFromObject(
35 $mock
36 );
37
38 return $mock;
39 }
40
41 private function buildQuery(
42 $requestOptions = null,
43 $user = null
44 ) {
45 $context = new RequestContext;
46 $context->setRequest( new FauxRequest( $requestOptions ) );
47 if ( $user ) {
48 $context->setUser( $user );
49 }
50
51 $this->changesListSpecialPage->setContext( $context );
52 $this->changesListSpecialPage->filterGroups = [];
53 $formOptions = $this->changesListSpecialPage->setup( null );
54
55 #  Filter out rc_timestamp conditions which depends on the test runtime
56 # This condition is not needed as of march 2, 2011 -- hashar
57 # @todo FIXME: Find a way to generate the correct rc_timestamp
58
59 $tables = [];
60 $fields = [];
61 $queryConditions = [];
62 $query_options = [];
63 $join_conds = [];
64
65 call_user_func_array(
66 [ $this->changesListSpecialPage, 'buildQuery' ],
67 [
68 &$tables,
69 &$fields,
70 &$queryConditions,
71 &$query_options,
72 &$join_conds,
73 $formOptions
74 ]
75 );
76
77 $queryConditions = array_filter(
78 $queryConditions,
79 'ChangesListSpecialPageTest::filterOutRcTimestampCondition'
80 );
81
82 return $queryConditions;
83 }
84
85 /** helper to test SpecialRecentchanges::buildQuery() */
86 private function assertConditions(
87 $expected,
88 $requestOptions = null,
89 $message = '',
90 $user = null
91 ) {
92 $queryConditions = $this->buildQuery( $requestOptions, $user );
93
94 $this->assertEquals(
95 self::normalizeCondition( $expected ),
96 self::normalizeCondition( $queryConditions ),
97 $message
98 );
99 }
100
101 private static function normalizeCondition( $conds ) {
102 $dbr = wfGetDB( DB_REPLICA );
103 $normalized = array_map(
104 function ( $k, $v ) use ( $dbr ) {
105 if ( is_array( $v ) ) {
106 sort( $v );
107 }
108 // (Ab)use makeList() to format only this entry
109 return $dbr->makeList( [ $k => $v ], Database::LIST_AND );
110 },
111 array_keys( $conds ),
112 $conds
113 );
114 sort( $normalized );
115 return $normalized;
116 }
117
118 /** return false if condition begins with 'rc_timestamp ' */
119 private static function filterOutRcTimestampCondition( $var ) {
120 return ( is_array( $var ) || strpos( $var, 'rc_timestamp ' ) === false );
121 }
122
123 public function testRcNsFilter() {
124 $this->assertConditions(
125 [ # expected
126 "rc_namespace = '0'",
127 ],
128 [
129 'namespace' => NS_MAIN,
130 ],
131 "rc conditions with one namespace"
132 );
133 }
134
135 public function testRcNsFilterInversion() {
136 $this->assertConditions(
137 [ # expected
138 "rc_namespace != '0'",
139 ],
140 [
141 'namespace' => NS_MAIN,
142 'invert' => 1,
143 ],
144 "rc conditions with namespace inverted"
145 );
146 }
147
148 public function testRcNsFilterMultiple() {
149 $this->assertConditions(
150 [ # expected
151 "rc_namespace IN ('1','2','3')",
152 ],
153 [
154 'namespace' => '1;2;3',
155 ],
156 "rc conditions with multiple namespaces"
157 );
158 }
159
160 public function testRcNsFilterMultipleAssociated() {
161 $this->assertConditions(
162 [ # expected
163 "rc_namespace IN ('0','1','4','5','6','7')",
164 ],
165 [
166 'namespace' => '1;4;7',
167 'associated' => 1,
168 ],
169 "rc conditions with multiple namespaces and associated"
170 );
171 }
172
173 public function testRcNsFilterAssociatedSpecial() {
174 $this->assertConditions(
175 [ # expected
176 "rc_namespace IN ('-1','0','1')",
177 ],
178 [
179 'namespace' => '1;-1',
180 'associated' => 1,
181 ],
182 "rc conditions with associated and special namespace"
183 );
184 }
185
186 public function testRcNsFilterMultipleAssociatedInvert() {
187 $this->assertConditions(
188 [ # expected
189 "rc_namespace NOT IN ('2','3','8','9')",
190 ],
191 [
192 'namespace' => '2;3;9',
193 'associated' => 1,
194 'invert' => 1
195 ],
196 "rc conditions with multiple namespaces, associated and inverted"
197 );
198 }
199
200 public function testRcNsFilterMultipleInvert() {
201 $this->assertConditions(
202 [ # expected
203 "rc_namespace NOT IN ('1','2','3')",
204 ],
205 [
206 'namespace' => '1;2;3',
207 'invert' => 1,
208 ],
209 "rc conditions with multiple namespaces inverted"
210 );
211 }
212
213 public function testRcNsFilterAllContents() {
214 $namespaces = MediaWikiServices::getInstance()->getNamespaceInfo()->getSubjectNamespaces();
215 $this->assertConditions(
216 [ # expected
217 'rc_namespace IN (' . $this->db->makeList( $namespaces ) . ')',
218 ],
219 [
220 'namespace' => 'all-contents',
221 ],
222 "rc conditions with all-contents"
223 );
224 }
225
226 public function testRcHidemyselfFilter() {
227 $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
228 $this->overrideMwServices();
229
230 $user = $this->getTestUser()->getUser();
231 $user->getActorId( wfGetDB( DB_MASTER ) );
232 $this->assertConditions(
233 [ # expected
234 "NOT((rc_actor = '{$user->getActorId()}'))",
235 ],
236 [
237 'hidemyself' => 1,
238 ],
239 "rc conditions: hidemyself=1 (logged in)",
240 $user
241 );
242
243 $user = User::newFromName( '10.11.12.13', false );
244 $id = $user->getActorId( wfGetDB( DB_MASTER ) );
245 $this->assertConditions(
246 [ # expected
247 "NOT((rc_actor = '{$user->getActorId()}'))",
248 ],
249 [
250 'hidemyself' => 1,
251 ],
252 "rc conditions: hidemyself=1 (anon)",
253 $user
254 );
255 }
256
257 public function testRcHidemyselfFilter_old() {
258 $this->setMwGlobals(
259 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
260 );
261 $this->overrideMwServices();
262
263 $user = $this->getTestUser()->getUser();
264 $user->getActorId( wfGetDB( DB_MASTER ) );
265 $this->assertConditions(
266 [ # expected
267 "NOT((rc_user = '{$user->getId()}'))",
268 ],
269 [
270 'hidemyself' => 1,
271 ],
272 "rc conditions: hidemyself=1 (logged in)",
273 $user
274 );
275
276 $user = User::newFromName( '10.11.12.13', false );
277 $id = $user->getActorId( wfGetDB( DB_MASTER ) );
278 $this->assertConditions(
279 [ # expected
280 "NOT((rc_user_text = '10.11.12.13'))",
281 ],
282 [
283 'hidemyself' => 1,
284 ],
285 "rc conditions: hidemyself=1 (anon)",
286 $user
287 );
288 }
289
290 public function testRcHidebyothersFilter() {
291 $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
292 $this->overrideMwServices();
293
294 $user = $this->getTestUser()->getUser();
295 $user->getActorId( wfGetDB( DB_MASTER ) );
296 $this->assertConditions(
297 [ # expected
298 "(rc_actor = '{$user->getActorId()}')",
299 ],
300 [
301 'hidebyothers' => 1,
302 ],
303 "rc conditions: hidebyothers=1 (logged in)",
304 $user
305 );
306
307 $user = User::newFromName( '10.11.12.13', false );
308 $id = $user->getActorId( wfGetDB( DB_MASTER ) );
309 $this->assertConditions(
310 [ # expected
311 "(rc_actor = '{$user->getActorId()}')",
312 ],
313 [
314 'hidebyothers' => 1,
315 ],
316 "rc conditions: hidebyothers=1 (anon)",
317 $user
318 );
319 }
320
321 public function testRcHidebyothersFilter_old() {
322 $this->setMwGlobals(
323 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
324 );
325 $this->overrideMwServices();
326
327 $user = $this->getTestUser()->getUser();
328 $user->getActorId( wfGetDB( DB_MASTER ) );
329 $this->assertConditions(
330 [ # expected
331 "(rc_user_text = '{$user->getName()}')",
332 ],
333 [
334 'hidebyothers' => 1,
335 ],
336 "rc conditions: hidebyothers=1 (logged in)",
337 $user
338 );
339
340 $user = User::newFromName( '10.11.12.13', false );
341 $id = $user->getActorId( wfGetDB( DB_MASTER ) );
342 $this->assertConditions(
343 [ # expected
344 "(rc_user_text = '10.11.12.13')",
345 ],
346 [
347 'hidebyothers' => 1,
348 ],
349 "rc conditions: hidebyothers=1 (anon)",
350 $user
351 );
352 }
353
354 public function testRcHidepageedits() {
355 $this->assertConditions(
356 [ # expected
357 "rc_type != '0'",
358 ],
359 [
360 'hidepageedits' => 1,
361 ],
362 "rc conditions: hidepageedits=1"
363 );
364 }
365
366 public function testRcHidenewpages() {
367 $this->assertConditions(
368 [ # expected
369 "rc_type != '1'",
370 ],
371 [
372 'hidenewpages' => 1,
373 ],
374 "rc conditions: hidenewpages=1"
375 );
376 }
377
378 public function testRcHidelog() {
379 $this->assertConditions(
380 [ # expected
381 "rc_type != '3'",
382 ],
383 [
384 'hidelog' => 1,
385 ],
386 "rc conditions: hidelog=1"
387 );
388 }
389
390 public function testRcHidehumans() {
391 $this->assertConditions(
392 [ # expected
393 'rc_bot' => 1,
394 ],
395 [
396 'hidebots' => 0,
397 'hidehumans' => 1,
398 ],
399 "rc conditions: hidebots=0 hidehumans=1"
400 );
401 }
402
403 public function testRcHidepatrolledDisabledFilter() {
404 $this->setMwGlobals( 'wgUseRCPatrol', false );
405 $user = $this->getTestUser()->getUser();
406 $this->assertConditions(
407 [ # expected
408 ],
409 [
410 'hidepatrolled' => 1,
411 ],
412 "rc conditions: hidepatrolled=1 (user not allowed)",
413 $user
414 );
415 }
416
417 public function testRcHideunpatrolledDisabledFilter() {
418 $this->setMwGlobals( 'wgUseRCPatrol', false );
419 $user = $this->getTestUser()->getUser();
420 $this->assertConditions(
421 [ # expected
422 ],
423 [
424 'hideunpatrolled' => 1,
425 ],
426 "rc conditions: hideunpatrolled=1 (user not allowed)",
427 $user
428 );
429 }
430
431 public function testRcHidepatrolledFilter() {
432 $user = $this->getTestSysop()->getUser();
433 $this->assertConditions(
434 [ # expected
435 'rc_patrolled' => 0,
436 ],
437 [
438 'hidepatrolled' => 1,
439 ],
440 "rc conditions: hidepatrolled=1",
441 $user
442 );
443 }
444
445 public function testRcHideunpatrolledFilter() {
446 $user = $this->getTestSysop()->getUser();
447 $this->assertConditions(
448 [ # expected
449 'rc_patrolled' => [ 1, 2 ],
450 ],
451 [
452 'hideunpatrolled' => 1,
453 ],
454 "rc conditions: hideunpatrolled=1",
455 $user
456 );
457 }
458
459 public function testRcReviewStatusFilter() {
460 $user = $this->getTestSysop()->getUser();
461 $this->assertConditions(
462 [ #expected
463 'rc_patrolled' => 1,
464 ],
465 [
466 'reviewStatus' => 'manual'
467 ],
468 "rc conditions: reviewStatus=manual",
469 $user
470 );
471 $this->assertConditions(
472 [ #expected
473 'rc_patrolled' => [ 0, 2 ],
474 ],
475 [
476 'reviewStatus' => 'unpatrolled;auto'
477 ],
478 "rc conditions: reviewStatus=unpatrolled;auto",
479 $user
480 );
481 }
482
483 public function testRcHideminorFilter() {
484 $this->assertConditions(
485 [ # expected
486 "rc_minor = 0",
487 ],
488 [
489 'hideminor' => 1,
490 ],
491 "rc conditions: hideminor=1"
492 );
493 }
494
495 public function testRcHidemajorFilter() {
496 $this->assertConditions(
497 [ # expected
498 "rc_minor = 1",
499 ],
500 [
501 'hidemajor' => 1,
502 ],
503 "rc conditions: hidemajor=1"
504 );
505 }
506
507 public function testHideCategorization() {
508 $this->assertConditions(
509 [
510 # expected
511 "rc_type != '6'"
512 ],
513 [
514 'hidecategorization' => 1
515 ],
516 "rc conditions: hidecategorization=1"
517 );
518 }
519
520 public function testFilterUserExpLevelAll() {
521 $this->assertConditions(
522 [
523 # expected
524 ],
525 [
526 'userExpLevel' => 'registered;unregistered;newcomer;learner;experienced',
527 ],
528 "rc conditions: userExpLevel=registered;unregistered;newcomer;learner;experienced"
529 );
530 }
531
532 public function testFilterUserExpLevelRegisteredUnregistered() {
533 $this->assertConditions(
534 [
535 # expected
536 ],
537 [
538 'userExpLevel' => 'registered;unregistered',
539 ],
540 "rc conditions: userExpLevel=registered;unregistered"
541 );
542 }
543
544 public function testFilterUserExpLevelRegisteredUnregisteredLearner() {
545 $this->assertConditions(
546 [
547 # expected
548 ],
549 [
550 'userExpLevel' => 'registered;unregistered;learner',
551 ],
552 "rc conditions: userExpLevel=registered;unregistered;learner"
553 );
554 }
555
556 public function testFilterUserExpLevelAllExperienceLevels() {
557 $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
558 $this->overrideMwServices();
559
560 $this->assertConditions(
561 [
562 # expected
563 'actor_rc_user.actor_user IS NOT NULL',
564 ],
565 [
566 'userExpLevel' => 'newcomer;learner;experienced',
567 ],
568 "rc conditions: userExpLevel=newcomer;learner;experienced"
569 );
570 }
571
572 public function testFilterUserExpLevelAllExperienceLevels_old() {
573 $this->setMwGlobals(
574 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
575 );
576 $this->overrideMwServices();
577
578 $this->assertConditions(
579 [
580 # expected
581 'rc_user != 0',
582 ],
583 [
584 'userExpLevel' => 'newcomer;learner;experienced',
585 ],
586 "rc conditions: userExpLevel=newcomer;learner;experienced"
587 );
588 }
589
590 public function testFilterUserExpLevelRegistrered() {
591 $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
592 $this->overrideMwServices();
593
594 $this->assertConditions(
595 [
596 # expected
597 'actor_rc_user.actor_user IS NOT NULL',
598 ],
599 [
600 'userExpLevel' => 'registered',
601 ],
602 "rc conditions: userExpLevel=registered"
603 );
604 }
605
606 public function testFilterUserExpLevelRegistrered_old() {
607 $this->setMwGlobals(
608 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
609 );
610 $this->overrideMwServices();
611
612 $this->assertConditions(
613 [
614 # expected
615 'rc_user != 0',
616 ],
617 [
618 'userExpLevel' => 'registered',
619 ],
620 "rc conditions: userExpLevel=registered"
621 );
622 }
623
624 public function testFilterUserExpLevelUnregistrered() {
625 $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
626 $this->overrideMwServices();
627
628 $this->assertConditions(
629 [
630 # expected
631 'actor_rc_user.actor_user IS NULL',
632 ],
633 [
634 'userExpLevel' => 'unregistered',
635 ],
636 "rc conditions: userExpLevel=unregistered"
637 );
638 }
639
640 public function testFilterUserExpLevelUnregistrered_old() {
641 $this->setMwGlobals(
642 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
643 );
644 $this->overrideMwServices();
645
646 $this->assertConditions(
647 [
648 # expected
649 'rc_user = 0',
650 ],
651 [
652 'userExpLevel' => 'unregistered',
653 ],
654 "rc conditions: userExpLevel=unregistered"
655 );
656 }
657
658 public function testFilterUserExpLevelRegistreredOrLearner() {
659 $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
660 $this->overrideMwServices();
661
662 $this->assertConditions(
663 [
664 # expected
665 'actor_rc_user.actor_user IS NOT NULL',
666 ],
667 [
668 'userExpLevel' => 'registered;learner',
669 ],
670 "rc conditions: userExpLevel=registered;learner"
671 );
672 }
673
674 public function testFilterUserExpLevelRegistreredOrLearner_old() {
675 $this->setMwGlobals(
676 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
677 );
678 $this->overrideMwServices();
679
680 $this->assertConditions(
681 [
682 # expected
683 'rc_user != 0',
684 ],
685 [
686 'userExpLevel' => 'registered;learner',
687 ],
688 "rc conditions: userExpLevel=registered;learner"
689 );
690 }
691
692 public function testFilterUserExpLevelUnregistreredOrExperienced() {
693 $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
694 $this->overrideMwServices();
695
696 $conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
697
698 $this->assertRegExp(
699 '/\(actor_rc_user\.actor_user IS NULL\) OR '
700 . '\(\(user_editcount >= 500\) AND \(user_registration <= \'[^\']+\'\)\)/',
701 reset( $conds ),
702 "rc conditions: userExpLevel=unregistered;experienced"
703 );
704 }
705
706 public function testFilterUserExpLevelUnregistreredOrExperienced_old() {
707 $this->setMwGlobals(
708 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
709 );
710 $this->overrideMwServices();
711
712 $conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
713
714 $this->assertRegExp(
715 '/\(rc_user = 0\) OR '
716 . '\(\(user_editcount >= 500\) AND \(user_registration <= \'[^\']+\'\)\)/',
717 reset( $conds ),
718 "rc conditions: userExpLevel=unregistered;experienced"
719 );
720 }
721
722 public function testFilterUserExpLevel() {
723 $now = time();
724 $this->setMwGlobals( [
725 'wgLearnerEdits' => 10,
726 'wgLearnerMemberSince' => 4,
727 'wgExperiencedUserEdits' => 500,
728 'wgExperiencedUserMemberSince' => 30,
729 ] );
730
731 $this->createUsers( [
732 'Newcomer1' => [ 'edits' => 2, 'days' => 2 ],
733 'Newcomer2' => [ 'edits' => 12, 'days' => 3 ],
734 'Newcomer3' => [ 'edits' => 8, 'days' => 5 ],
735 'Learner1' => [ 'edits' => 15, 'days' => 10 ],
736 'Learner2' => [ 'edits' => 450, 'days' => 20 ],
737 'Learner3' => [ 'edits' => 460, 'days' => 33 ],
738 'Learner4' => [ 'edits' => 525, 'days' => 28 ],
739 'Experienced1' => [ 'edits' => 538, 'days' => 33 ],
740 ], $now );
741
742 // newcomers only
743 $this->assertArrayEquals(
744 [ 'Newcomer1', 'Newcomer2', 'Newcomer3' ],
745 $this->fetchUsers( [ 'newcomer' ], $now )
746 );
747
748 // newcomers and learner
749 $this->assertArrayEquals(
750 [
751 'Newcomer1', 'Newcomer2', 'Newcomer3',
752 'Learner1', 'Learner2', 'Learner3', 'Learner4',
753 ],
754 $this->fetchUsers( [ 'newcomer', 'learner' ], $now )
755 );
756
757 // newcomers and more learner
758 $this->assertArrayEquals(
759 [
760 'Newcomer1', 'Newcomer2', 'Newcomer3',
761 'Experienced1',
762 ],
763 $this->fetchUsers( [ 'newcomer', 'experienced' ], $now )
764 );
765
766 // learner only
767 $this->assertArrayEquals(
768 [ 'Learner1', 'Learner2', 'Learner3', 'Learner4' ],
769 $this->fetchUsers( [ 'learner' ], $now )
770 );
771
772 // more experienced only
773 $this->assertArrayEquals(
774 [ 'Experienced1' ],
775 $this->fetchUsers( [ 'experienced' ], $now )
776 );
777
778 // learner and more experienced
779 $this->assertArrayEquals(
780 [
781 'Learner1', 'Learner2', 'Learner3', 'Learner4',
782 'Experienced1',
783 ],
784 $this->fetchUsers( [ 'learner', 'experienced' ], $now )
785 );
786 }
787
788 private function createUsers( $specs, $now ) {
789 $dbw = wfGetDB( DB_MASTER );
790 foreach ( $specs as $name => $spec ) {
791 User::createNew(
792 $name,
793 [
794 'editcount' => $spec['edits'],
795 'registration' => $dbw->timestamp( $this->daysAgo( $spec['days'], $now ) ),
796 'email' => 'ut',
797 ]
798 );
799 }
800 }
801
802 private function fetchUsers( $filters, $now ) {
803 $tables = [];
804 $conds = [];
805 $fields = [];
806 $query_options = [];
807 $join_conds = [];
808
809 sort( $filters );
810
811 call_user_func_array(
812 [ $this->changesListSpecialPage, 'filterOnUserExperienceLevel' ],
813 [
814 get_class( $this->changesListSpecialPage ),
815 $this->changesListSpecialPage->getContext(),
816 $this->changesListSpecialPage->getDB(),
817 &$tables,
818 &$fields,
819 &$conds,
820 &$query_options,
821 &$join_conds,
822 $filters,
823 $now
824 ]
825 );
826
827 // @todo: This is not at all safe or sane. It just blindly assumes
828 // nothing in $conds depends on any other tables.
829 $result = wfGetDB( DB_MASTER )->select(
830 'user',
831 'user_name',
832 array_filter( $conds ) + [ 'user_email' => 'ut' ]
833 );
834
835 $usernames = [];
836 foreach ( $result as $row ) {
837 $usernames[] = $row->user_name;
838 }
839
840 return $usernames;
841 }
842
843 private function daysAgo( $days, $now ) {
844 $secondsPerDay = 86400;
845 return $now - $days * $secondsPerDay;
846 }
847
848 public function testGetStructuredFilterJsData() {
849 $this->changesListSpecialPage->filterGroups = [];
850
851 $definition = [
852 [
853 'name' => 'gub-group',
854 'title' => 'gub-group-title',
855 'class' => ChangesListBooleanFilterGroup::class,
856 'filters' => [
857 [
858 'name' => 'hidefoo',
859 'label' => 'foo-label',
860 'description' => 'foo-description',
861 'default' => true,
862 'showHide' => 'showhidefoo',
863 'priority' => 2,
864 ],
865 [
866 'name' => 'hidebar',
867 'label' => 'bar-label',
868 'description' => 'bar-description',
869 'default' => false,
870 'priority' => 4,
871 ]
872 ],
873 ],
874
875 [
876 'name' => 'des-group',
877 'title' => 'des-group-title',
878 'class' => ChangesListStringOptionsFilterGroup::class,
879 'isFullCoverage' => true,
880 'filters' => [
881 [
882 'name' => 'grault',
883 'label' => 'grault-label',
884 'description' => 'grault-description',
885 ],
886 [
887 'name' => 'garply',
888 'label' => 'garply-label',
889 'description' => 'garply-description',
890 ],
891 ],
892 'queryCallable' => function () {
893 },
894 'default' => ChangesListStringOptionsFilterGroup::NONE,
895 ],
896
897 [
898 'name' => 'unstructured',
899 'class' => ChangesListBooleanFilterGroup::class,
900 'filters' => [
901 [
902 'name' => 'hidethud',
903 'showHide' => 'showhidethud',
904 'default' => true,
905 ],
906
907 [
908 'name' => 'hidemos',
909 'showHide' => 'showhidemos',
910 'default' => false,
911 ],
912 ],
913 ],
914
915 ];
916
917 $this->changesListSpecialPage->registerFiltersFromDefinitions( $definition );
918
919 $this->assertArrayEquals(
920 [
921 // Filters that only display in the unstructured UI are
922 // are not included, and neither are groups that would
923 // be empty due to the above.
924 'groups' => [
925 [
926 'name' => 'gub-group',
927 'title' => 'gub-group-title',
928 'type' => ChangesListBooleanFilterGroup::TYPE,
929 'priority' => -1,
930 'filters' => [
931 [
932 'name' => 'hidebar',
933 'label' => 'bar-label',
934 'description' => 'bar-description',
935 'default' => false,
936 'priority' => 4,
937 'cssClass' => null,
938 'conflicts' => [],
939 'subset' => [],
940 'defaultHighlightColor' => null
941 ],
942 [
943 'name' => 'hidefoo',
944 'label' => 'foo-label',
945 'description' => 'foo-description',
946 'default' => true,
947 'priority' => 2,
948 'cssClass' => null,
949 'conflicts' => [],
950 'subset' => [],
951 'defaultHighlightColor' => null
952 ],
953 ],
954 'fullCoverage' => true,
955 'conflicts' => [],
956 ],
957
958 [
959 'name' => 'des-group',
960 'title' => 'des-group-title',
961 'type' => ChangesListStringOptionsFilterGroup::TYPE,
962 'priority' => -2,
963 'fullCoverage' => true,
964 'filters' => [
965 [
966 'name' => 'grault',
967 'label' => 'grault-label',
968 'description' => 'grault-description',
969 'cssClass' => null,
970 'priority' => -2,
971 'conflicts' => [],
972 'subset' => [],
973 'defaultHighlightColor' => null
974 ],
975 [
976 'name' => 'garply',
977 'label' => 'garply-label',
978 'description' => 'garply-description',
979 'cssClass' => null,
980 'priority' => -3,
981 'conflicts' => [],
982 'subset' => [],
983 'defaultHighlightColor' => null
984 ],
985 ],
986 'conflicts' => [],
987 'separator' => ';',
988 'default' => ChangesListStringOptionsFilterGroup::NONE,
989 ],
990 ],
991 'messageKeys' => [
992 'gub-group-title',
993 'bar-label',
994 'bar-description',
995 'foo-label',
996 'foo-description',
997 'des-group-title',
998 'grault-label',
999 'grault-description',
1000 'garply-label',
1001 'garply-description',
1002 ],
1003 ],
1004 $this->changesListSpecialPage->getStructuredFilterJsData(),
1005 /** ordered= */ false,
1006 /** named= */ true
1007 );
1008 }
1009
1010 public function provideParseParameters() {
1011 return [
1012 [ 'hidebots', [ 'hidebots' => true ] ],
1013
1014 [ 'bots', [ 'hidebots' => false ] ],
1015
1016 [ 'hideminor', [ 'hideminor' => true ] ],
1017
1018 [ 'minor', [ 'hideminor' => false ] ],
1019
1020 [ 'hidemajor', [ 'hidemajor' => true ] ],
1021
1022 [ 'hideliu', [ 'hideliu' => true ] ],
1023
1024 [ 'hidepatrolled', [ 'hidepatrolled' => true ] ],
1025
1026 [ 'hideunpatrolled', [ 'hideunpatrolled' => true ] ],
1027
1028 [ 'hideanons', [ 'hideanons' => true ] ],
1029
1030 [ 'hidemyself', [ 'hidemyself' => true ] ],
1031
1032 [ 'hidebyothers', [ 'hidebyothers' => true ] ],
1033
1034 [ 'hidehumans', [ 'hidehumans' => true ] ],
1035
1036 [ 'hidepageedits', [ 'hidepageedits' => true ] ],
1037
1038 [ 'pagedits', [ 'hidepageedits' => false ] ],
1039
1040 [ 'hidenewpages', [ 'hidenewpages' => true ] ],
1041
1042 [ 'hidecategorization', [ 'hidecategorization' => true ] ],
1043
1044 [ 'hidelog', [ 'hidelog' => true ] ],
1045
1046 [
1047 'userExpLevel=learner;experienced',
1048 [
1049 'userExpLevel' => 'learner;experienced'
1050 ],
1051 ],
1052
1053 // A few random combos
1054 [
1055 'bots,hideliu,hidemyself',
1056 [
1057 'hidebots' => false,
1058 'hideliu' => true,
1059 'hidemyself' => true,
1060 ],
1061 ],
1062
1063 [
1064 'minor,hideanons,categorization',
1065 [
1066 'hideminor' => false,
1067 'hideanons' => true,
1068 'hidecategorization' => false,
1069 ]
1070 ],
1071
1072 [
1073 'hidehumans,bots,hidecategorization',
1074 [
1075 'hidehumans' => true,
1076 'hidebots' => false,
1077 'hidecategorization' => true,
1078 ],
1079 ],
1080
1081 [
1082 'hidemyself,userExpLevel=newcomer;learner,hideminor',
1083 [
1084 'hidemyself' => true,
1085 'hideminor' => true,
1086 'userExpLevel' => 'newcomer;learner',
1087 ],
1088 ],
1089 ];
1090 }
1091
1092 public function provideGetFilterConflicts() {
1093 return [
1094 [
1095 "parameters" => [],
1096 "expectedConflicts" => false,
1097 ],
1098 [
1099 "parameters" => [
1100 "hideliu" => true,
1101 "userExpLevel" => "newcomer",
1102 ],
1103 "expectedConflicts" => false,
1104 ],
1105 [
1106 "parameters" => [
1107 "hideanons" => true,
1108 "userExpLevel" => "learner",
1109 ],
1110 "expectedConflicts" => false,
1111 ],
1112 [
1113 "parameters" => [
1114 "hidemajor" => true,
1115 "hidenewpages" => true,
1116 "hidepageedits" => true,
1117 "hidecategorization" => false,
1118 "hidelog" => true,
1119 "hideWikidata" => true,
1120 ],
1121 "expectedConflicts" => true,
1122 ],
1123 [
1124 "parameters" => [
1125 "hidemajor" => true,
1126 "hidenewpages" => false,
1127 "hidepageedits" => true,
1128 "hidecategorization" => false,
1129 "hidelog" => false,
1130 "hideWikidata" => true,
1131 ],
1132 "expectedConflicts" => true,
1133 ],
1134 [
1135 "parameters" => [
1136 "hidemajor" => true,
1137 "hidenewpages" => false,
1138 "hidepageedits" => false,
1139 "hidecategorization" => true,
1140 "hidelog" => true,
1141 "hideWikidata" => true,
1142 ],
1143 "expectedConflicts" => false,
1144 ],
1145 [
1146 "parameters" => [
1147 "hideminor" => true,
1148 "hidenewpages" => true,
1149 "hidepageedits" => true,
1150 "hidecategorization" => false,
1151 "hidelog" => true,
1152 "hideWikidata" => true,
1153 ],
1154 "expectedConflicts" => false,
1155 ],
1156 ];
1157 }
1158
1159 /**
1160 * @dataProvider provideGetFilterConflicts
1161 */
1162 public function testGetFilterConflicts( $parameters, $expectedConflicts ) {
1163 $context = new RequestContext;
1164 $context->setRequest( new FauxRequest( $parameters ) );
1165 $this->changesListSpecialPage->setContext( $context );
1166
1167 $this->assertEquals(
1168 $expectedConflicts,
1169 $this->changesListSpecialPage->areFiltersInConflict()
1170 );
1171 }
1172
1173 public function validateOptionsProvider() {
1174 return [
1175 [
1176 [ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 1 ],
1177 true,
1178 [ 'userExpLevel' => 'unregistered', 'hidebots' => 1, ],
1179 true,
1180 ],
1181 [
1182 [ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 0 ],
1183 true,
1184 [ 'hidebots' => 0, 'hidehumans' => 1 ],
1185 true,
1186 ],
1187 [
1188 [ 'hideanons' => 1 ],
1189 true,
1190 [ 'userExpLevel' => 'registered' ],
1191 true,
1192 ],
1193 [
1194 [ 'hideliu' => 1 ],
1195 true,
1196 [ 'userExpLevel' => 'unregistered' ],
1197 true,
1198 ],
1199 [
1200 [ 'hideanons' => 1, 'hidebots' => 1 ],
1201 true,
1202 [ 'userExpLevel' => 'registered', 'hidebots' => 1 ],
1203 true,
1204 ],
1205 [
1206 [ 'hideliu' => 1, 'hidebots' => 0 ],
1207 true,
1208 [ 'userExpLevel' => 'unregistered', 'hidebots' => 0 ],
1209 true,
1210 ],
1211 [
1212 [ 'hidemyself' => 1, 'hidebyothers' => 1 ],
1213 true,
1214 [],
1215 true,
1216 ],
1217 [
1218 [ 'hidebots' => 1, 'hidehumans' => 1 ],
1219 true,
1220 [],
1221 true,
1222 ],
1223 [
1224 [ 'hidepatrolled' => 1, 'hideunpatrolled' => 1 ],
1225 true,
1226 [],
1227 true,
1228 ],
1229 [
1230 [ 'hideminor' => 1, 'hidemajor' => 1 ],
1231 true,
1232 [],
1233 true,
1234 ],
1235 [
1236 // changeType
1237 [ 'hidepageedits' => 1, 'hidenewpages' => 1, 'hidecategorization' => 1, 'hidelog' => 1, ],
1238 true,
1239 [],
1240 true,
1241 ],
1242 ];
1243 }
1244 }