diff options
Diffstat (limited to 'tests/lib/NavigationManagerTest.php')
-rw-r--r-- | tests/lib/NavigationManagerTest.php | 513 |
1 files changed, 459 insertions, 54 deletions
diff --git a/tests/lib/NavigationManagerTest.php b/tests/lib/NavigationManagerTest.php index d5c827fe1cb..53d7e81302b 100644 --- a/tests/lib/NavigationManagerTest.php +++ b/tests/lib/NavigationManagerTest.php @@ -1,13 +1,9 @@ <?php + /** - * ownCloud - * - * @author Joas Schilling - * @copyright 2015 Joas Schilling nickvergessen@owncloud.com - * - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace Test; @@ -16,6 +12,7 @@ use OC\App\AppManager; use OC\Group\Manager; use OC\NavigationManager; use OC\SubAdmin; +use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; use OCP\IGroupManager; use OCP\IL10N; @@ -23,6 +20,10 @@ use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserSession; use OCP\L10N\IFactory; +use OCP\Navigation\Events\LoadAdditionalEntriesEvent; +use OCP\Util; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; class NavigationManagerTest extends TestCase { /** @var AppManager|\PHPUnit\Framework\MockObject\MockObject */ @@ -38,8 +39,11 @@ class NavigationManagerTest extends TestCase { /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ protected $config; - /** @var \OC\NavigationManager */ + protected IEVentDispatcher|MockObject $dispatcher; + + /** @var NavigationManager */ protected $navigationManager; + protected LoggerInterface $logger; protected function setUp(): void { parent::setUp(); @@ -50,22 +54,26 @@ class NavigationManagerTest extends TestCase { $this->userSession = $this->createMock(IUserSession::class); $this->groupManager = $this->createMock(Manager::class); $this->config = $this->createMock(IConfig::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->dispatcher = $this->createMock(IEventDispatcher::class); $this->navigationManager = new NavigationManager( $this->appManager, $this->urlGenerator, $this->l10nFac, $this->userSession, $this->groupManager, - $this->config + $this->config, + $this->logger, + $this->dispatcher, ); $this->navigationManager->clear(false); } - public function addArrayData() { + public static function addArrayData(): array { return [ [ - 'entry id' => [ + 'entry' => [ 'id' => 'entry id', 'name' => 'link text', 'order' => 1, @@ -75,7 +83,7 @@ class NavigationManagerTest extends TestCase { 'classes' => '', 'unread' => 0 ], - 'entry id2' => [ + 'expectedEntry' => [ 'id' => 'entry id', 'name' => 'link text', 'order' => 1, @@ -88,16 +96,16 @@ class NavigationManagerTest extends TestCase { ] ], [ - 'entry id' => [ + 'entry' => [ 'id' => 'entry id', 'name' => 'link text', 'order' => 1, //'icon' => 'optional', 'href' => 'url', 'active' => true, - 'unread' => 0 + 'unread' => 0, ], - 'entry id2' => [ + 'expectedEntry' => [ 'id' => 'entry id', 'name' => 'link text', 'order' => 1, @@ -106,19 +114,20 @@ class NavigationManagerTest extends TestCase { 'active' => false, 'type' => 'link', 'classes' => '', - 'unread' => 0 + 'unread' => 0, + 'default' => true, ] ] ]; } /** - * @dataProvider addArrayData * * @param array $entry * @param array $expectedEntry */ - public function testAddArray(array $entry, array $expectedEntry) { + #[\PHPUnit\Framework\Attributes\DataProvider('addArrayData')] + public function testAddArray(array $entry, array $expectedEntry): void { $this->assertEmpty($this->navigationManager->getAll('all'), 'Expected no navigation entry exists'); $this->navigationManager->add($entry); @@ -131,12 +140,12 @@ class NavigationManagerTest extends TestCase { } /** - * @dataProvider addArrayData * * @param array $entry * @param array $expectedEntry */ - public function testAddClosure(array $entry, array $expectedEntry) { + #[\PHPUnit\Framework\Attributes\DataProvider('addArrayData')] + public function testAddClosure(array $entry, array $expectedEntry): void { global $testAddClosureNumberOfCalls; $testAddClosureNumberOfCalls = 0; @@ -163,7 +172,7 @@ class NavigationManagerTest extends TestCase { $this->assertEmpty($this->navigationManager->getAll('all'), 'Expected no navigation entry exists after clear()'); } - public function testAddArrayClearGetAll() { + public function testAddArrayClearGetAll(): void { $entry = [ 'id' => 'entry id', 'name' => 'link text', @@ -178,7 +187,7 @@ class NavigationManagerTest extends TestCase { $this->assertEmpty($this->navigationManager->getAll(), 'Expected no navigation entry exists after clear()'); } - public function testAddClosureClearGetAll() { + public function testAddClosureClearGetAll(): void { $this->assertEmpty($this->navigationManager->getAll(), 'Expected no navigation entry exists'); $entry = [ @@ -206,32 +215,34 @@ class NavigationManagerTest extends TestCase { $this->assertEquals(0, $testAddClosureNumberOfCalls, 'Expected that the closure is not called by getAll()'); } - /** - * @dataProvider providesNavigationConfig - */ - public function testWithAppManager($expected, $navigation, $isAdmin = false) { + #[\PHPUnit\Framework\Attributes\DataProvider('providesNavigationConfig')] + public function testWithAppManager($expected, $navigation, $isAdmin = false): void { $l = $this->createMock(IL10N::class); $l->expects($this->any())->method('t')->willReturnCallback(function ($text, $parameters = []) { return vsprintf($text, $parameters); }); + /* Return default value */ + $this->config->method('getUserValue') + ->willReturnArgument(3); + $this->appManager->expects($this->any()) - ->method('isEnabledForUser') - ->with('theming') - ->willReturn(true); - $this->appManager->expects($this->once())->method('getAppInfo')->with('test')->willReturn($navigation); - /* + ->method('isEnabledForUser') + ->with('theming') + ->willReturn(true); + $this->appManager->expects($this->once()) + ->method('getAppInfo') + ->with('test') + ->willReturn($navigation); + $this->urlGenerator->expects($this->any()) + ->method('imagePath') + ->willReturnCallback(function ($appName, $file) { + return "/apps/$appName/img/$file"; + }); $this->appManager->expects($this->any()) - ->method('getAppInfo') - ->will($this->returnValueMap([ - ['test', null, null, $navigation], - ['theming', null, null, null], - ])); - */ + ->method('getAppIcon') + ->willReturnCallback(fn (string $appName) => "/apps/$appName/img/app.svg"); $this->l10nFac->expects($this->any())->method('get')->willReturn($l); - $this->urlGenerator->expects($this->any())->method('imagePath')->willReturnCallback(function ($appName, $file) { - return "/apps/$appName/img/$file"; - }); $this->urlGenerator->expects($this->any())->method('linkToRoute')->willReturnCallback(function ($route) { if ($route === 'core.login.logout') { return 'https://example.com/logout'; @@ -243,20 +254,25 @@ class NavigationManagerTest extends TestCase { $this->userSession->expects($this->any())->method('getUser')->willReturn($user); $this->userSession->expects($this->any())->method('isLoggedIn')->willReturn(true); $this->appManager->expects($this->any()) - ->method('getEnabledAppsForUser') - ->with($user) - ->willReturn(['test']); + ->method('getEnabledAppsForUser') + ->with($user) + ->willReturn(['test']); $this->groupManager->expects($this->any())->method('isAdmin')->willReturn($isAdmin); $subadmin = $this->createMock(SubAdmin::class); $subadmin->expects($this->any())->method('isSubAdmin')->with($user)->willReturn(false); $this->groupManager->expects($this->any())->method('getSubAdmin')->willReturn($subadmin); $this->navigationManager->clear(); + $this->dispatcher->expects($this->once()) + ->method('dispatchTyped') + ->willReturnCallback(function ($event): void { + $this->assertInstanceOf(LoadAdditionalEntriesEvent::class, $event); + }); $entries = $this->navigationManager->getAll('all'); $this->assertEquals($expected, $entries); } - public function providesNavigationConfig() { + public static function providesNavigationConfig(): array { $apps = [ 'core_apps' => [ 'id' => 'core_apps', @@ -271,6 +287,17 @@ class NavigationManagerTest extends TestCase { ] ]; $defaults = [ + 'profile' => [ + 'type' => 'settings', + 'id' => 'profile', + 'order' => 1, + 'href' => '/apps/test/', + 'name' => 'View profile', + 'icon' => '', + 'active' => false, + 'classes' => '', + 'unread' => 0, + ], 'accessibility_settings' => [ 'type' => 'settings', 'id' => 'accessibility_settings', @@ -296,7 +323,7 @@ class NavigationManagerTest extends TestCase { 'logout' => [ 'id' => 'logout', 'order' => 99999, - 'href' => 'https://example.com/logout?requesttoken='. urlencode(\OCP\Util::callRegister()), + 'href' => 'https://example.com/logout?requesttoken=' . urlencode(Util::callRegister()), 'icon' => '/apps/core/img/actions/logout.svg', 'name' => 'Log out', 'active' => false, @@ -334,6 +361,7 @@ class NavigationManagerTest extends TestCase { return [ 'minimalistic' => [ array_merge( + ['profile' => $defaults['profile']], ['accessibility_settings' => $defaults['accessibility_settings']], ['settings' => $defaults['settings']], ['test' => [ @@ -345,7 +373,9 @@ class NavigationManagerTest extends TestCase { 'active' => false, 'type' => 'link', 'classes' => '', - 'unread' => 0 + 'unread' => 0, + 'default' => true, + 'app' => 'test', ]], ['logout' => $defaults['logout']] ), @@ -357,6 +387,7 @@ class NavigationManagerTest extends TestCase { ], 'minimalistic-settings' => [ array_merge( + ['profile' => $defaults['profile']], ['accessibility_settings' => $defaults['accessibility_settings']], ['settings' => $defaults['settings']], ['test' => [ @@ -368,7 +399,7 @@ class NavigationManagerTest extends TestCase { 'active' => false, 'type' => 'settings', 'classes' => '', - 'unread' => 0 + 'unread' => 0, ]], ['logout' => $defaults['logout']] ), @@ -378,8 +409,49 @@ class NavigationManagerTest extends TestCase { ], ]] ], + 'with-multiple' => [ + array_merge( + ['profile' => $defaults['profile']], + ['accessibility_settings' => $defaults['accessibility_settings']], + ['settings' => $defaults['settings']], + ['test' => [ + 'id' => 'test', + 'order' => 100, + 'href' => '/apps/test/', + 'icon' => '/apps/test/img/app.svg', + 'name' => 'Test', + 'active' => false, + 'type' => 'link', + 'classes' => '', + 'unread' => 0, + 'default' => false, + 'app' => 'test', + ], + 'test1' => [ + 'id' => 'test1', + 'order' => 50, + 'href' => '/apps/test/', + 'icon' => '/apps/test/img/app.svg', + 'name' => 'Other test', + 'active' => false, + 'type' => 'link', + 'classes' => '', + 'unread' => 0, + 'default' => true, // because of order + 'app' => 'test', + ]], + ['logout' => $defaults['logout']] + ), + ['navigations' => [ + 'navigation' => [ + ['route' => 'test.page.index', 'name' => 'Test'], + ['route' => 'test.page.index', 'name' => 'Other test', 'order' => 50], + ] + ]] + ], 'admin' => [ array_merge( + ['profile' => $defaults['profile']], $adminSettings, $apps, ['test' => [ @@ -391,7 +463,9 @@ class NavigationManagerTest extends TestCase { 'active' => false, 'type' => 'link', 'classes' => '', - 'unread' => 0 + 'unread' => 0, + 'default' => true, + 'app' => 'test', ]], ['logout' => $defaults['logout']] ), @@ -404,6 +478,7 @@ class NavigationManagerTest extends TestCase { ], 'no name' => [ array_merge( + ['profile' => $defaults['profile']], $adminSettings, $apps, ['logout' => $defaults['logout']] @@ -417,12 +492,342 @@ class NavigationManagerTest extends TestCase { ], 'no admin' => [ $defaults, - ['navigations' => [[ - '@attributes' => ['role' => 'admin'], - 'route' => 'test.page.index', - 'name' => 'Test' - ]]] + ['navigations' => [ + 'navigation' => [ + ['@attributes' => ['role' => 'admin'], 'route' => 'test.page.index', 'name' => 'Test'] + ], + ]], ] ]; } + + public function testWithAppManagerAndApporder(): void { + $l = $this->createMock(IL10N::class); + $l->expects($this->any())->method('t')->willReturnCallback(function ($text, $parameters = []) { + return vsprintf($text, $parameters); + }); + + $testOrder = 12; + $expected = [ + 'test' => [ + 'type' => 'link', + 'id' => 'test', + 'order' => $testOrder, + 'href' => '/apps/test/', + 'name' => 'Test', + 'icon' => '/apps/test/img/app.svg', + 'active' => false, + 'classes' => '', + 'unread' => 0, + 'default' => true, + 'app' => 'test', + ], + ]; + $navigation = ['navigations' => [ + 'navigation' => [ + ['route' => 'test.page.index', 'name' => 'Test'] + ], + ]]; + + $this->config->method('getUserValue') + ->willReturnCallback( + function (string $userId, string $appName, string $key, mixed $default = '') use ($testOrder) { + $this->assertEquals('user001', $userId); + if ($key === 'apporder') { + return json_encode(['test' => ['app' => 'test', 'order' => $testOrder]]); + } + return $default; + } + ); + + $this->appManager->expects($this->any()) + ->method('isEnabledForUser') + ->with('theming') + ->willReturn(true); + $this->appManager->expects($this->once())->method('getAppInfo')->with('test')->willReturn($navigation); + $this->appManager->expects($this->once())->method('getAppIcon')->with('test')->willReturn('/apps/test/img/app.svg'); + $this->l10nFac->expects($this->any())->method('get')->willReturn($l); + $this->urlGenerator->expects($this->any())->method('imagePath')->willReturnCallback(function ($appName, $file) { + return "/apps/$appName/img/$file"; + }); + $this->urlGenerator->expects($this->any())->method('linkToRoute')->willReturnCallback(function ($route) { + if ($route === 'core.login.logout') { + return 'https://example.com/logout'; + } + return '/apps/test/'; + }); + $user = $this->createMock(IUser::class); + $user->expects($this->any())->method('getUID')->willReturn('user001'); + $this->userSession->expects($this->any())->method('getUser')->willReturn($user); + $this->userSession->expects($this->any())->method('isLoggedIn')->willReturn(true); + $this->appManager->expects($this->any()) + ->method('getEnabledAppsForUser') + ->with($user) + ->willReturn(['test']); + $this->groupManager->expects($this->any())->method('isAdmin')->willReturn(false); + $subadmin = $this->createMock(SubAdmin::class); + $subadmin->expects($this->any())->method('isSubAdmin')->with($user)->willReturn(false); + $this->groupManager->expects($this->any())->method('getSubAdmin')->willReturn($subadmin); + + $this->navigationManager->clear(); + $this->dispatcher->expects($this->once()) + ->method('dispatchTyped') + ->willReturnCallback(function ($event): void { + $this->assertInstanceOf(LoadAdditionalEntriesEvent::class, $event); + }); + $entries = $this->navigationManager->getAll(); + $this->assertEquals($expected, $entries); + } + + public static function provideDefaultEntries(): array { + return [ + // none specified, default to files + [ + '', + '', + '{}', + true, + 'files', + ], + // none specified, without fallback + [ + '', + '', + '{}', + false, + '', + ], + // unexisting or inaccessible app specified, default to files + [ + 'unexist', + '', + '{}', + true, + 'files', + ], + // unexisting or inaccessible app specified, without fallbacks + [ + 'unexist', + '', + '{}', + false, + '', + ], + // non-standard app + [ + 'settings', + '', + '{}', + true, + 'settings', + ], + // non-standard app, without fallback + [ + 'settings', + '', + '{}', + false, + 'settings', + ], + // non-standard app with fallback + [ + 'unexist,settings', + '', + '{}', + true, + 'settings', + ], + // system default app and user apporder + [ + // system default is settings + 'unexist,settings', + '', + // apporder says default app is files (order is lower) + '{"files_id":{"app":"files","order":1},"settings_id":{"app":"settings","order":2}}', + true, + // system default should override apporder + 'settings' + ], + // user-customized defaultapp + [ + '', + 'files', + '', + true, + 'files', + ], + // user-customized defaultapp with systemwide + [ + 'unexist,settings', + 'files', + '', + true, + 'files', + ], + // user-customized defaultapp with system wide and apporder + [ + 'unexist,settings', + 'files', + '{"settings_id":{"app":"settings","order":1},"files_id":{"app":"files","order":2}}', + true, + 'files', + ], + // user-customized apporder fallback + [ + '', + '', + '{"settings_id":{"app":"settings","order":1},"files":{"app":"files","order":2}}', + true, + 'settings', + ], + // user-customized apporder fallback with missing app key (entries added by closures does not always have an app key set (Nextcloud 27 spreed app for example)) + [ + '', + '', + '{"spreed":{"order":1},"files":{"app":"files","order":2}}', + true, + 'files', + ], + // user-customized apporder, but called without fallback + [ + '', + '', + '{"settings":{"app":"settings","order":1},"files":{"app":"files","order":2}}', + false, + '', + ], + // user-customized apporder with an app that has multiple routes + [ + '', + '', + '{"settings_id":{"app":"settings","order":1},"settings_id_2":{"app":"settings","order":3},"id_files":{"app":"files","order":2}}', + true, + 'settings', + ], + // closure navigation entries are also resolved + [ + 'closure2', + '', + '', + true, + 'closure2', + ], + [ + '', + 'closure2', + '', + true, + 'closure2', + ], + [ + '', + '', + '{"closure2":{"order":1,"app":"closure2","href":"/closure2"}}', + true, + 'closure2', + ], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('provideDefaultEntries')] + public function testGetDefaultEntryIdForUser(string $defaultApps, string $userDefaultApps, string $userApporder, bool $withFallbacks, string $expectedApp): void { + $this->navigationManager->add([ + 'id' => 'files', + ]); + $this->navigationManager->add([ + 'id' => 'settings', + ]); + $this->navigationManager->add(static function (): array { + return [ + 'id' => 'closure1', + 'href' => '/closure1', + ]; + }); + $this->navigationManager->add(static function (): array { + return [ + 'id' => 'closure2', + 'href' => '/closure2', + ]; + }); + + $this->appManager->method('getEnabledApps')->willReturn([]); + + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('user1'); + + $this->userSession->expects($this->atLeastOnce()) + ->method('getUser') + ->willReturn($user); + + $this->config->expects($this->atLeastOnce()) + ->method('getSystemValueString') + ->with('defaultapp', $this->anything()) + ->willReturn($defaultApps); + + $this->config->expects($this->atLeastOnce()) + ->method('getUserValue') + ->willReturnMap([ + ['user1', 'core', 'defaultapp', '', $userDefaultApps], + ['user1', 'core', 'apporder', '[]', $userApporder], + ]); + + $this->assertEquals($expectedApp, $this->navigationManager->getDefaultEntryIdForUser(null, $withFallbacks)); + } + + public function testDefaultEntryUpdated(): void { + $this->appManager->method('getEnabledApps')->willReturn([]); + + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('user1'); + + $this->userSession + ->method('getUser') + ->willReturn($user); + + $this->config + ->method('getSystemValueString') + ->with('defaultapp', $this->anything()) + ->willReturn('app4,app3,app2,app1'); + + $this->config + ->method('getUserValue') + ->willReturnMap([ + ['user1', 'core', 'defaultapp', '', ''], + ['user1', 'core', 'apporder', '[]', ''], + ]); + + $this->navigationManager->add([ + 'id' => 'app1', + ]); + + $this->assertEquals('app1', $this->navigationManager->getDefaultEntryIdForUser(null, false)); + $this->assertEquals(true, $this->navigationManager->get('app1')['default']); + + $this->navigationManager->add([ + 'id' => 'app3', + ]); + + $this->assertEquals('app3', $this->navigationManager->getDefaultEntryIdForUser(null, false)); + $this->assertEquals(false, $this->navigationManager->get('app1')['default']); + $this->assertEquals(true, $this->navigationManager->get('app3')['default']); + + $this->navigationManager->add([ + 'id' => 'app2', + ]); + + $this->assertEquals('app3', $this->navigationManager->getDefaultEntryIdForUser(null, false)); + $this->assertEquals(false, $this->navigationManager->get('app1')['default']); + $this->assertEquals(false, $this->navigationManager->get('app2')['default']); + $this->assertEquals(true, $this->navigationManager->get('app3')['default']); + + $this->navigationManager->add([ + 'id' => 'app4', + ]); + + $this->assertEquals('app4', $this->navigationManager->getDefaultEntryIdForUser(null, false)); + $this->assertEquals(false, $this->navigationManager->get('app1')['default']); + $this->assertEquals(false, $this->navigationManager->get('app2')['default']); + $this->assertEquals(false, $this->navigationManager->get('app3')['default']); + $this->assertEquals(true, $this->navigationManager->get('app4')['default']); + } } |