diff options
Diffstat (limited to 'www/wiki/tests/phpunit/includes/changes')
11 files changed, 1984 insertions, 0 deletions
diff --git a/www/wiki/tests/phpunit/includes/changes/CategoryMembershipChangeTest.php b/www/wiki/tests/phpunit/includes/changes/CategoryMembershipChangeTest.php new file mode 100644 index 00000000..ca3ac1b6 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/changes/CategoryMembershipChangeTest.php @@ -0,0 +1,156 @@ +<?php + +/** + * @covers CategoryMembershipChange + * + * @group Database + * + * @author Addshore + */ +class CategoryMembershipChangeTest extends MediaWikiLangTestCase { + + /** + * @var array|Title[]|User[] + */ + private static $lastNotifyArgs; + + /** + * @var int + */ + private static $notifyCallCounter = 0; + + /** + * @var RecentChange + */ + private static $mockRecentChange; + + /** + * @var Revision + */ + private static $pageRev = null; + + /** + * @var User + */ + private static $revUser = null; + + /** + * @var string + */ + private static $pageName = 'CategoryMembershipChangeTestPage'; + + public static function newForCategorizationCallback() { + self::$lastNotifyArgs = func_get_args(); + self::$notifyCallCounter += 1; + return self::$mockRecentChange; + } + + public function setUp() { + parent::setUp(); + self::$notifyCallCounter = 0; + self::$mockRecentChange = self::getMock( RecentChange::class ); + + $this->setContentLang( 'qqx' ); + } + + public function addDBDataOnce() { + $info = $this->insertPage( self::$pageName ); + $title = $info['title']; + + $page = WikiPage::factory( $title ); + self::$pageRev = $page->getRevision(); + self::$revUser = User::newFromId( self::$pageRev->getUser( Revision::RAW ) ); + } + + private function newChange( Revision $revision = null ) { + $change = new CategoryMembershipChange( Title::newFromText( self::$pageName ), $revision ); + $change->overrideNewForCategorizationCallback( + 'CategoryMembershipChangeTest::newForCategorizationCallback' + ); + + return $change; + } + + public function testChangeAddedNoRev() { + $change = $this->newChange(); + $change->triggerCategoryAddedNotification( Title::newFromText( 'CategoryName', NS_CATEGORY ) ); + + $this->assertEquals( 1, self::$notifyCallCounter ); + + $this->assertTrue( strlen( self::$lastNotifyArgs[0] ) === 14 ); + $this->assertEquals( 'Category:CategoryName', self::$lastNotifyArgs[1]->getPrefixedText() ); + $this->assertEquals( '(autochange-username)', self::$lastNotifyArgs[2]->getName() ); + $this->assertEquals( '(recentchanges-page-added-to-category: ' . self::$pageName . ')', + self::$lastNotifyArgs[3] ); + $this->assertEquals( self::$pageName, self::$lastNotifyArgs[4]->getPrefixedText() ); + $this->assertEquals( 0, self::$lastNotifyArgs[5] ); + $this->assertEquals( 0, self::$lastNotifyArgs[6] ); + $this->assertEquals( null, self::$lastNotifyArgs[7] ); + $this->assertEquals( 1, self::$lastNotifyArgs[8] ); + $this->assertEquals( null, self::$lastNotifyArgs[9] ); + $this->assertEquals( 0, self::$lastNotifyArgs[10] ); + } + + public function testChangeRemovedNoRev() { + $change = $this->newChange(); + $change->triggerCategoryRemovedNotification( Title::newFromText( 'CategoryName', NS_CATEGORY ) ); + + $this->assertEquals( 1, self::$notifyCallCounter ); + + $this->assertTrue( strlen( self::$lastNotifyArgs[0] ) === 14 ); + $this->assertEquals( 'Category:CategoryName', self::$lastNotifyArgs[1]->getPrefixedText() ); + $this->assertEquals( '(autochange-username)', self::$lastNotifyArgs[2]->getName() ); + $this->assertEquals( '(recentchanges-page-removed-from-category: ' . self::$pageName . ')', + self::$lastNotifyArgs[3] ); + $this->assertEquals( self::$pageName, self::$lastNotifyArgs[4]->getPrefixedText() ); + $this->assertEquals( 0, self::$lastNotifyArgs[5] ); + $this->assertEquals( 0, self::$lastNotifyArgs[6] ); + $this->assertEquals( null, self::$lastNotifyArgs[7] ); + $this->assertEquals( 1, self::$lastNotifyArgs[8] ); + $this->assertEquals( null, self::$lastNotifyArgs[9] ); + $this->assertEquals( 0, self::$lastNotifyArgs[10] ); + } + + public function testChangeAddedWithRev() { + $revision = Revision::newFromId( Title::newFromText( self::$pageName )->getLatestRevID() ); + $change = $this->newChange( $revision ); + $change->triggerCategoryAddedNotification( Title::newFromText( 'CategoryName', NS_CATEGORY ) ); + + $this->assertEquals( 1, self::$notifyCallCounter ); + + $this->assertTrue( strlen( self::$lastNotifyArgs[0] ) === 14 ); + $this->assertEquals( 'Category:CategoryName', self::$lastNotifyArgs[1]->getPrefixedText() ); + $this->assertEquals( self::$revUser->getName(), self::$lastNotifyArgs[2]->getName() ); + $this->assertEquals( '(recentchanges-page-added-to-category: ' . self::$pageName . ')', + self::$lastNotifyArgs[3] ); + $this->assertEquals( self::$pageName, self::$lastNotifyArgs[4]->getPrefixedText() ); + $this->assertEquals( self::$pageRev->getParentId(), self::$lastNotifyArgs[5] ); + $this->assertEquals( $revision->getId(), self::$lastNotifyArgs[6] ); + $this->assertEquals( null, self::$lastNotifyArgs[7] ); + $this->assertEquals( 0, self::$lastNotifyArgs[8] ); + $this->assertEquals( '127.0.0.1', self::$lastNotifyArgs[9] ); + $this->assertEquals( 0, self::$lastNotifyArgs[10] ); + } + + public function testChangeRemovedWithRev() { + $revision = Revision::newFromId( Title::newFromText( self::$pageName )->getLatestRevID() ); + $change = $this->newChange( $revision ); + $change->triggerCategoryRemovedNotification( Title::newFromText( 'CategoryName', NS_CATEGORY ) ); + + $this->assertEquals( 1, self::$notifyCallCounter ); + + $this->assertTrue( strlen( self::$lastNotifyArgs[0] ) === 14 ); + $this->assertEquals( 'Category:CategoryName', self::$lastNotifyArgs[1]->getPrefixedText() ); + $this->assertEquals( self::$revUser->getName(), self::$lastNotifyArgs[2]->getName() ); + $this->assertEquals( '(recentchanges-page-removed-from-category: ' . self::$pageName . ')', + self::$lastNotifyArgs[3] ); + $this->assertEquals( self::$pageName, self::$lastNotifyArgs[4]->getPrefixedText() ); + $this->assertEquals( self::$pageRev->getParentId(), self::$lastNotifyArgs[5] ); + $this->assertEquals( $revision->getId(), self::$lastNotifyArgs[6] ); + $this->assertEquals( null, self::$lastNotifyArgs[7] ); + $this->assertEquals( 0, self::$lastNotifyArgs[8] ); + $this->assertEquals( '127.0.0.1', self::$lastNotifyArgs[9] ); + $this->assertEquals( 0, self::$lastNotifyArgs[10] ); + } + +} diff --git a/www/wiki/tests/phpunit/includes/changes/ChangesListBooleanFilterGroupTest.php b/www/wiki/tests/phpunit/includes/changes/ChangesListBooleanFilterGroupTest.php new file mode 100644 index 00000000..d80b6c10 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/changes/ChangesListBooleanFilterGroupTest.php @@ -0,0 +1,96 @@ +<?php + +use Wikimedia\TestingAccessWrapper; + +/** + * @covers ChangesListBooleanFilterGroup + */ +class ChangesListBooleanFilterGroupTest extends MediaWikiTestCase { + public function testIsFullCoverage() { + $hideGroupDefault = TestingAccessWrapper::newFromObject( + new ChangesListBooleanFilterGroup( [ + 'name' => 'groupName', + 'priority' => 1, + 'filters' => [], + ] ) + ); + + $this->assertSame( + true, + $hideGroupDefault->isFullCoverage + ); + } + + public function testGetJsData() { + $definition = [ + 'name' => 'some-group', + 'title' => 'some-group-title', + 'priority' => 1, + 'filters' => [ + [ + 'name' => 'hidefoo', + 'label' => 'foo-label', + 'description' => 'foo-description', + 'default' => true, + 'showHide' => 'showhidefoo', + 'priority' => 2, + ], + [ + 'name' => 'hidebar', + 'label' => 'bar-label', + 'description' => 'bar-description', + 'default' => false, + 'priority' => 4, + ] + ], + ]; + + $group = new ChangesListBooleanFilterGroup( $definition ); + + $this->assertArrayEquals( + [ + 'name' => 'some-group', + 'title' => 'some-group-title', + 'type' => ChangesListBooleanFilterGroup::TYPE, + 'priority' => 1, + 'filters' => [ + [ + 'name' => 'hidebar', + 'label' => 'bar-label', + 'description' => 'bar-description', + 'default' => false, + 'priority' => 4, + 'cssClass' => null, + 'conflicts' => [], + 'subset' => [], + 'defaultHighlightColor' => null, + ], + [ + 'name' => 'hidefoo', + 'label' => 'foo-label', + 'description' => 'foo-description', + 'default' => true, + 'priority' => 2, + 'cssClass' => null, + 'conflicts' => [], + 'subset' => [], + 'defaultHighlightColor' => null, + ], + ], + 'conflicts' => [], + 'fullCoverage' => true, + 'messageKeys' => [ + 'some-group-title', + 'bar-label', + 'bar-description', + 'foo-label', + 'foo-description', + ], + ], + + $group->getJsData(), + /** ordered= */ false, + /** named= */ true + ); + } +} diff --git a/www/wiki/tests/phpunit/includes/changes/ChangesListBooleanFilterTest.php b/www/wiki/tests/phpunit/includes/changes/ChangesListBooleanFilterTest.php new file mode 100644 index 00000000..35dc1a83 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/changes/ChangesListBooleanFilterTest.php @@ -0,0 +1,166 @@ +<?php + +/** + * @covers ChangesListBooleanFilter + */ +class ChangesListBooleanFilterTest extends MediaWikiTestCase { + public function testGetJsData() { + $group = new ChangesListBooleanFilterGroup( [ + 'name' => 'group', + 'priority' => 2, + 'filters' => [], + ] ); + + $definition = [ + 'group' => $group, + 'label' => 'main-label', + 'description' => 'main-description', + 'default' => 1, + 'priority' => 1, + ]; + + $fooFilter = new ChangesListBooleanFilter( + $definition + [ 'name' => 'hidefoo' ] + ); + + $barFilter = new ChangesListBooleanFilter( + $definition + [ 'name' => 'hidebar' ] + ); + + $bazFilter = new ChangesListBooleanFilter( + $definition + [ 'name' => 'hidebaz' ] + ); + + $fooFilter->conflictsWith( + $barFilter, + 'foo-bar-global-conflict', + 'foo-conflicts-bar', + 'bar-conflicts-foo' + ); + + $fooFilter->setAsSupersetOf( $bazFilter, 'foo-superset-of-baz' ); + + $fooData = $fooFilter->getJsData(); + $this->assertArrayEquals( + [ + 'name' => 'hidefoo', + 'label' => 'main-label', + 'description' => 'main-description', + 'default' => 1, + 'priority' => 1, + 'cssClass' => null, + 'defaultHighlightColor' => null, + 'conflicts' => [ + [ + 'group' => 'group', + 'filter' => 'hidebar', + 'globalDescription' => 'foo-bar-global-conflict', + 'contextDescription' => 'foo-conflicts-bar', + ] + ], + 'subset' => [ + [ + 'group' => 'group', + 'filter' => 'hidebaz', + ], + + ], + 'messageKeys' => [ + 'main-label', + 'main-description', + 'foo-bar-global-conflict', + 'foo-conflicts-bar', + ], + ], + $fooData, + /** ordered= */ false, + /** named= */ true + ); + + $barData = $barFilter->getJsData(); + $this->assertArrayEquals( + [ + 'name' => 'hidebar', + 'label' => 'main-label', + 'description' => 'main-description', + 'default' => 1, + 'priority' => 1, + 'cssClass' => null, + 'defaultHighlightColor' => null, + 'conflicts' => [ + [ + 'group' => 'group', + 'filter' => 'hidefoo', + 'globalDescription' => 'foo-bar-global-conflict', + 'contextDescription' => 'bar-conflicts-foo', + ] + ], + 'subset' => [], + 'messageKeys' => [ + 'main-label', + 'main-description', + 'foo-bar-global-conflict', + 'bar-conflicts-foo', + ], + ], + $barData, + /** ordered= */ false, + /** named= */ true + ); + } + + public function testIsFeatureAvailableOnStructuredUi() { + $groupA = new ChangesListBooleanFilterGroup( [ + 'name' => 'groupA', + 'priority' => 1, + 'filters' => [], + ] ); + + $foo = new ChangesListBooleanFilter( [ + 'name' => 'hidefoo', + 'group' => $groupA, + 'label' => 'foo-label', + 'description' => 'foo-description', + 'default' => true, + 'showHide' => 'showhidefoo', + 'priority' => 2, + ] ); + + $this->assertEquals( + true, + $foo->isFeatureAvailableOnStructuredUi(), + 'Same filter appears on both' + ); + + // Should only be legacy ones that haven't been ported yet + $bar = new ChangesListBooleanFilter( [ + 'name' => 'hidebar', + 'default' => true, + 'group' => $groupA, + 'showHide' => 'showhidebar', + 'priority' => 2, + ] ); + + $this->assertEquals( + false, + $bar->isFeatureAvailableOnStructuredUi(), + 'Only on unstructured UI' + ); + + $baz = new ChangesListBooleanFilter( [ + 'name' => 'hidebaz', + 'default' => true, + 'group' => $groupA, + 'showHide' => 'showhidebaz', + 'isReplacedInStructuredUi' => true, + 'priority' => 2, + ] ); + + $this->assertEquals( + true, + $baz->isFeatureAvailableOnStructuredUi(), + 'Legacy filter does not appear directly in new UI, but equivalent ' . + 'does and is marked with isReplacedInStructuredUi' + ); + } +} diff --git a/www/wiki/tests/phpunit/includes/changes/ChangesListFilterGroupTest.php b/www/wiki/tests/phpunit/includes/changes/ChangesListFilterGroupTest.php new file mode 100644 index 00000000..6190516e --- /dev/null +++ b/www/wiki/tests/phpunit/includes/changes/ChangesListFilterGroupTest.php @@ -0,0 +1,79 @@ +<?php + +/** + * @covers ChangesListFilterGroup + */ +class ChangesListFilterGroupTest extends MediaWikiTestCase { + /** + * phpcs:disable Generic.Files.LineLength + * @expectedException MWException + * @expectedExceptionMessage Group names may not contain '_'. Use the naming convention: 'camelCase' + * phpcs:enable + */ + public function testReservedCharacter() { + new MockChangesListFilterGroup( + [ + 'type' => 'some_type', + 'name' => 'group_name', + 'priority' => 1, + 'filters' => [], + ] + ); + } + + public function testAutoPriorities() { + $group = new MockChangesListFilterGroup( + [ + 'type' => 'some_type', + 'name' => 'groupName', + 'isFullCoverage' => true, + 'priority' => 1, + 'filters' => [ + [ 'name' => 'hidefoo' ], + [ 'name' => 'hidebar' ], + [ 'name' => 'hidebaz' ], + ], + ] + ); + + $filters = $group->getFilters(); + $this->assertEquals( + [ + -2, + -3, + -4, + ], + array_map( + function ( $f ) { + return $f->getPriority(); + }, + array_values( $filters ) + ) + ); + } + + // Get without warnings + public function testGetFilter() { + $group = new MockChangesListFilterGroup( + [ + 'type' => 'some_type', + 'name' => 'groupName', + 'isFullCoverage' => true, + 'priority' => 1, + 'filters' => [ + [ 'name' => 'foo' ], + ], + ] + ); + + $this->assertEquals( + 'foo', + $group->getFilter( 'foo' )->getName() + ); + + $this->assertEquals( + null, + $group->getFilter( 'bar' ) + ); + } +} diff --git a/www/wiki/tests/phpunit/includes/changes/ChangesListFilterTest.php b/www/wiki/tests/phpunit/includes/changes/ChangesListFilterTest.php new file mode 100644 index 00000000..039658e2 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/changes/ChangesListFilterTest.php @@ -0,0 +1,116 @@ +<?php + +use Wikimedia\TestingAccessWrapper; + +/** + * @covers ChangesListFilter + */ +class ChangesListFilterTest extends MediaWikiTestCase { + protected $group; + + public function setUp() { + $this->group = $this->getGroup( [ 'name' => 'group' ] ); + + parent::setUp(); + } + + protected function getGroup( $groupDefinition ) { + return new MockChangesListFilterGroup( + $groupDefinition + [ + 'isFullCoverage' => true, + 'type' => 'some_type', + 'name' => 'group', + 'filters' => [], + ] + ); + } + + /** + * phpcs:disable Generic.Files.LineLength + * @expectedException MWException + * @expectedExceptionMessage Filter names may not contain '_'. Use the naming convention: 'lowercase' + * phpcs:enable + */ + public function testReservedCharacter() { + $filter = new MockChangesListFilter( + [ + 'group' => $this->group, + 'name' => 'some_name', + 'priority' => 1, + ] + ); + } + + /** + * @expectedException MWException + * @expectedExceptionMessage Two filters in a group cannot have the same name: 'somename' + */ + public function testDuplicateName() { + new MockChangesListFilter( + [ + 'group' => $this->group, + 'name' => 'somename', + 'priority' => 1, + ] + ); + + new MockChangesListFilter( + [ + 'group' => $this->group, + 'name' => 'somename', + 'priority' => 2, + ] + ); + } + + /** + * @expectedException MWException + * @expectedExceptionMessage Supersets can only be defined for filters in the same group + */ + public function testSetAsSupersetOf() { + $groupA = $this->getGroup( + [ + 'name' => 'groupA', + 'filters' => [ + [ + 'name' => 'foo', + ], + [ + 'name' => 'bar', + ] + ], + ] + ); + + $groupB = $this->getGroup( + [ + 'name' => 'groupB', + 'filters' => [ + [ + 'name' => 'baz', + ], + ], + ] + ); + + $foo = TestingAccessWrapper::newFromObject( $groupA->getFilter( 'foo' ) ); + + $bar = $groupA->getFilter( 'bar' ); + + $baz = $groupB->getFilter( 'baz' ); + + $foo->setAsSupersetOf( $bar ); + $this->assertArrayEquals( [ + [ + 'group' => 'groupA', + 'filter' => 'bar', + ], + ], + $foo->subsetFilters, + /** ordered= */ false, + /** named= */ true + ); + + $foo->setAsSupersetOf( $baz ); + } +} diff --git a/www/wiki/tests/phpunit/includes/changes/ChangesListStringOptionsFilterGroupTest.php b/www/wiki/tests/phpunit/includes/changes/ChangesListStringOptionsFilterGroupTest.php new file mode 100644 index 00000000..b627178a --- /dev/null +++ b/www/wiki/tests/phpunit/includes/changes/ChangesListStringOptionsFilterGroupTest.php @@ -0,0 +1,279 @@ +<?php + +use Wikimedia\TestingAccessWrapper; + +/** + * @covers ChangesListStringOptionsFilterGroup + */ +class ChangesListStringOptionsFilterGroupTest extends MediaWikiTestCase { + /** + * @expectedException MWException + */ + public function testIsFullCoverage() { + $falseGroup = TestingAccessWrapper::newFromObject( + new ChangesListStringOptionsFilterGroup( [ + 'name' => 'group', + 'filters' => [], + 'isFullCoverage' => false, + 'queryCallable' => function () { + } + ] ) + ); + + $this->assertSame( + false, + $falseGroup->isFullCoverage + ); + + // Should throw due to missing isFullCoverage + $undefinedFullCoverageGroup = new ChangesListStringOptionsFilterGroup( [ + 'name' => 'othergroup', + 'filters' => [], + ] ); + } + + /** + * @param array $filterDefinitions Array of filter definitions + * @param array $expectedValues Array of values callback should receive + * @param string $input Value in URL + * + * @dataProvider provideModifyQuery + */ + public function testModifyQuery( $filterDefinitions, $expectedValues, $input ) { + $queryCallable = function ( + $className, + $ctx, + $dbr, + &$tables, + &$fields, + &$conds, + &$query_options, + &$join_conds, + $actualSelectedValues + ) use ( $expectedValues ) { + $this->assertSame( + $expectedValues, + $actualSelectedValues + ); + }; + + $groupDefinition = [ + 'name' => 'group', + 'default' => '', + 'isFullCoverage' => true, + 'filters' => $filterDefinitions, + 'queryCallable' => $queryCallable, + ]; + + $this->modifyQueryHelper( $groupDefinition, $input ); + } + + public function provideModifyQuery() { + $mixedFilters = [ + [ + 'name' => 'foo', + ], + [ + 'name' => 'baz', + ], + [ + 'name' => 'goo' + ], + ]; + + return [ + [ + $mixedFilters, + [ 'baz', 'foo', ], + 'foo;bar;BaZ;invalid', + ], + + [ + $mixedFilters, + [ 'baz', 'foo', 'goo' ], + 'all', + ], + ]; + } + + /** + * @param array $filterDefinitions Array of filter definitions + * @param string $input Value in URL + * @param string $message Message thrown by exception + * + * @dataProvider provideNoOpModifyQuery + */ + public function testNoOpModifyQuery( $filterDefinitions, $input, $message ) { + $noFiltersAllowedCallable = function ( + $className, + $ctx, + $dbr, + &$tables, + &$fields, + &$conds, + &$query_options, + &$join_conds, + $actualSelectedValues + ) use ( $message ) { + throw new MWException( $message ); + }; + + $groupDefinition = [ + 'name' => 'group', + 'default' => '', + 'isFullCoverage' => true, + 'filters' => $filterDefinitions, + 'queryCallable' => $noFiltersAllowedCallable, + ]; + + $this->modifyQueryHelper( $groupDefinition, $input ); + + $this->assertTrue( + true, + 'Test successfully completed without calling queryCallable' + ); + } + + public function provideNoOpModifyQuery() { + $noFilters = []; + + $normalFilters = [ + [ + 'name' => 'foo', + ], + [ + 'name' => 'bar', + ] + ]; + + return [ + [ + $noFilters, + 'disallowed1;disallowed3', + 'The queryCallable should not be called if there are no filters', + ], + + [ + $normalFilters, + '', + 'The queryCallable should not be called if no filters are selected', + ], + + [ + $normalFilters, + 'invalid1', + 'The queryCallable should not be called if no valid filters are selected', + ], + ]; + } + + protected function getSpecialPage() { + return $this->getMockBuilder( ChangesListSpecialPage::class ) + ->setConstructorArgs( [ + 'ChangesListSpecialPage', + '', + ] ) + ->getMockForAbstractClass(); + } + + /** + * @param array $groupDefinition Group definition + * @param string $input Value in URL + */ + protected function modifyQueryHelper( $groupDefinition, $input ) { + $ctx = $this->createMock( IContextSource::class ); + $dbr = $this->createMock( Wikimedia\Rdbms\IDatabase::class ); + $tables = $fields = $conds = $query_options = $join_conds = []; + + $group = new ChangesListStringOptionsFilterGroup( $groupDefinition ); + + $specialPage = $this->getSpecialPage(); + $opts = new FormOptions(); + $opts->add( $groupDefinition[ 'name' ], $input ); + + $group->modifyQuery( + $dbr, + $specialPage, + $tables, + $fields, + $conds, + $query_options, + $join_conds, + $opts, + true + ); + } + + public function testGetJsData() { + $definition = [ + 'name' => 'some-group', + 'title' => 'some-group-title', + 'default' => 'foo', + 'priority' => 1, + 'isFullCoverage' => false, + 'queryCallable' => function () { + }, + 'filters' => [ + [ + 'name' => 'foo', + 'label' => 'foo-label', + 'description' => 'foo-description', + 'priority' => 2, + ], + [ + 'name' => 'bar', + 'label' => 'bar-label', + 'description' => 'bar-description', + 'priority' => 4, + ] + ], + ]; + + $group = new ChangesListStringOptionsFilterGroup( $definition ); + + $this->assertArrayEquals( + [ + 'name' => 'some-group', + 'title' => 'some-group-title', + 'type' => ChangesListStringOptionsFilterGroup::TYPE, + 'default' => 'foo', + 'priority' => 1, + 'fullCoverage' => false, + 'filters' => [ + [ + 'name' => 'bar', + 'label' => 'bar-label', + 'description' => 'bar-description', + 'priority' => 4, + 'cssClass' => null, + 'conflicts' => [], + 'subset' => [], + 'defaultHighlightColor' => null, + ], + [ + 'name' => 'foo', + 'label' => 'foo-label', + 'description' => 'foo-description', + 'priority' => 2, + 'cssClass' => null, + 'conflicts' => [], + 'subset' => [], + 'defaultHighlightColor' => null, + ], + ], + 'conflicts' => [], + 'separator' => ';', + 'messageKeys' => [ + 'some-group-title', + 'bar-label', + 'bar-description', + 'foo-label', + 'foo-description', + ], + ], + $group->getJsData(), + /** ordered= */ false, + /** named= */ true + ); + } +} diff --git a/www/wiki/tests/phpunit/includes/changes/EnhancedChangesListTest.php b/www/wiki/tests/phpunit/includes/changes/EnhancedChangesListTest.php new file mode 100644 index 00000000..420fe749 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/changes/EnhancedChangesListTest.php @@ -0,0 +1,228 @@ +<?php + +/** + * @covers EnhancedChangesList + * + * @group Database + * + * @author Katie Filbert < aude.wiki@gmail.com > + */ +class EnhancedChangesListTest extends MediaWikiLangTestCase { + + /** + * @var TestRecentChangesHelper + */ + private $testRecentChangesHelper; + + public function __construct( $name = null, array $data = [], $dataName = '' ) { + parent::__construct( $name, $data, $dataName ); + + $this->testRecentChangesHelper = new TestRecentChangesHelper(); + } + + public function testBeginRecentChangesList_styleModules() { + $enhancedChangesList = $this->newEnhancedChangesList(); + $enhancedChangesList->beginRecentChangesList(); + + $styleModules = $enhancedChangesList->getOutput()->getModuleStyles(); + + $this->assertContains( + 'mediawiki.special.changeslist', + $styleModules, + 'has mediawiki.special.changeslist' + ); + + $this->assertContains( + 'mediawiki.special.changeslist.enhanced', + $styleModules, + 'has mediawiki.special.changeslist.enhanced' + ); + } + + public function testBeginRecentChangesList_jsModules() { + $enhancedChangesList = $this->newEnhancedChangesList(); + $enhancedChangesList->beginRecentChangesList(); + + $modules = $enhancedChangesList->getOutput()->getModules(); + + $this->assertContains( 'jquery.makeCollapsible', $modules, 'has jquery.makeCollapsible' ); + $this->assertContains( 'mediawiki.icon', $modules, 'has mediawiki.icon' ); + } + + public function testBeginRecentChangesList_html() { + $enhancedChangesList = $this->newEnhancedChangesList(); + $html = $enhancedChangesList->beginRecentChangesList(); + + $this->assertEquals( '<div class="mw-changeslist">', $html ); + } + + /** + * @todo more tests + */ + public function testRecentChangesLine() { + $enhancedChangesList = $this->newEnhancedChangesList(); + $enhancedChangesList->beginRecentChangesList(); + + $recentChange = $this->getEditChange( '20131103092153' ); + $html = $enhancedChangesList->recentChangesLine( $recentChange, false ); + + $this->assertInternalType( 'string', $html ); + + $recentChange2 = $this->getEditChange( '20131103092253' ); + $html = $enhancedChangesList->recentChangesLine( $recentChange2, false ); + + $this->assertEquals( '', $html ); + } + + public function testRecentChangesPrefix() { + $mockContext = $this->getMockBuilder( RequestContext::class ) + ->setMethods( [ 'getTitle' ] ) + ->getMock(); + $mockContext->method( 'getTitle' ) + ->will( $this->returnValue( Title::newFromText( 'Expected Context Title' ) ) ); + + // One group of two lines + $enhancedChangesList = $this->newEnhancedChangesList(); + $enhancedChangesList->setContext( $mockContext ); + $enhancedChangesList->setChangeLinePrefixer( function ( $rc, $changesList ) { + // Make sure RecentChange and ChangesList objects are the same + $this->assertEquals( 'Expected Context Title', $changesList->getContext()->getTitle() ); + $this->assertTrue( $rc->getTitle() == 'Cat' || $rc->getTitle() == 'Dog' ); + return 'Hello world prefix'; + } ); + $enhancedChangesList->beginRecentChangesList(); + + $recentChange = $this->getEditChange( '20131103092153' ); + $enhancedChangesList->recentChangesLine( $recentChange ); + $recentChange = $this->getEditChange( '20131103092154' ); + $enhancedChangesList->recentChangesLine( $recentChange ); + + $html = $enhancedChangesList->endRecentChangesList(); + + $this->assertRegExp( '/Hello world prefix/', $html ); + + // Two separate lines + $enhancedChangesList->beginRecentChangesList(); + + $recentChange = $this->getEditChange( '20131103092153' ); + $enhancedChangesList->recentChangesLine( $recentChange ); + $recentChange = $this->getEditChange( '20131103092154', 'Dog' ); + $enhancedChangesList->recentChangesLine( $recentChange ); + + $html = $enhancedChangesList->endRecentChangesList(); + + preg_match_all( '/Hello world prefix/', $html, $matches ); + $this->assertCount( 2, $matches[0] ); + } + + public function testCategorizationLineFormatting() { + $html = $this->createCategorizationLine( + $this->getCategorizationChange( '20150629191735', 0, 0 ) + ); + $this->assertNotContains( '(diff | hist)', strip_tags( $html ) ); + } + + public function testCategorizationLineFormattingWithRevision() { + $html = $this->createCategorizationLine( + $this->getCategorizationChange( '20150629191735', 1025, 1024 ) + ); + $this->assertContains( '(diff | hist)', strip_tags( $html ) ); + } + + /** + * @todo more tests for actual formatting, this is more of a smoke test + */ + public function testEndRecentChangesList() { + $enhancedChangesList = $this->newEnhancedChangesList(); + $enhancedChangesList->beginRecentChangesList(); + + $recentChange = $this->getEditChange( '20131103092153' ); + $enhancedChangesList->recentChangesLine( $recentChange, false ); + + $html = $enhancedChangesList->endRecentChangesList(); + $this->assertRegExp( + '/data-mw-revid="5" data-mw-ts="20131103092153" class="[^"]*mw-enhanced-rc[^"]*"/', + $html + ); + + $recentChange2 = $this->getEditChange( '20131103092253' ); + $enhancedChangesList->recentChangesLine( $recentChange2, false ); + + $html = $enhancedChangesList->endRecentChangesList(); + + preg_match_all( '/td class="mw-enhanced-rc-nested"/', $html, $matches ); + $this->assertCount( 2, $matches[0] ); + + preg_match_all( '/data-target-page="Cat"/', $html, $matches ); + $this->assertCount( 2, $matches[0] ); + + $recentChange3 = $this->getLogChange(); + $enhancedChangesList->recentChangesLine( $recentChange3, false ); + + $html = $enhancedChangesList->endRecentChangesList(); + $this->assertContains( 'data-mw-logaction="foo/bar"', $html ); + $this->assertContains( 'data-mw-logid="25"', $html ); + $this->assertContains( 'data-target-page="Title"', $html ); + } + + /** + * @return EnhancedChangesList + */ + private function newEnhancedChangesList() { + $user = User::newFromId( 0 ); + $context = $this->testRecentChangesHelper->getTestContext( $user ); + + return new EnhancedChangesList( $context ); + } + + /** + * @return RecentChange + */ + private function getEditChange( $timestamp, $pageTitle = 'Cat' ) { + $user = $this->getMutableTestUser()->getUser(); + $recentChange = $this->testRecentChangesHelper->makeEditRecentChange( + $user, $pageTitle, 0, 5, 191, $timestamp, 0, 0 + ); + + return $recentChange; + } + + private function getLogChange() { + $user = $this->getMutableTestUser()->getUser(); + $recentChange = $this->testRecentChangesHelper->makeLogRecentChange( 'foo', 'bar', $user, + 'Title', '20131103092153', 0, 0 + ); + + return $recentChange; + } + + /** + * @return RecentChange + */ + private function getCategorizationChange( $timestamp, $thisId, $lastId ) { + $wikiPage = new WikiPage( Title::newFromText( 'Testpage' ) ); + $wikiPage->doEditContent( new WikitextContent( 'Some random text' ), 'page created' ); + + $wikiPage = new WikiPage( Title::newFromText( 'Category:Foo' ) ); + $wikiPage->doEditContent( new WikitextContent( 'Some random text' ), 'category page created' ); + + $user = $this->getMutableTestUser()->getUser(); + $recentChange = $this->testRecentChangesHelper->makeCategorizationRecentChange( + $user, 'Category:Foo', $wikiPage->getId(), $thisId, $lastId, $timestamp + ); + + return $recentChange; + } + + private function createCategorizationLine( $recentChange ) { + $enhancedChangesList = $this->newEnhancedChangesList(); + $cacheEntry = $this->testRecentChangesHelper->getCacheEntry( $recentChange ); + + $reflection = new \ReflectionClass( get_class( $enhancedChangesList ) ); + $method = $reflection->getMethod( 'recentChangesBlockLine' ); + $method->setAccessible( true ); + + return $method->invokeArgs( $enhancedChangesList, [ $cacheEntry ] ); + } + +} diff --git a/www/wiki/tests/phpunit/includes/changes/OldChangesListTest.php b/www/wiki/tests/phpunit/includes/changes/OldChangesListTest.php new file mode 100644 index 00000000..91dc7312 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/changes/OldChangesListTest.php @@ -0,0 +1,234 @@ +<?php + +/** + * @covers OldChangesList + * + * @todo add tests to cover article link, timestamp, character difference, + * log entry, user tool links, direction marks, tags, rollback, + * watching users, and date header. + * + * @group Database + * + * @author Katie Filbert < aude.wiki@gmail.com > + */ +class OldChangesListTest extends MediaWikiLangTestCase { + + /** + * @var TestRecentChangesHelper + */ + private $testRecentChangesHelper; + + public function __construct( $name = null, array $data = [], $dataName = '' ) { + parent::__construct( $name, $data, $dataName ); + + $this->testRecentChangesHelper = new TestRecentChangesHelper(); + } + + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( [ + 'wgArticlePath' => '/wiki/$1', + ] ); + $this->setUserLang( 'qqx' ); + } + + /** + * @dataProvider recentChangesLine_CssForLineNumberProvider + */ + public function testRecentChangesLine_CssForLineNumber( $expected, $linenumber, $message ) { + $oldChangesList = $this->getOldChangesList(); + $recentChange = $this->getEditChange(); + + $line = $oldChangesList->recentChangesLine( $recentChange, false, $linenumber ); + + $this->assertRegExp( $expected, $line, $message ); + } + + public function recentChangesLine_CssForLineNumberProvider() { + return [ + [ '/mw-line-odd/', 1, 'odd line number' ], + [ '/mw-line-even/', 2, 'even line number' ] + ]; + } + + public function testRecentChangesLine_NotWatchedCssClass() { + $oldChangesList = $this->getOldChangesList(); + $recentChange = $this->getEditChange(); + + $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 ); + + $this->assertRegExp( '/mw-changeslist-line-not-watched/', $line ); + } + + public function testRecentChangesLine_WatchedCssClass() { + $oldChangesList = $this->getOldChangesList(); + $recentChange = $this->getEditChange(); + + $line = $oldChangesList->recentChangesLine( $recentChange, true, 1 ); + + $this->assertRegExp( '/mw-changeslist-line-watched/', $line ); + } + + public function testRecentChangesLine_LogTitle() { + $oldChangesList = $this->getOldChangesList(); + $recentChange = $this->getLogChange( 'delete', 'delete' ); + + $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 ); + + $this->assertRegExp( '/href="\/wiki\/Special:Log\/delete/', $line, 'link has href attribute' ); + $this->assertRegExp( '/title="Special:Log\/delete/', $line, 'link has title attribute' ); + $this->assertRegExp( "/dellogpage/", $line, 'link text' ); + } + + public function testRecentChangesLine_DiffHistLinks() { + $oldChangesList = $this->getOldChangesList(); + $recentChange = $this->getEditChange(); + + $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 ); + + $this->assertRegExp( + '/title=Cat&curid=20131103212153&diff=5&oldid=191/', + $line, + 'assert diff link' + ); + + $this->assertRegExp( + '/title=Cat&curid=20131103212153&action=history"/', + $line, + 'assert history link' + ); + } + + public function testRecentChangesLine_Flags() { + $oldChangesList = $this->getOldChangesList(); + $recentChange = $this->getNewBotEditChange(); + + $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 ); + + $this->assertContains( + '<abbr class="newpage" title="(recentchanges-label-newpage)">(newpageletter)</abbr>', + $line, + 'new page flag' + ); + + $this->assertContains( + '<abbr class="botedit" title="(recentchanges-label-bot)">(boteditletter)</abbr>', + $line, + 'bot flag' + ); + } + + public function testRecentChangesLine_Attribs() { + $recentChange = $this->getEditChange(); + $recentChange->mAttribs['ts_tags'] = 'vandalism,newbie'; + + $oldChangesList = $this->getOldChangesList(); + $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 ); + + $this->assertRegExp( + '/<li data-mw-revid="\d+" data-mw-ts="\d+" class="[\w\s-]*mw-tag-vandalism[\w\s-]*">/', + $line + ); + $this->assertRegExp( + '/<li data-mw-revid="\d+" data-mw-ts="\d+" class="[\w\s-]*mw-tag-newbie[\w\s-]*">/', + $line + ); + } + + public function testRecentChangesLine_numberOfWatchingUsers() { + $oldChangesList = $this->getOldChangesList(); + + $recentChange = $this->getEditChange(); + $recentChange->numberofWatchingusers = 100; + + $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 ); + $this->assertRegExp( "/(number_of_watching_users_RCview: 100)/", $line ); + } + + public function testRecentChangesLine_watchlistCssClass() { + $oldChangesList = $this->getOldChangesList(); + $oldChangesList->setWatchlistDivs( true ); + + $recentChange = $this->getEditChange(); + $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 ); + $this->assertRegExp( "/watchlist-0-Cat/", $line ); + } + + public function testRecentChangesLine_dataAttribute() { + $oldChangesList = $this->getOldChangesList(); + $oldChangesList->setWatchlistDivs( true ); + + $recentChange = $this->getEditChange(); + $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 ); + $this->assertRegExp( '/data-target-page=\"Cat\"/', $line ); + + $recentChange = $this->getLogChange( 'delete', 'delete' ); + $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 ); + $this->assertRegExp( '/data-target-page="Abc"/', $line ); + } + + public function testRecentChangesLine_prefix() { + $mockContext = $this->getMockBuilder( RequestContext::class ) + ->setMethods( [ 'getTitle' ] ) + ->getMock(); + $mockContext->method( 'getTitle' ) + ->will( $this->returnValue( Title::newFromText( 'Expected Context Title' ) ) ); + + $oldChangesList = $this->getOldChangesList(); + $oldChangesList->setContext( $mockContext ); + $recentChange = $this->getEditChange(); + + $oldChangesList->setChangeLinePrefixer( function ( $rc, $changesList ) { + // Make sure RecentChange and ChangesList objects are the same + $this->assertEquals( 'Expected Context Title', $changesList->getContext()->getTitle() ); + $this->assertEquals( 'Cat', $rc->getTitle() ); + return 'I am a prefix'; + } ); + $line = $oldChangesList->recentChangesLine( $recentChange ); + $this->assertRegExp( "/I am a prefix/", $line ); + } + + private function getNewBotEditChange() { + $user = $this->getMutableTestUser()->getUser(); + + $recentChange = $this->testRecentChangesHelper->makeNewBotEditRecentChange( + $user, 'Abc', '20131103212153', 5, 191, 190, 0, 0 + ); + + return $recentChange; + } + + private function getLogChange( $logType, $logAction ) { + $user = $this->getMutableTestUser()->getUser(); + + $recentChange = $this->testRecentChangesHelper->makeLogRecentChange( + $logType, $logAction, $user, 'Abc', '20131103212153', 0, 0 + ); + + return $recentChange; + } + + private function getEditChange() { + $user = $this->getMutableTestUser()->getUser(); + $recentChange = $this->testRecentChangesHelper->makeEditRecentChange( + $user, 'Cat', '20131103212153', 5, 191, 190, 0, 0 + ); + + return $recentChange; + } + + private function getOldChangesList() { + $context = $this->getContext(); + return new OldChangesList( $context ); + } + + private function getContext() { + $user = $this->getMutableTestUser()->getUser(); + $context = $this->testRecentChangesHelper->getTestContext( $user ); + $context->setLanguage( 'qqx' ); + + return $context; + } + +} diff --git a/www/wiki/tests/phpunit/includes/changes/RCCacheEntryFactoryTest.php b/www/wiki/tests/phpunit/includes/changes/RCCacheEntryFactoryTest.php new file mode 100644 index 00000000..b1857ccc --- /dev/null +++ b/www/wiki/tests/phpunit/includes/changes/RCCacheEntryFactoryTest.php @@ -0,0 +1,236 @@ +<?php + +use MediaWiki\Linker\LinkRenderer; +use MediaWiki\MediaWikiServices; + +/** + * @covers RCCacheEntryFactory + * + * @group Database + * + * @author Katie Filbert < aude.wiki@gmail.com > + */ +class RCCacheEntryFactoryTest extends MediaWikiLangTestCase { + + /** + * @var TestRecentChangesHelper + */ + private $testRecentChangesHelper; + + /** + * @var LinkRenderer + */ + private $linkRenderer; + + public function __construct( $name = null, array $data = [], $dataName = '' ) { + parent::__construct( $name, $data, $dataName ); + + $this->testRecentChangesHelper = new TestRecentChangesHelper(); + } + + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( [ + 'wgArticlePath' => '/wiki/$1' + ] ); + + $this->linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer(); + } + + public function testNewFromRecentChange() { + $user = $this->getMutableTestUser()->getUser(); + $recentChange = $this->testRecentChangesHelper->makeEditRecentChange( + $user, + 'Xyz', + 5, // curid + 191, // thisid + 190, // lastid + '20131103212153', + 0, // counter + 0 // number of watching users + ); + $cacheEntryFactory = new RCCacheEntryFactory( + $this->getContext(), + $this->getMessages(), + $this->linkRenderer + ); + $cacheEntry = $cacheEntryFactory->newFromRecentChange( $recentChange, false ); + + $this->assertInstanceOf( RCCacheEntry::class, $cacheEntry ); + + $this->assertEquals( false, $cacheEntry->watched, 'watched' ); + $this->assertEquals( '21:21', $cacheEntry->timestamp, 'timestamp' ); + $this->assertEquals( 0, $cacheEntry->numberofWatchingusers, 'watching users' ); + $this->assertEquals( false, $cacheEntry->unpatrolled, 'unpatrolled' ); + + $this->assertUserLinks( $user->getName(), $cacheEntry ); + $this->assertTitleLink( 'Xyz', $cacheEntry ); + + $diff = [ 'curid' => 5, 'diff' => 191, 'oldid' => 190 ]; + $cur = [ 'curid' => 5, 'diff' => 0, 'oldid' => 191 ]; + $this->assertQueryLink( 'cur', $cur, $cacheEntry->curlink ); + $this->assertQueryLink( 'prev', $diff, $cacheEntry->lastlink ); + $this->assertQueryLink( 'diff', $diff, $cacheEntry->difflink ); + } + + public function testNewForDeleteChange() { + $user = $this->getMutableTestUser()->getUser(); + $recentChange = $this->testRecentChangesHelper->makeLogRecentChange( + 'delete', + 'delete', + $user, + 'Abc', + '20131103212153', + 0, // counter + 0 // number of watching users + ); + $cacheEntryFactory = new RCCacheEntryFactory( + $this->getContext(), + $this->getMessages(), + $this->linkRenderer + ); + $cacheEntry = $cacheEntryFactory->newFromRecentChange( $recentChange, false ); + + $this->assertInstanceOf( RCCacheEntry::class, $cacheEntry ); + + $this->assertEquals( false, $cacheEntry->watched, 'watched' ); + $this->assertEquals( '21:21', $cacheEntry->timestamp, 'timestamp' ); + $this->assertEquals( 0, $cacheEntry->numberofWatchingusers, 'watching users' ); + $this->assertEquals( false, $cacheEntry->unpatrolled, 'unpatrolled' ); + + $this->assertDeleteLogLink( $cacheEntry ); + $this->assertUserLinks( $user->getName(), $cacheEntry ); + + $this->assertEquals( 'cur', $cacheEntry->curlink, 'cur link for delete log or rev' ); + $this->assertEquals( 'diff', $cacheEntry->difflink, 'diff link for delete log or rev' ); + $this->assertEquals( 'prev', $cacheEntry->lastlink, 'pref link for delete log or rev' ); + } + + public function testNewForRevUserDeleteChange() { + $user = $this->getMutableTestUser()->getUser(); + $recentChange = $this->testRecentChangesHelper->makeDeletedEditRecentChange( + $user, + 'Zzz', + '20131103212153', + 191, // thisid + 190, // lastid + '20131103212153', + 0, // counter + 0 // number of watching users + ); + $cacheEntryFactory = new RCCacheEntryFactory( + $this->getContext(), + $this->getMessages(), + $this->linkRenderer + ); + $cacheEntry = $cacheEntryFactory->newFromRecentChange( $recentChange, false ); + + $this->assertInstanceOf( RCCacheEntry::class, $cacheEntry ); + + $this->assertEquals( false, $cacheEntry->watched, 'watched' ); + $this->assertEquals( '21:21', $cacheEntry->timestamp, 'timestamp' ); + $this->assertEquals( 0, $cacheEntry->numberofWatchingusers, 'watching users' ); + $this->assertEquals( false, $cacheEntry->unpatrolled, 'unpatrolled' ); + + $this->assertRevDel( $cacheEntry ); + $this->assertTitleLink( 'Zzz', $cacheEntry ); + + $this->assertEquals( 'cur', $cacheEntry->curlink, 'cur link for delete log or rev' ); + $this->assertEquals( 'diff', $cacheEntry->difflink, 'diff link for delete log or rev' ); + $this->assertEquals( 'prev', $cacheEntry->lastlink, 'pref link for delete log or rev' ); + } + + private function assertValidHTML( $actual ) { + // Throws if invalid + $doc = PHPUnit_Util_XML::load( $actual, /* isHtml */ true ); + } + + private function assertUserLinks( $user, $cacheEntry ) { + $this->assertValidHTML( $cacheEntry->userlink ); + $this->assertRegExp( + '#^<a .*class="new mw-userlink".*><bdi>' . $user . '</bdi></a>#', + $cacheEntry->userlink, + 'verify user link' + ); + + $this->assertValidHTML( $cacheEntry->usertalklink ); + $this->assertRegExp( + '#^ <span class="mw-usertoollinks">\(.*<a .+>talk</a>.*\)</span>#', + $cacheEntry->usertalklink, + 'verify user talk link' + ); + + $this->assertValidHTML( $cacheEntry->usertalklink ); + $this->assertRegExp( + '#^ <span class="mw-usertoollinks">\(.*<a .+>contribs</a>.*\)</span>$#', + $cacheEntry->usertalklink, + 'verify user tool links' + ); + } + + private function assertDeleteLogLink( $cacheEntry ) { + $this->assertEquals( + '(<a href="/wiki/Special:Log/delete" title="Special:Log/delete">Deletion log</a>)', + $cacheEntry->link, + 'verify deletion log link' + ); + + $this->assertValidHTML( $cacheEntry->link ); + } + + private function assertRevDel( $cacheEntry ) { + $this->assertEquals( + ' <span class="history-deleted">(username removed)</span>', + $cacheEntry->userlink, + 'verify user link for change with deleted revision and user' + ); + $this->assertValidHTML( $cacheEntry->userlink ); + } + + private function assertTitleLink( $title, $cacheEntry ) { + $this->assertEquals( + '<a href="/wiki/' . $title . '" title="' . $title . '">' . $title . '</a>', + $cacheEntry->link, + 'verify title link' + ); + $this->assertValidHTML( $cacheEntry->link ); + } + + private function assertQueryLink( $content, $params, $link ) { + $this->assertRegExp( + "#^<a .+>$content</a>$#", + $link, + 'verify query link element' + ); + $this->assertValidHTML( $link ); + + foreach ( $params as $key => $value ) { + $this->assertRegExp( '/' . $key . '=' . $value . '/', $link, "verify $key link params" ); + } + } + + private function getMessages() { + return [ + 'cur' => 'cur', + 'diff' => 'diff', + 'hist' => 'hist', + 'enhancedrc-history' => 'history', + 'last' => 'prev', + 'blocklink' => 'block', + 'history' => 'Page history', + 'semicolon-separator' => '; ', + 'pipe-separator' => ' | ' + ]; + } + + private function getContext() { + $user = $this->getMutableTestUser()->getUser(); + $context = $this->testRecentChangesHelper->getTestContext( $user ); + + $title = Title::newFromText( 'RecentChanges', NS_SPECIAL ); + $context->setTitle( $title ); + + return $context; + } +} diff --git a/www/wiki/tests/phpunit/includes/changes/RecentChangeTest.php b/www/wiki/tests/phpunit/includes/changes/RecentChangeTest.php new file mode 100644 index 00000000..333eb286 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/changes/RecentChangeTest.php @@ -0,0 +1,224 @@ +<?php +use Wikimedia\ScopedCallback; + +/** + * @group Database + */ +class RecentChangeTest extends MediaWikiTestCase { + protected $title; + protected $target; + protected $user; + protected $user_comment; + protected $context; + + public function setUp() { + parent::setUp(); + + $this->title = Title::newFromText( 'SomeTitle' ); + $this->target = Title::newFromText( 'TestTarget' ); + $this->user = User::newFromName( 'UserName' ); + + $this->user_comment = '<User comment about action>'; + $this->context = RequestContext::newExtraneousContext( $this->title ); + } + + /** + * @covers RecentChange::newFromRow + * @covers RecentChange::loadFromRow + */ + public function testNewFromRow() { + $user = $this->getTestUser()->getUser(); + $actorId = $user->getActorId(); + + $row = new stdClass(); + $row->rc_foo = 'AAA'; + $row->rc_timestamp = '20150921134808'; + $row->rc_deleted = 'bar'; + $row->rc_comment_text = 'comment'; + $row->rc_comment_data = null; + $row->rc_user = $user->getId(); + + $rc = RecentChange::newFromRow( $row ); + + $expected = [ + 'rc_foo' => 'AAA', + 'rc_timestamp' => '20150921134808', + 'rc_deleted' => 'bar', + 'rc_comment' => 'comment', + 'rc_comment_text' => 'comment', + 'rc_comment_data' => null, + 'rc_user' => $user->getId(), + 'rc_user_text' => $user->getName(), + 'rc_actor' => $actorId, + ]; + $this->assertEquals( $expected, $rc->getAttributes() ); + + $row = new stdClass(); + $row->rc_foo = 'AAA'; + $row->rc_timestamp = '20150921134808'; + $row->rc_deleted = 'bar'; + $row->rc_comment = 'comment'; + $row->rc_user = $user->getId(); + + Wikimedia\suppressWarnings(); + $rc = RecentChange::newFromRow( $row ); + Wikimedia\restoreWarnings(); + + $expected = [ + 'rc_foo' => 'AAA', + 'rc_timestamp' => '20150921134808', + 'rc_deleted' => 'bar', + 'rc_comment' => 'comment', + 'rc_comment_text' => 'comment', + 'rc_comment_data' => null, + 'rc_user' => $user->getId(), + 'rc_user_text' => $user->getName(), + 'rc_actor' => $actorId, + ]; + $this->assertEquals( $expected, $rc->getAttributes() ); + } + + /** + * @covers RecentChange::parseParams + */ + public function testParseParams() { + $params = [ + 'root' => [ + 'A' => 1, + 'B' => 'two' + ] + ]; + + $this->assertParseParams( + $params, + 'a:1:{s:4:"root";a:2:{s:1:"A";i:1;s:1:"B";s:3:"two";}}' + ); + + $this->assertParseParams( + null, + null + ); + + $this->assertParseParams( + null, + serialize( false ) + ); + + $this->assertParseParams( + null, + 'not-an-array' + ); + } + + /** + * @param array $expectedParseParams + * @param string|null $rawRcParams + */ + protected function assertParseParams( $expectedParseParams, $rawRcParams ) { + $rc = new RecentChange; + $rc->setAttribs( [ 'rc_params' => $rawRcParams ] ); + + $actualParseParams = $rc->parseParams(); + + $this->assertEquals( $expectedParseParams, $actualParseParams ); + } + + /** + * @return array + */ + public function provideIsInRCLifespan() { + return [ + [ 6000, -3000, 0, true ], + [ 3000, -6000, 0, false ], + [ 6000, -3000, 6000, true ], + [ 3000, -6000, 6000, true ], + ]; + } + + /** + * @covers RecentChange::isInRCLifespan + * @dataProvider provideIsInRCLifespan + */ + public function testIsInRCLifespan( $maxAge, $offset, $tolerance, $expected ) { + $this->setMwGlobals( 'wgRCMaxAge', $maxAge ); + // Calculate this here instead of the data provider because the provider + // is expanded early on and the full test suite may take longer than 100 minutes + // when coverage is enabled. + $timestamp = time() + $offset; + $this->assertEquals( $expected, RecentChange::isInRCLifespan( $timestamp, $tolerance ) ); + } + + public function provideRCTypes() { + return [ + [ RC_EDIT, 'edit' ], + [ RC_NEW, 'new' ], + [ RC_LOG, 'log' ], + [ RC_EXTERNAL, 'external' ], + [ RC_CATEGORIZE, 'categorize' ], + ]; + } + + /** + * @dataProvider provideRCTypes + * @covers RecentChange::parseFromRCType + */ + public function testParseFromRCType( $rcType, $type ) { + $this->assertEquals( $type, RecentChange::parseFromRCType( $rcType ) ); + } + + /** + * @dataProvider provideRCTypes + * @covers RecentChange::parseToRCType + */ + public function testParseToRCType( $rcType, $type ) { + $this->assertEquals( $rcType, RecentChange::parseToRCType( $type ) ); + } + + /** + * @return PHPUnit_Framework_MockObject_MockObject|PageProps + */ + private function getMockPageProps() { + return $this->getMockBuilder( PageProps::class ) + ->disableOriginalConstructor() + ->getMock(); + } + + public function provideCategoryContent() { + return [ + [ true ], + [ false ], + ]; + } + + /** + * @dataProvider provideCategoryContent + * @covers RecentChange::newForCategorization + */ + public function testHiddenCategoryChange( $isHidden ) { + $categoryTitle = Title::newFromText( 'CategoryPage', NS_CATEGORY ); + + $pageProps = $this->getMockPageProps(); + $pageProps->expects( $this->once() ) + ->method( 'getProperties' ) + ->with( $categoryTitle, 'hiddencat' ) + ->will( $this->returnValue( $isHidden ? [ $categoryTitle->getArticleID() => '' ] : [] ) ); + + $scopedOverride = PageProps::overrideInstance( $pageProps ); + + $rc = RecentChange::newForCategorization( + '0', + $categoryTitle, + $this->user, + $this->user_comment, + $this->title, + $categoryTitle->getLatestRevID(), + $categoryTitle->getLatestRevID(), + '0', + false + ); + + $this->assertEquals( $isHidden, $rc->getParam( 'hidden-cat' ) ); + + ScopedCallback::consume( $scopedOverride ); + } +} diff --git a/www/wiki/tests/phpunit/includes/changes/TestRecentChangesHelper.php b/www/wiki/tests/phpunit/includes/changes/TestRecentChangesHelper.php new file mode 100644 index 00000000..2c309487 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/changes/TestRecentChangesHelper.php @@ -0,0 +1,170 @@ +<?php + +use MediaWiki\MediaWikiServices; + +/** + * Helper for generating test recent changes entries. + * + * @author Katie Filbert < aude.wiki@gmail.com > + */ +class TestRecentChangesHelper { + + public function makeEditRecentChange( User $user, $titleText, $curid, $thisid, $lastid, + $timestamp, $counter, $watchingUsers + ) { + $attribs = array_merge( + $this->getDefaultAttributes( $titleText, $timestamp ), + [ + 'rc_user' => $user->getId(), + 'rc_user_text' => $user->getName(), + 'rc_this_oldid' => $thisid, + 'rc_last_oldid' => $lastid, + 'rc_cur_id' => $curid + ] + ); + + return $this->makeRecentChange( $attribs, $counter, $watchingUsers ); + } + + public function makeLogRecentChange( + $logType, $logAction, User $user, $titleText, $timestamp, $counter, $watchingUsers + ) { + $attribs = array_merge( + $this->getDefaultAttributes( $titleText, $timestamp ), + [ + 'rc_cur_id' => 0, + 'rc_user' => $user->getId(), + 'rc_user_text' => $user->getName(), + 'rc_this_oldid' => 0, + 'rc_last_oldid' => 0, + 'rc_old_len' => null, + 'rc_new_len' => null, + 'rc_type' => 3, + 'rc_logid' => 25, + 'rc_log_type' => $logType, + 'rc_log_action' => $logAction, + 'rc_source' => 'mw.log' + ] + ); + + return $this->makeRecentChange( $attribs, $counter, $watchingUsers ); + } + + public function makeDeletedEditRecentChange( User $user, $titleText, $timestamp, $curid, + $thisid, $lastid, $counter, $watchingUsers + ) { + $attribs = array_merge( + $this->getDefaultAttributes( $titleText, $timestamp ), + [ + 'rc_user' => $user->getId(), + 'rc_user_text' => $user->getName(), + 'rc_deleted' => 5, + 'rc_cur_id' => $curid, + 'rc_this_oldid' => $thisid, + 'rc_last_oldid' => $lastid + ] + ); + + return $this->makeRecentChange( $attribs, $counter, $watchingUsers ); + } + + public function makeNewBotEditRecentChange( User $user, $titleText, $curid, $thisid, $lastid, + $timestamp, $counter, $watchingUsers + ) { + $attribs = array_merge( + $this->getDefaultAttributes( $titleText, $timestamp ), + [ + 'rc_user' => $user->getId(), + 'rc_user_text' => $user->getName(), + 'rc_this_oldid' => $thisid, + 'rc_last_oldid' => $lastid, + 'rc_cur_id' => $curid, + 'rc_type' => 1, + 'rc_bot' => 1, + 'rc_source' => 'mw.new' + ] + ); + + return $this->makeRecentChange( $attribs, $counter, $watchingUsers ); + } + + private function makeRecentChange( $attribs, $counter, $watchingUsers ) { + $change = new RecentChange(); + $change->setAttribs( $attribs ); + $change->counter = $counter; + $change->numberofWatchingusers = $watchingUsers; + + return $change; + } + + public function getCacheEntry( $recentChange ) { + $rcCacheFactory = new RCCacheEntryFactory( + new RequestContext(), + [ 'diff' => 'diff', 'cur' => 'cur', 'last' => 'last' ], + MediaWikiServices::getInstance()->getLinkRenderer() + ); + return $rcCacheFactory->newFromRecentChange( $recentChange, false ); + } + + public function makeCategorizationRecentChange( + User $user, $titleText, $curid, $thisid, $lastid, $timestamp + ) { + $attribs = array_merge( + $this->getDefaultAttributes( $titleText, $timestamp ), + [ + 'rc_type' => RC_CATEGORIZE, + 'rc_user' => $user->getId(), + 'rc_user_text' => $user->getName(), + 'rc_this_oldid' => $thisid, + 'rc_last_oldid' => $lastid, + 'rc_cur_id' => $curid, + 'rc_comment' => '[[:Testpage]] added to category', + 'rc_comment_text' => '[[:Testpage]] added to category', + 'rc_comment_data' => null, + 'rc_old_len' => 0, + 'rc_new_len' => 0, + ] + ); + + return $this->makeRecentChange( $attribs, 0, 0 ); + } + + private function getDefaultAttributes( $titleText, $timestamp ) { + return [ + 'rc_id' => 545, + 'rc_user' => 0, + 'rc_user_text' => '127.0.0.1', + 'rc_ip' => '127.0.0.1', + 'rc_title' => $titleText, + 'rc_namespace' => 0, + 'rc_timestamp' => $timestamp, + 'rc_old_len' => 212, + 'rc_new_len' => 188, + 'rc_comment' => '', + 'rc_comment_text' => '', + 'rc_comment_data' => null, + 'rc_minor' => 0, + 'rc_bot' => 0, + 'rc_type' => 0, + 'rc_patrolled' => 1, + 'rc_deleted' => 0, + 'rc_logid' => 0, + 'rc_log_type' => null, + 'rc_log_action' => '', + 'rc_params' => '', + 'rc_source' => 'mw.edit' + ]; + } + + public function getTestContext( User $user ) { + $context = new RequestContext(); + $context->setLanguage( 'en' ); + + $context->setUser( $user ); + + $title = Title::newFromText( 'RecentChanges', NS_SPECIAL ); + $context->setTitle( $title ); + + return $context; + } +} |