diff options
Diffstat (limited to 'apps/theming/tests')
21 files changed, 2691 insertions, 1115 deletions
diff --git a/apps/theming/tests/CapabilitiesTest.php b/apps/theming/tests/CapabilitiesTest.php index 1a11421ce1d..aa08a45a28b 100644 --- a/apps/theming/tests/CapabilitiesTest.php +++ b/apps/theming/tests/CapabilitiesTest.php @@ -1,41 +1,23 @@ <?php + +declare(strict_types=1); /** - * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Guillaume COMPAGNON <gcompagnon@outlook.com> - * @author Jan-Christoph Borchardt <hey@jancborchardt.net> - * @author Joas Schilling <coding@schilljs.com> - * @author Julius Härtl <jus@bitgrid.net> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ - namespace OCA\Theming\Tests; use OCA\Theming\Capabilities; +use OCA\Theming\ImageManager; use OCA\Theming\ThemingDefaults; use OCA\Theming\Util; use OCP\App\IAppManager; use OCP\Files\IAppData; use OCP\IConfig; use OCP\IURLGenerator; +use OCP\IUserSession; +use OCP\ServerVersion; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; /** @@ -44,51 +26,53 @@ use Test\TestCase; * @package OCA\Theming\Tests */ class CapabilitiesTest extends TestCase { - /** @var ThemingDefaults|\PHPUnit\Framework\MockObject\MockObject */ - protected $theming; - - /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ - protected $url; - - /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ - protected $config; - - /** @var Util|\PHPUnit\Framework\MockObject\MockObject */ - protected $util; - - /** @var Capabilities */ - protected $capabilities; + protected ThemingDefaults&MockObject $theming; + protected IURLGenerator&MockObject $url; + protected IConfig&MockObject $config; + protected Util&MockObject $util; + protected IUserSession $userSession; + protected Capabilities $capabilities; protected function setUp(): void { parent::setUp(); $this->theming = $this->createMock(ThemingDefaults::class); - $this->url = $this->getMockBuilder(IURLGenerator::class)->getMock(); + $this->url = $this->createMock(IURLGenerator::class); $this->config = $this->createMock(IConfig::class); $this->util = $this->createMock(Util::class); - $this->capabilities = new Capabilities($this->theming, $this->util, $this->url, $this->config); + $this->userSession = $this->createMock(IUserSession::class); + $this->capabilities = new Capabilities( + $this->theming, + $this->util, + $this->url, + $this->config, + $this->userSession, + ); } - public function dataGetCapabilities() { + public static function dataGetCapabilities(): array { return [ - ['name', 'url', 'slogan', '#FFFFFF', '#000000', 'logo', 'background', 'http://absolute/', true, [ + ['name', 'url', 'slogan', '#FFFFFF', '#000000', 'logo', 'background', '#fff', '#000', 'http://absolute/', true, [ 'name' => 'name', + 'productName' => 'name', 'url' => 'url', 'slogan' => 'slogan', 'color' => '#FFFFFF', 'color-text' => '#000000', - 'color-element' => '#aaaaaa', - 'color-element-bright' => '#aaaaaa', + 'color-element' => '#b3b3b3', + 'color-element-bright' => '#b3b3b3', 'color-element-dark' => '#FFFFFF', 'logo' => 'http://absolute/logo', 'background' => 'http://absolute/background', + 'background-text' => '#000', 'background-plain' => false, 'background-default' => false, 'logoheader' => 'http://absolute/logo', 'favicon' => 'http://absolute/logo', ]], - ['name1', 'url2', 'slogan3', '#01e4a0', '#ffffff', 'logo5', 'background6', 'http://localhost/', false, [ + ['name1', 'url2', 'slogan3', '#01e4a0', '#ffffff', 'logo5', 'background6', '#fff', '#000', 'http://localhost/', false, [ 'name' => 'name1', + 'productName' => 'name1', 'url' => 'url2', 'slogan' => 'slogan3', 'color' => '#01e4a0', @@ -98,38 +82,43 @@ class CapabilitiesTest extends TestCase { 'color-element-dark' => '#01e4a0', 'logo' => 'http://localhost/logo5', 'background' => 'http://localhost/background6', + 'background-text' => '#000', 'background-plain' => false, 'background-default' => true, 'logoheader' => 'http://localhost/logo5', 'favicon' => 'http://localhost/logo5', ]], - ['name1', 'url2', 'slogan3', '#000000', '#ffffff', 'logo5', 'backgroundColor', 'http://localhost/', true, [ + ['name1', 'url2', 'slogan3', '#000000', '#ffffff', 'logo5', 'backgroundColor', '#000000', '#ffffff', 'http://localhost/', true, [ 'name' => 'name1', + 'productName' => 'name1', 'url' => 'url2', 'slogan' => 'slogan3', 'color' => '#000000', 'color-text' => '#ffffff', - 'color-element' => '#000000', - 'color-element-bright' => '#000000', - 'color-element-dark' => '#555555', + 'color-element' => '#4d4d4d', + 'color-element-bright' => '#4d4d4d', + 'color-element-dark' => '#4d4d4d', 'logo' => 'http://localhost/logo5', 'background' => '#000000', + 'background-text' => '#ffffff', 'background-plain' => true, 'background-default' => false, 'logoheader' => 'http://localhost/logo5', 'favicon' => 'http://localhost/logo5', ]], - ['name1', 'url2', 'slogan3', '#000000', '#ffffff', 'logo5', 'backgroundColor', 'http://localhost/', false, [ + ['name1', 'url2', 'slogan3', '#000000', '#ffffff', 'logo5', 'backgroundColor', '#000000', '#ffffff', 'http://localhost/', false, [ 'name' => 'name1', + 'productName' => 'name1', 'url' => 'url2', 'slogan' => 'slogan3', 'color' => '#000000', 'color-text' => '#ffffff', - 'color-element' => '#000000', - 'color-element-bright' => '#000000', - 'color-element-dark' => '#555555', + 'color-element' => '#4d4d4d', + 'color-element-bright' => '#4d4d4d', + 'color-element-dark' => '#4d4d4d', 'logo' => 'http://localhost/logo5', 'background' => '#000000', + 'background-text' => '#ffffff', 'background-plain' => true, 'background-default' => true, 'logoheader' => 'http://localhost/logo5', @@ -139,19 +128,10 @@ class CapabilitiesTest extends TestCase { } /** - * @dataProvider dataGetCapabilities - * @param string $name - * @param string $url - * @param string $slogan - * @param string $color - * @param string $textColor - * @param string $logo - * @param string $background - * @param string $baseUrl - * @param bool $backgroundThemed - * @param string[] $expected + * @param non-empty-array<string, string> $expected */ - public function testGetCapabilities($name, $url, $slogan, $color, $textColor, $logo, $background, $baseUrl, $backgroundThemed, array $expected) { + #[\PHPUnit\Framework\Attributes\DataProvider('dataGetCapabilities')] + public function testGetCapabilities(string $name, string $url, string $slogan, string $color, string $textColor, string $logo, string $background, string $backgroundColor, string $backgroundTextColor, string $baseUrl, bool $backgroundThemed, array $expected): void { $this->config->expects($this->once()) ->method('getAppValue') ->willReturn($background); @@ -159,29 +139,38 @@ class CapabilitiesTest extends TestCase { ->method('getName') ->willReturn($name); $this->theming->expects($this->once()) + ->method('getProductName') + ->willReturn($name); + $this->theming->expects($this->once()) ->method('getBaseUrl') ->willReturn($url); $this->theming->expects($this->once()) ->method('getSlogan') ->willReturn($slogan); + $this->theming->expects($this->once()) + ->method('getColorBackground') + ->willReturn($backgroundColor); + $this->theming->expects($this->once()) + ->method('getTextColorBackground') + ->willReturn($backgroundTextColor); $this->theming->expects($this->atLeast(1)) - ->method('getColorPrimary') + ->method('getDefaultColorPrimary') ->willReturn($color); $this->theming->expects($this->exactly(3)) ->method('getLogo') ->willReturn($logo); - $this->theming->expects($this->once()) - ->method('getTextColorPrimary') - ->willReturn($textColor); - $util = new Util($this->config, $this->createMock(IAppManager::class), $this->createMock(IAppData::class)); + $util = new Util($this->createMock(ServerVersion::class), $this->config, $this->createMock(IAppManager::class), $this->createMock(IAppData::class), $this->createMock(ImageManager::class)); $this->util->expects($this->exactly(3)) ->method('elementColor') ->with($color) - ->willReturnCallback(static function (string $color, bool $brightBackground = true) use ($util) { + ->willReturnCallback(static function (string $color, ?bool $brightBackground = null) use ($util) { return $util->elementColor($color, $brightBackground); }); + $this->util->expects($this->any()) + ->method('invertTextColor') + ->willReturnCallback(fn () => $textColor === '#000000'); $this->util->expects($this->once()) ->method('isBackgroundThemed') ->willReturn($backgroundThemed); diff --git a/apps/theming/tests/Controller/IconControllerTest.php b/apps/theming/tests/Controller/IconControllerTest.php index a40809855d1..c5034600e03 100644 --- a/apps/theming/tests/Controller/IconControllerTest.php +++ b/apps/theming/tests/Controller/IconControllerTest.php @@ -1,31 +1,10 @@ <?php + +declare(strict_types=1); /** - * @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net> - * - * @author Joas Schilling <coding@schilljs.com> - * @author Julius Haertl <jus@bitgrid.net> - * @author Julius Härtl <jus@bitgrid.net> - * @author Michael Weimann <mail@michael-weimann.eu> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ - namespace OCA\Theming\Tests\Controller; use OC\Files\SimpleFS\SimpleFile; @@ -34,32 +13,25 @@ use OCA\Theming\Controller\IconController; use OCA\Theming\IconBuilder; use OCA\Theming\ImageManager; use OCA\Theming\ThemingDefaults; +use OCP\App\IAppManager; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataDisplayResponse; use OCP\AppFramework\Http\FileDisplayResponse; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Files\NotFoundException; -use OCP\IConfig; use OCP\IRequest; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class IconControllerTest extends TestCase { - /** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */ - private $request; - /** @var ThemingDefaults|\PHPUnit\Framework\MockObject\MockObject */ - private $themingDefaults; - /** @var \OCP\AppFramework\Utility\ITimeFactory */ - private $timeFactory; - /** @var IconController|\PHPUnit\Framework\MockObject\MockObject */ - private $iconController; - /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ - private $config; - /** @var IconBuilder|\PHPUnit\Framework\MockObject\MockObject */ - private $iconBuilder; - /** @var FileAccessHelper|\PHPUnit\Framework\MockObject\MockObject */ - private $fileAccessHelper; - /** @var ImageManager */ - private $imageManager; + private IRequest&MockObject $request; + private ThemingDefaults&MockObject $themingDefaults; + private ITimeFactory&MockObject $timeFactory; + private IconBuilder&MockObject $iconBuilder; + private FileAccessHelper&MockObject $fileAccessHelper; + private IAppManager&MockObject $appManager; + private ImageManager&MockObject $imageManager; + private IconController $iconController; protected function setUp(): void { $this->request = $this->createMock(IRequest::class); @@ -67,6 +39,7 @@ class IconControllerTest extends TestCase { $this->iconBuilder = $this->createMock(IconBuilder::class); $this->imageManager = $this->createMock(ImageManager::class); $this->fileAccessHelper = $this->createMock(FileAccessHelper::class); + $this->appManager = $this->createMock(IAppManager::class); $this->timeFactory = $this->createMock(ITimeFactory::class); $this->timeFactory->expects($this->any()) @@ -81,7 +54,8 @@ class IconControllerTest extends TestCase { $this->themingDefaults, $this->iconBuilder, $this->imageManager, - $this->fileAccessHelper + $this->fileAccessHelper, + $this->appManager, ); parent::setUp(); @@ -92,22 +66,24 @@ class IconControllerTest extends TestCase { $icon->expects($this->any())->method('getContent')->willReturn($data); $icon->expects($this->any())->method('getMimeType')->willReturn('image type'); $icon->expects($this->any())->method('getEtag')->willReturn('my etag'); + $icon->expects($this->any())->method('getName')->willReturn('my name'); + $icon->expects($this->any())->method('getMTime')->willReturn(42); $icon->method('getName')->willReturn($filename); return new SimpleFile($icon); } - public function testGetThemedIcon() { + public function testGetThemedIcon(): void { $file = $this->iconFileMock('icon-core-filetypes_folder.svg', 'filecontent'); $this->imageManager->expects($this->once()) ->method('getCachedImage') ->with('icon-core-filetypes_folder.svg') ->willReturn($file); $expected = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => 'image/svg+xml']); - $expected->cacheFor(86400); + $expected->cacheFor(86400, false, true); $this->assertEquals($expected, $this->iconController->getThemedIcon('core', 'filetypes/folder.svg')); } - public function testGetFaviconDefault() { + public function testGetFaviconDefault(): void { if (!extension_loaded('imagick')) { $this->markTestSkipped('Imagemagick is required for dynamic icon generation.'); } @@ -119,13 +95,13 @@ class IconControllerTest extends TestCase { $this->imageManager->expects($this->once()) ->method('getImage', false) ->with('favicon') - ->will($this->throwException(new NotFoundException())); + ->willThrowException(new NotFoundException()); $this->imageManager->expects($this->any()) ->method('shouldReplaceIcons') ->willReturn(true); $this->imageManager->expects($this->once()) ->method('getCachedImage') - ->will($this->throwException(new NotFoundException())); + ->willThrowException(new NotFoundException()); $this->iconBuilder->expects($this->once()) ->method('getFavicon') ->with('core') @@ -139,11 +115,11 @@ class IconControllerTest extends TestCase { $this->assertEquals($expected, $this->iconController->getFavicon()); } - public function testGetFaviconFail() { + public function testGetFaviconFail(): void { $this->imageManager->expects($this->once()) ->method('getImage') ->with('favicon', false) - ->will($this->throwException(new NotFoundException())); + ->willThrowException(new NotFoundException()); $this->imageManager->expects($this->any()) ->method('shouldReplaceIcons') ->willReturn(false); @@ -157,7 +133,7 @@ class IconControllerTest extends TestCase { $this->assertEquals($expected, $this->iconController->getFavicon()); } - public function testGetTouchIconDefault() { + public function testGetTouchIconDefault(): void { if (!extension_loaded('imagick')) { $this->markTestSkipped('Imagemagick is required for dynamic icon generation.'); } @@ -168,7 +144,7 @@ class IconControllerTest extends TestCase { $this->imageManager->expects($this->once()) ->method('getImage') - ->will($this->throwException(new NotFoundException())); + ->willThrowException(new NotFoundException()); $this->imageManager->expects($this->any()) ->method('shouldReplaceIcons') ->willReturn(true); @@ -179,7 +155,7 @@ class IconControllerTest extends TestCase { $file = $this->iconFileMock('filename', 'filecontent'); $this->imageManager->expects($this->once()) ->method('getCachedImage') - ->will($this->throwException(new NotFoundException())); + ->willThrowException(new NotFoundException()); $this->imageManager->expects($this->once()) ->method('setCachedImage') ->willReturn($file); @@ -189,11 +165,11 @@ class IconControllerTest extends TestCase { $this->assertEquals($expected, $this->iconController->getTouchIcon()); } - public function testGetTouchIconFail() { + public function testGetTouchIconFail(): void { $this->imageManager->expects($this->once()) ->method('getImage') ->with('favicon') - ->will($this->throwException(new NotFoundException())); + ->willThrowException(new NotFoundException()); $this->imageManager->expects($this->any()) ->method('shouldReplaceIcons') ->willReturn(false); diff --git a/apps/theming/tests/Controller/ThemingControllerTest.php b/apps/theming/tests/Controller/ThemingControllerTest.php index c5ecc08b211..fb461f03a28 100644 --- a/apps/theming/tests/Controller/ThemingControllerTest.php +++ b/apps/theming/tests/Controller/ThemingControllerTest.php @@ -1,94 +1,64 @@ <?php + +declare(strict_types=1); /** - * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch> - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Daniel Calviño Sánchez <danxuliu@gmail.com> - * @author Joas Schilling <coding@schilljs.com> - * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> - * @author Julius Haertl <jus@bitgrid.net> - * @author Julius Härtl <jus@bitgrid.net> - * @author Kyle Fazzari <kyrofa@ubuntu.com> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Michael Weimann <mail@michael-weimann.eu> - * @author rakekniven <mark.ziegler@rakekniven.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ - namespace OCA\Theming\Tests\Controller; use OC\L10N\L10N; -use OC\Template\SCSSCacher; use OCA\Theming\Controller\ThemingController; use OCA\Theming\ImageManager; +use OCA\Theming\Service\ThemesService; use OCA\Theming\ThemingDefaults; use OCP\App\IAppManager; use OCP\AppFramework\Http; +use OCP\AppFramework\Http\ContentSecurityPolicy; use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\FileDisplayResponse; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Http\NotFoundResponse; +use OCP\AppFramework\Services\IAppConfig; use OCP\AppFramework\Utility\ITimeFactory; -use OCP\Files\IAppData; use OCP\Files\NotFoundException; use OCP\Files\SimpleFS\ISimpleFile; use OCP\IConfig; use OCP\IL10N; +use OCP\INavigationManager; use OCP\IRequest; use OCP\ITempManager; use OCP\IURLGenerator; +use OCP\Server; use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class ThemingControllerTest extends TestCase { - /** @var IRequest|MockObject */ - private $request; - /** @var IConfig|MockObject */ - private $config; - /** @var ThemingDefaults|MockObject */ - private $themingDefaults; - /** @var IL10N|MockObject */ - private $l10n; - /** @var ThemingController */ - private $themingController; - /** @var ITempManager */ - private $tempManager; - /** @var IAppManager|MockObject */ - private $appManager; - /** @var IAppData|MockObject */ - private $appData; - /** @var ImageManager|MockObject */ - private $imageManager; - /** @var SCSSCacher */ - private $scssCacher; - /** @var IURLGenerator */ - private $urlGenerator; + + private IRequest&MockObject $request; + private IConfig&MockObject $config; + private IAppConfig&MockObject $appConfig; + private ThemingDefaults&MockObject $themingDefaults; + private IL10N&MockObject $l10n; + private IAppManager&MockObject $appManager; + private ImageManager&MockObject $imageManager; + private IURLGenerator&MockObject $urlGenerator; + private ThemesService&MockObject $themesService; + private INavigationManager&MockObject $navigationManager; + + private ThemingController $themingController; protected function setUp(): void { $this->request = $this->createMock(IRequest::class); $this->config = $this->createMock(IConfig::class); + $this->appConfig = $this->createMock(IAppConfig::class); $this->themingDefaults = $this->createMock(ThemingDefaults::class); $this->l10n = $this->createMock(L10N::class); - $this->appData = $this->createMock(IAppData::class); $this->appManager = $this->createMock(IAppManager::class); - $this->tempManager = \OC::$server->getTempManager(); - $this->scssCacher = $this->createMock(SCSSCacher::class); $this->urlGenerator = $this->createMock(IURLGenerator::class); $this->imageManager = $this->createMock(ImageManager::class); + $this->themesService = $this->createMock(ThemesService::class); + $this->navigationManager = $this->createMock(INavigationManager::class); $timeFactory = $this->createMock(ITimeFactory::class); $timeFactory->expects($this->any()) @@ -101,20 +71,20 @@ class ThemingControllerTest extends TestCase { 'theming', $this->request, $this->config, + $this->appConfig, $this->themingDefaults, $this->l10n, - $this->tempManager, - $this->appData, - $this->scssCacher, $this->urlGenerator, $this->appManager, - $this->imageManager + $this->imageManager, + $this->themesService, + $this->navigationManager, ); parent::setUp(); } - public function dataUpdateStylesheetSuccess() { + public static function dataUpdateStylesheetSuccess(): array { return [ ['name', str_repeat('a', 250), 'Saved'], ['url', 'https://nextcloud.com/' . str_repeat('a', 478), 'Saved'], @@ -127,14 +97,8 @@ class ThemingControllerTest extends TestCase { ]; } - /** - * @dataProvider dataUpdateStylesheetSuccess - * - * @param string $setting - * @param string $value - * @param string $message - */ - public function testUpdateStylesheetSuccess($setting, $value, $message) { + #[\PHPUnit\Framework\Attributes\DataProvider('dataUpdateStylesheetSuccess')] + public function testUpdateStylesheetSuccess(string $setting, string $value, string $message): void { $this->themingDefaults ->expects($this->once()) ->method('set') @@ -145,23 +109,12 @@ class ThemingControllerTest extends TestCase { ->willReturnCallback(function ($str) { return $str; }); - $this->scssCacher - ->expects($this->once()) - ->method('getCachedSCSS') - ->with('core', '/core/css/css-variables.scss') - ->willReturn('/core/css/someHash-css-variables.scss'); - $this->urlGenerator - ->expects($this->once()) - ->method('linkTo') - ->with('', '/core/css/someHash-css-variables.scss') - ->willReturn('/nextcloudWebroot/core/css/someHash-css-variables.scss'); $expected = new DataResponse( [ - 'data' => - [ + 'data' + => [ 'message' => $message, - 'serverCssUrl' => '/nextcloudWebroot/core/css/someHash-css-variables.scss', ], 'status' => 'success', ] @@ -169,31 +122,39 @@ class ThemingControllerTest extends TestCase { $this->assertEquals($expected, $this->themingController->updateStylesheet($setting, $value)); } - public function dataUpdateStylesheetError() { + public static function dataUpdateStylesheetError(): array { + $urls = [ + 'url' => 'web address', + 'imprintUrl' => 'legal notice address', + 'privacyUrl' => 'privacy policy address', + ]; + + $urlTests = []; + foreach ($urls as $urlKey => $urlName) { + // Check length limit + $urlTests[] = [$urlKey, 'http://example.com/' . str_repeat('a', 501), "The given {$urlName} is too long"]; + // Check potential evil javascript + $urlTests[] = [$urlKey, 'javascript:alert(1)', "The given {$urlName} is not a valid URL"]; + // Check XSS + $urlTests[] = [$urlKey, 'https://example.com/"><script/src="alert(\'1\')"><a/href/="', "The given {$urlName} is not a valid URL"]; + } + return [ ['name', str_repeat('a', 251), 'The given name is too long'], - ['url', 'http://example.com/' . str_repeat('a', 501), 'The given web address is too long'], - ['url', str_repeat('a', 501), 'The given web address is not a valid URL'], - ['url', 'javascript:alert(1)', 'The given web address is not a valid URL'], ['slogan', str_repeat('a', 501), 'The given slogan is too long'], - ['color', '0082C9', 'The given color is invalid'], - ['color', '#0082Z9', 'The given color is invalid'], - ['color', 'Nextcloud', 'The given color is invalid'], - ['imprintUrl', '0082C9', 'The given legal notice address is not a valid URL'], - ['imprintUrl', '0082C9', 'The given legal notice address is not a valid URL'], - ['imprintUrl', 'javascript:foo', 'The given legal notice address is not a valid URL'], - ['privacyUrl', '#0082Z9', 'The given privacy policy address is not a valid URL'], + ['primary_color', '0082C9', 'The given color is invalid'], + ['primary_color', '#0082Z9', 'The given color is invalid'], + ['primary_color', 'Nextcloud', 'The given color is invalid'], + ['background_color', '0082C9', 'The given color is invalid'], + ['background_color', '#0082Z9', 'The given color is invalid'], + ['background_color', 'Nextcloud', 'The given color is invalid'], + + ...$urlTests, ]; } - /** - * @dataProvider dataUpdateStylesheetError - * - * @param string $setting - * @param string $value - * @param string $message - */ - public function testUpdateStylesheetError($setting, $value, $message) { + #[\PHPUnit\Framework\Attributes\DataProvider('dataUpdateStylesheetError')] + public function testUpdateStylesheetError(string $setting, string $value, string $message): void { $this->themingDefaults ->expects($this->never()) ->method('set') @@ -207,8 +168,8 @@ class ThemingControllerTest extends TestCase { $expected = new DataResponse( [ - 'data' => - [ + 'data' + => [ 'message' => $message, ], 'status' => 'error', @@ -218,14 +179,14 @@ class ThemingControllerTest extends TestCase { $this->assertEquals($expected, $this->themingController->updateStylesheet($setting, $value)); } - public function testUpdateLogoNoData() { + public function testUpdateLogoNoData(): void { $this->request - ->expects($this->at(0)) + ->expects($this->once()) ->method('getParam') ->with('key') ->willReturn('logo'); $this->request - ->expects($this->at(1)) + ->expects($this->once()) ->method('getUploadedFile') ->with('image') ->willReturn(null); @@ -238,8 +199,8 @@ class ThemingControllerTest extends TestCase { $expected = new DataResponse( [ - 'data' => - [ + 'data' + => [ 'message' => 'No file uploaded', ], 'status' => 'failure', @@ -250,29 +211,56 @@ class ThemingControllerTest extends TestCase { $this->assertEquals($expected, $this->themingController->uploadImage()); } + public function testUploadInvalidUploadKey(): void { + $this->request + ->expects($this->once()) + ->method('getParam') + ->with('key') + ->willReturn('invalid'); + $this->request + ->expects($this->never()) + ->method('getUploadedFile'); + $this->l10n + ->expects($this->any()) + ->method('t') + ->willReturnCallback(function ($str) { + return $str; + }); + + $expected = new DataResponse( + [ + 'data' + => [ + 'message' => 'Invalid key', + ], + 'status' => 'failure', + ], + Http::STATUS_BAD_REQUEST + ); + + $this->assertEquals($expected, $this->themingController->uploadImage()); + } + /** * Checks that trying to upload an SVG favicon without imagemagick * results in an unsupported media type response. - * - * @test - * @return void */ - public function testUploadSVGFaviconWithoutImagemagick() { + public function testUploadSVGFaviconWithoutImagemagick(): void { $this->imageManager ->method('shouldReplaceIcons') ->willReturn(false); $this->request - ->expects($this->at(0)) + ->expects($this->once()) ->method('getParam') ->with('key') ->willReturn('favicon'); $this->request - ->expects($this->at(1)) + ->expects($this->once()) ->method('getUploadedFile') ->with('image') ->willReturn([ - 'tmp_name' => __DIR__ . '/../../../../tests/data/testimagelarge.svg', + 'tmp_name' => __DIR__ . '/../../../../tests/data/testimagelarge.svg', 'type' => 'image/svg', 'name' => 'testimagelarge.svg', 'error' => 0, @@ -290,8 +278,8 @@ class ThemingControllerTest extends TestCase { $expected = new DataResponse( [ - 'data' => - [ + 'data' + => [ 'message' => 'Unsupported image type', ], 'status' => 'failure' @@ -302,18 +290,18 @@ class ThemingControllerTest extends TestCase { $this->assertEquals($expected, $this->themingController->uploadImage()); } - public function testUpdateLogoInvalidMimeType() { + public function testUpdateLogoInvalidMimeType(): void { $this->request - ->expects($this->at(0)) + ->expects($this->once()) ->method('getParam') ->with('key') ->willReturn('logo'); $this->request - ->expects($this->at(1)) + ->expects($this->once()) ->method('getUploadedFile') ->with('image') ->willReturn([ - 'tmp_name' => __DIR__ . '/../../../../tests/data/lorem.txt', + 'tmp_name' => __DIR__ . '/../../../../tests/data/lorem.txt', 'type' => 'application/pdf', 'name' => 'logo.pdf', 'error' => 0, @@ -331,8 +319,8 @@ class ThemingControllerTest extends TestCase { $expected = new DataResponse( [ - 'data' => - [ + 'data' + => [ 'message' => 'Unsupported image type', ], 'status' => 'failure' @@ -343,7 +331,7 @@ class ThemingControllerTest extends TestCase { $this->assertEquals($expected, $this->themingController->uploadImage()); } - public function dataUpdateImages() { + public static function dataUpdateImages(): array { return [ ['image/jpeg', false], ['image/jpeg', true], @@ -354,20 +342,20 @@ class ThemingControllerTest extends TestCase { ]; } - /** @dataProvider dataUpdateImages */ - public function testUpdateLogoNormalLogoUpload($mimeType, $folderExists = true) { - $tmpLogo = \OC::$server->getTempManager()->getTemporaryFolder() . '/logo.svg'; - $destination = \OC::$server->getTempManager()->getTemporaryFolder(); + #[\PHPUnit\Framework\Attributes\DataProvider('dataUpdateImages')] + public function testUpdateLogoNormalLogoUpload(string $mimeType, bool $folderExists = true): void { + $tmpLogo = Server::get(ITempManager::class)->getTemporaryFolder() . '/logo.svg'; + $destination = Server::get(ITempManager::class)->getTemporaryFolder(); touch($tmpLogo); copy(__DIR__ . '/../../../../tests/data/testimage.png', $tmpLogo); $this->request - ->expects($this->at(0)) + ->expects($this->once()) ->method('getParam') ->with('key') ->willReturn('logo'); $this->request - ->expects($this->at(1)) + ->expects($this->once()) ->method('getUploadedFile') ->with('image') ->willReturn([ @@ -383,9 +371,6 @@ class ThemingControllerTest extends TestCase { return $str; }); - $this->urlGenerator->expects($this->once()) - ->method('linkTo') - ->willReturn('serverCss'); $this->imageManager->expects($this->once()) ->method('getImageUrl') ->with('logo') @@ -396,12 +381,11 @@ class ThemingControllerTest extends TestCase { $expected = new DataResponse( [ - 'data' => - [ + 'data' + => [ 'name' => 'logo.svg', 'message' => 'Saved', 'url' => 'imageUrl', - 'serverCssUrl' => 'serverCss' ], 'status' => 'success' ] @@ -410,19 +394,18 @@ class ThemingControllerTest extends TestCase { $this->assertEquals($expected, $this->themingController->uploadImage()); } - /** @dataProvider dataUpdateImages */ - public function testUpdateLogoLoginScreenUpload($folderExists) { - $tmpLogo = \OC::$server->getTempManager()->getTemporaryFolder() . 'logo.png'; + public function testUpdateLogoLoginScreenUpload(): void { + $tmpLogo = Server::get(ITempManager::class)->getTemporaryFolder() . 'logo.png'; touch($tmpLogo); copy(__DIR__ . '/../../../../tests/data/desktopapp.png', $tmpLogo); $this->request - ->expects($this->at(0)) + ->expects($this->once()) ->method('getParam') ->with('key') ->willReturn('background'); $this->request - ->expects($this->at(1)) + ->expects($this->once()) ->method('getUploadedFile') ->with('image') ->willReturn([ @@ -441,21 +424,17 @@ class ThemingControllerTest extends TestCase { $this->imageManager->expects($this->once()) ->method('updateImage'); - $this->urlGenerator->expects($this->once()) - ->method('linkTo') - ->willReturn('serverCss'); $this->imageManager->expects($this->once()) ->method('getImageUrl') ->with('background') ->willReturn('imageUrl'); $expected = new DataResponse( [ - 'data' => - [ + 'data' + => [ 'name' => 'logo.svg', 'message' => 'Saved', 'url' => 'imageUrl', - 'serverCssUrl' => 'serverCss' ], 'status' => 'success' ] @@ -463,18 +442,18 @@ class ThemingControllerTest extends TestCase { $this->assertEquals($expected, $this->themingController->uploadImage()); } - public function testUpdateLogoLoginScreenUploadWithInvalidImage() { - $tmpLogo = \OC::$server->getTempManager()->getTemporaryFolder() . '/logo.svg'; + public function testUpdateLogoLoginScreenUploadWithInvalidImage(): void { + $tmpLogo = Server::get(ITempManager::class)->getTemporaryFolder() . '/logo.svg'; touch($tmpLogo); - file_put_contents($tmpLogo, file_get_contents(__DIR__ . '/../../../../tests/data/data.zip')); + file_put_contents($tmpLogo, file_get_contents(__DIR__ . '/../../../../tests/data/data.zip')); $this->request - ->expects($this->at(0)) + ->expects($this->once()) ->method('getParam') ->with('key') ->willReturn('logo'); $this->request - ->expects($this->at(1)) + ->expects($this->once()) ->method('getUploadedFile') ->with('image') ->willReturn([ @@ -496,8 +475,8 @@ class ThemingControllerTest extends TestCase { $expected = new DataResponse( [ - 'data' => - [ + 'data' + => [ 'message' => 'Unsupported image type', ], 'status' => 'failure' @@ -507,7 +486,7 @@ class ThemingControllerTest extends TestCase { $this->assertEquals($expected, $this->themingController->uploadImage()); } - public function dataPhpUploadErrors() { + public static function dataPhpUploadErrors(): array { return [ [UPLOAD_ERR_INI_SIZE, 'The uploaded file exceeds the upload_max_filesize directive in php.ini'], [UPLOAD_ERR_FORM_SIZE, 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'], @@ -519,17 +498,15 @@ class ThemingControllerTest extends TestCase { ]; } - /** - * @dataProvider dataPhpUploadErrors - */ - public function testUpdateLogoLoginScreenUploadWithInvalidImageUpload($error, $expectedErrorMessage) { + #[\PHPUnit\Framework\Attributes\DataProvider('dataPhpUploadErrors')] + public function testUpdateLogoLoginScreenUploadWithInvalidImageUpload(int $error, string $expectedErrorMessage): void { $this->request - ->expects($this->at(0)) + ->expects($this->once()) ->method('getParam') ->with('key') ->willReturn('background'); $this->request - ->expects($this->at(1)) + ->expects($this->once()) ->method('getUploadedFile') ->with('image') ->willReturn([ @@ -547,8 +524,8 @@ class ThemingControllerTest extends TestCase { $expected = new DataResponse( [ - 'data' => - [ + 'data' + => [ 'message' => $expectedErrorMessage, ], 'status' => 'failure' @@ -558,17 +535,15 @@ class ThemingControllerTest extends TestCase { $this->assertEquals($expected, $this->themingController->uploadImage()); } - /** - * @dataProvider dataPhpUploadErrors - */ - public function testUpdateLogoUploadWithInvalidImageUpload($error, $expectedErrorMessage) { + #[\PHPUnit\Framework\Attributes\DataProvider('dataPhpUploadErrors')] + public function testUpdateLogoUploadWithInvalidImageUpload($error, $expectedErrorMessage): void { $this->request - ->expects($this->at(0)) + ->expects($this->once()) ->method('getParam') ->with('key') ->willReturn('background'); $this->request - ->expects($this->at(1)) + ->expects($this->once()) ->method('getUploadedFile') ->with('image') ->willReturn([ @@ -586,8 +561,8 @@ class ThemingControllerTest extends TestCase { $expected = new DataResponse( [ - 'data' => - [ + 'data' + => [ 'message' => $expectedErrorMessage ], 'status' => 'failure' @@ -597,7 +572,7 @@ class ThemingControllerTest extends TestCase { $this->assertEquals($expected, $this->themingController->uploadImage()); } - public function testUndo() { + public function testUndo(): void { $this->l10n ->expects($this->once()) ->method('t') @@ -608,24 +583,13 @@ class ThemingControllerTest extends TestCase { ->method('undo') ->with('MySetting') ->willReturn('MyValue'); - $this->scssCacher - ->expects($this->once()) - ->method('getCachedSCSS') - ->with('core', '/core/css/css-variables.scss') - ->willReturn('/core/css/someHash-css-variables.scss'); - $this->urlGenerator - ->expects($this->once()) - ->method('linkTo') - ->with('', '/core/css/someHash-css-variables.scss') - ->willReturn('/nextcloudWebroot/core/css/someHash-css-variables.scss'); $expected = new DataResponse( [ - 'data' => - [ + 'data' + => [ 'value' => 'MyValue', - 'message' => 'Saved', - 'serverCssUrl' => '/nextcloudWebroot/core/css/someHash-css-variables.scss', + 'message' => 'Saved' ], 'status' => 'success' ] @@ -633,15 +597,15 @@ class ThemingControllerTest extends TestCase { $this->assertEquals($expected, $this->themingController->undo('MySetting')); } - public function dataUndoDelete() { + public static function dataUndoDelete(): array { return [ [ 'backgroundMime', 'background' ], [ 'logoMime', 'logo' ] ]; } - /** @dataProvider dataUndoDelete */ - public function testUndoDelete($value, $filename) { + #[\PHPUnit\Framework\Attributes\DataProvider('dataUndoDelete')] + public function testUndoDelete(string $value, string $filename): void { $this->l10n ->expects($this->once()) ->method('t') @@ -652,24 +616,13 @@ class ThemingControllerTest extends TestCase { ->method('undo') ->with($value) ->willReturn($value); - $this->scssCacher - ->expects($this->once()) - ->method('getCachedSCSS') - ->with('core', '/core/css/css-variables.scss') - ->willReturn('/core/css/someHash-css-variables.scss'); - $this->urlGenerator - ->expects($this->once()) - ->method('linkTo') - ->with('', '/core/css/someHash-css-variables.scss') - ->willReturn('/nextcloudWebroot/core/css/someHash-css-variables.scss'); $expected = new DataResponse( [ - 'data' => - [ + 'data' + => [ 'value' => $value, 'message' => 'Saved', - 'serverCssUrl' => '/nextcloudWebroot/core/css/someHash-css-variables.scss', ], 'status' => 'success' ] @@ -679,17 +632,19 @@ class ThemingControllerTest extends TestCase { - public function testGetLogoNotExistent() { + public function testGetLogoNotExistent(): void { $this->imageManager->method('getImage') ->with($this->equalTo('logo')) ->willThrowException(new NotFoundException()); - $expected = new Http\NotFoundResponse(); + $expected = new NotFoundResponse(); $this->assertEquals($expected, $this->themingController->getImage('logo')); } - public function testGetLogo() { + public function testGetLogo(): void { $file = $this->createMock(ISimpleFile::class); + $file->method('getName')->willReturn('logo.svg'); + $file->method('getMTime')->willReturn(42); $this->imageManager->expects($this->once()) ->method('getImage') ->willReturn($file); @@ -699,27 +654,29 @@ class ThemingControllerTest extends TestCase { ->with('theming', 'logoMime', '') ->willReturn('text/svg'); - @$expected = new Http\FileDisplayResponse($file); + @$expected = new FileDisplayResponse($file); $expected->cacheFor(3600); $expected->addHeader('Content-Type', 'text/svg'); $expected->addHeader('Content-Disposition', 'attachment; filename="logo"'); - $csp = new Http\ContentSecurityPolicy(); + $csp = new ContentSecurityPolicy(); $csp->allowInlineStyle(); $expected->setContentSecurityPolicy($csp); @$this->assertEquals($expected, $this->themingController->getImage('logo')); } - public function testGetLoginBackgroundNotExistent() { + public function testGetLoginBackgroundNotExistent(): void { $this->imageManager->method('getImage') ->with($this->equalTo('background')) ->willThrowException(new NotFoundException()); - $expected = new Http\NotFoundResponse(); + $expected = new NotFoundResponse(); $this->assertEquals($expected, $this->themingController->getImage('background')); } - public function testGetLoginBackground() { + public function testGetLoginBackground(): void { $file = $this->createMock(ISimpleFile::class); + $file->method('getName')->willReturn('background.png'); + $file->method('getMTime')->willReturn(42); $this->imageManager->expects($this->once()) ->method('getImage') ->willReturn($file); @@ -730,61 +687,25 @@ class ThemingControllerTest extends TestCase { ->with('theming', 'backgroundMime', '') ->willReturn('image/png'); - @$expected = new Http\FileDisplayResponse($file); + @$expected = new FileDisplayResponse($file); $expected->cacheFor(3600); $expected->addHeader('Content-Type', 'image/png'); $expected->addHeader('Content-Disposition', 'attachment; filename="background"'); - $csp = new Http\ContentSecurityPolicy(); + $csp = new ContentSecurityPolicy(); $csp->allowInlineStyle(); $expected->setContentSecurityPolicy($csp); @$this->assertEquals($expected, $this->themingController->getImage('background')); } - - public function testGetStylesheet() { - $this->appManager->expects($this->once())->method('getAppPath')->with('theming')->willReturn(\OC::$SERVERROOT . '/theming'); - $file = $this->createMock(ISimpleFile::class); - $file->expects($this->any())->method('getName')->willReturn('theming.css'); - $file->expects($this->any())->method('getContent')->willReturn('compiled'); - $this->scssCacher->expects($this->once())->method('process')->willReturn(true); - $this->scssCacher->expects($this->once())->method('getCachedCSS')->willReturn($file); - - $response = new Http\FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => 'text/css']); - $response->cacheFor(86400); - - $actual = $this->themingController->getStylesheet(); - $this->assertEquals($response, $actual); - } - - public function testGetStylesheetFails() { - $this->appManager->expects($this->once())->method('getAppPath')->with('theming')->willReturn(\OC::$SERVERROOT . '/theming'); - $file = $this->createMock(ISimpleFile::class); - $file->expects($this->any())->method('getName')->willReturn('theming.css'); - $file->expects($this->any())->method('getContent')->willReturn('compiled'); - $this->scssCacher->expects($this->once())->method('process')->willReturn(true); - $this->scssCacher->expects($this->once())->method('getCachedCSS')->willThrowException(new NotFoundException()); - $response = new Http\NotFoundResponse(); - - $actual = $this->themingController->getStylesheet(); - $this->assertEquals($response, $actual); - } - - public function testGetStylesheetOutsideServerroot() { - $this->appManager->expects($this->once())->method('getAppPath')->with('theming')->willReturn('/outside/serverroot/theming'); - $file = $this->createMock(ISimpleFile::class); - $file->expects($this->any())->method('getName')->willReturn('theming.css'); - $file->expects($this->any())->method('getContent')->willReturn('compiled'); - $this->scssCacher->expects($this->once())->method('process')->with('/outside/serverroot/theming', 'css/theming.scss', 'theming')->willReturn(true); - $this->scssCacher->expects($this->once())->method('getCachedCSS')->willReturn($file); - - $response = new Http\FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => 'text/css']); - $response->cacheFor(86400); - - $actual = $this->themingController->getStylesheet(); - $this->assertEquals($response, $actual); + public static function dataGetManifest(): array { + return [ + [true], + [false], + ]; } - public function testGetManifest() { + #[\PHPUnit\Framework\Attributes\DataProvider('dataGetManifest')] + public function testGetManifest(bool $standalone): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -795,24 +716,26 @@ class ThemingControllerTest extends TestCase { ->method('getName') ->willReturn('Nextcloud'); $this->urlGenerator - ->expects($this->at(0)) + ->expects($this->once()) ->method('getBaseUrl') ->willReturn('localhost'); $this->urlGenerator - ->expects($this->at(1)) - ->method('linkToRoute') - ->with('theming.Icon.getTouchIcon', ['app' => 'core']) - ->willReturn('touchicon'); - $this->urlGenerator - ->expects($this->at(2)) + ->expects($this->exactly(2)) ->method('linkToRoute') - ->with('theming.Icon.getFavicon', ['app' => 'core']) - ->willReturn('favicon'); - $response = new Http\JSONResponse([ + ->willReturnMap([ + ['theming.Icon.getTouchIcon', ['app' => 'core'], 'touchicon'], + ['theming.Icon.getFavicon', ['app' => 'core'], 'favicon'], + ]); + $this->config + ->expects($this->exactly(2)) + ->method('getSystemValueBool') + ->with('theming.standalone_window.enabled', true) + ->willReturn($standalone); + $response = new JSONResponse([ 'name' => 'Nextcloud', 'start_url' => 'localhost', - 'icons' => - [ + 'icons' + => [ [ 'src' => 'touchicon?v=0', 'type' => 'image/png', @@ -824,7 +747,12 @@ class ThemingControllerTest extends TestCase { 'sizes' => '16x16' ] ], - 'display' => 'standalone' + 'display_override' => [$standalone ? 'minimal-ui' : ''], + 'display' => $standalone ? 'standalone' : 'browser', + 'short_name' => 'Nextcloud', + 'theme_color' => null, + 'background_color' => null, + 'description' => null ]); $response->cacheFor(3600); $this->assertEquals($response, $this->themingController->getManifest('core')); diff --git a/apps/theming/tests/Controller/UserThemeControllerTest.php b/apps/theming/tests/Controller/UserThemeControllerTest.php new file mode 100644 index 00000000000..9a8c1cd19aa --- /dev/null +++ b/apps/theming/tests/Controller/UserThemeControllerTest.php @@ -0,0 +1,124 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Theming\Tests\Controller; + +use OCA\Theming\AppInfo\Application; +use OCA\Theming\Controller\UserThemeController; +use OCA\Theming\ITheme; +use OCA\Theming\Service\BackgroundService; +use OCA\Theming\Service\ThemesService; +use OCA\Theming\Themes\DarkHighContrastTheme; +use OCA\Theming\Themes\DarkTheme; +use OCA\Theming\Themes\DefaultTheme; +use OCA\Theming\Themes\DyslexiaFont; +use OCA\Theming\Themes\HighContrastTheme; +use OCA\Theming\Themes\LightTheme; +use OCA\Theming\ThemingDefaults; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCS\OCSBadRequestException; +use OCP\IConfig; +use OCP\IRequest; +use OCP\IUser; +use OCP\IUserSession; +use PHPUnit\Framework\MockObject\MockObject; +use Test\TestCase; + +class UserThemeControllerTest extends TestCase { + private IRequest&MockObject $request; + private IConfig&MockObject $config; + private IUserSession&MockObject $userSession; + private ThemesService&MockObject $themesService; + private ThemingDefaults&MockObject $themingDefaults; + private BackgroundService&MockObject $backgroundService; + private UserThemeController $userThemeController; + + + /** @var ITheme[] */ + private array $themes; + + protected function setUp(): void { + $this->request = $this->createMock(IRequest::class); + $this->config = $this->createMock(IConfig::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->themesService = $this->createMock(ThemesService::class); + $this->themingDefaults = $this->createMock(ThemingDefaults::class); + $this->backgroundService = $this->createMock(BackgroundService::class); + + $this->themes = [ + 'default' => $this->createMock(DefaultTheme::class), + 'light' => $this->createMock(LightTheme::class), + 'dark' => $this->createMock(DarkTheme::class), + 'light-highcontrast' => $this->createMock(HighContrastTheme::class), + 'dark-highcontrast' => $this->createMock(DarkHighContrastTheme::class), + 'opendyslexic' => $this->createMock(DyslexiaFont::class), + ]; + + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->any()) + ->method('getUser') + ->willReturn($user); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('user'); + + $this->userThemeController = new UserThemeController( + Application::APP_ID, + $this->request, + $this->config, + $this->userSession, + $this->themesService, + $this->themingDefaults, + $this->backgroundService, + ); + + parent::setUp(); + } + + public static function dataTestThemes(): array { + return [ + ['default'], + ['light'], + ['dark'], + ['light-highcontrast'], + ['dark-highcontrast'], + ['opendyslexic'], + ['', OCSBadRequestException::class], + ['badTheme', OCSBadRequestException::class], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestThemes')] + public function testEnableTheme(string $themeId, ?string $exception = null): void { + $this->themesService + ->expects($this->any()) + ->method('getThemes') + ->willReturn($this->themes); + + if ($exception) { + $this->expectException($exception); + } + + $expected = new DataResponse(); + $this->assertEquals($expected, $this->userThemeController->enableTheme($themeId)); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestThemes')] + public function testDisableTheme(string $themeId, ?string $exception = null): void { + $this->themesService + ->expects($this->any()) + ->method('getThemes') + ->willReturn($this->themes); + + if ($exception) { + $this->expectException($exception); + } + + $expected = new DataResponse(); + $this->assertEquals($expected, $this->userThemeController->disableTheme($themeId)); + } +} diff --git a/apps/theming/tests/IconBuilderTest.php b/apps/theming/tests/IconBuilderTest.php index 542bfcc51bd..d881e4eb75c 100644 --- a/apps/theming/tests/IconBuilderTest.php +++ b/apps/theming/tests/IconBuilderTest.php @@ -1,31 +1,10 @@ <?php + +declare(strict_types=1); /** - * @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Jan-Christoph Borchardt <hey@jancborchardt.net> - * @author Joas Schilling <coding@schilljs.com> - * @author Julius Haertl <jus@bitgrid.net> - * @author Julius Härtl <jus@bitgrid.net> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ - namespace OCA\Theming\Tests; use OC\Files\AppData\AppData; @@ -36,25 +15,18 @@ use OCA\Theming\Util; use OCP\App\IAppManager; use OCP\Files\NotFoundException; use OCP\IConfig; -use PHPUnit\Framework\Error\Warning; +use OCP\ServerVersion; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class IconBuilderTest extends TestCase { - - /** @var IConfig */ - protected $config; - /** @var AppData */ - protected $appData; - /** @var ThemingDefaults */ - protected $themingDefaults; - /** @var Util */ - protected $util; - /** @var ImageManager */ - protected $imageManager; - /** @var IconBuilder */ - protected $iconBuilder; - /** @var IAppManager */ - protected $appManager; + protected IConfig&MockObject $config; + protected AppData&MockObject $appData; + protected ThemingDefaults&MockObject $themingDefaults; + protected ImageManager&MockObject $imageManager; + protected IAppManager&MockObject $appManager; + protected Util $util; + protected IconBuilder $iconBuilder; protected function setUp(): void { parent::setUp(); @@ -64,7 +36,7 @@ class IconBuilderTest extends TestCase { $this->themingDefaults = $this->createMock(ThemingDefaults::class); $this->appManager = $this->createMock(IAppManager::class); $this->imageManager = $this->createMock(ImageManager::class); - $this->util = new Util($this->config, $this->appManager, $this->appData); + $this->util = new Util($this->createMock(ServerVersion::class), $this->config, $this->appManager, $this->appData, $this->imageManager); $this->iconBuilder = new IconBuilder($this->themingDefaults, $this->util, $this->imageManager); } @@ -81,7 +53,7 @@ class IconBuilderTest extends TestCase { } } - public function dataRenderAppIcon() { + public static function dataRenderAppIcon(): array { return [ ['core', '#0082c9', 'touch-original.png'], ['core', '#FF0000', 'touch-core-red.png'], @@ -91,23 +63,18 @@ class IconBuilderTest extends TestCase { ]; } - /** - * @dataProvider dataRenderAppIcon - * @param $app - * @param $color - * @param $file - */ - public function testRenderAppIcon($app, $color, $file) { + #[\PHPUnit\Framework\Attributes\DataProvider('dataRenderAppIcon')] + public function testRenderAppIcon(string $app, string $color, string $file): void { $this->checkImagick(); $this->themingDefaults->expects($this->once()) ->method('getColorPrimary') ->willReturn($color); $this->appData->expects($this->once()) ->method('getFolder') - ->with('images') + ->with('global/images') ->willThrowException(new NotFoundException()); - $expectedIcon = new \Imagick(realpath(dirname(__FILE__)). "/data/" . $file); + $expectedIcon = new \Imagick(realpath(__DIR__) . '/data/' . $file); $icon = $this->iconBuilder->renderAppIcon($app, 512); $this->assertEquals(true, $icon->valid()); @@ -120,23 +87,18 @@ class IconBuilderTest extends TestCase { // cloud be something like $expectedIcon->compareImages($icon, Imagick::METRIC_MEANABSOLUTEERROR)[1]) } - /** - * @dataProvider dataRenderAppIcon - * @param $app - * @param $color - * @param $file - */ - public function testGetTouchIcon($app, $color, $file) { + #[\PHPUnit\Framework\Attributes\DataProvider('dataRenderAppIcon')] + public function testGetTouchIcon(string $app, string $color, string $file): void { $this->checkImagick(); $this->themingDefaults->expects($this->once()) ->method('getColorPrimary') ->willReturn($color); $this->appData->expects($this->once()) ->method('getFolder') - ->with('images') + ->with('global/images') ->willThrowException(new NotFoundException()); - $expectedIcon = new \Imagick(realpath(dirname(__FILE__)). "/data/" . $file); + $expectedIcon = new \Imagick(realpath(__DIR__) . '/data/' . $file); $icon = new \Imagick(); $icon->readImageBlob($this->iconBuilder->getTouchIcon($app)); @@ -150,13 +112,8 @@ class IconBuilderTest extends TestCase { // cloud be something like $expectedIcon->compareImages($icon, Imagick::METRIC_MEANABSOLUTEERROR)[1]) } - /** - * @dataProvider dataRenderAppIcon - * @param $app - * @param $color - * @param $file - */ - public function testGetFavicon($app, $color, $file) { + #[\PHPUnit\Framework\Attributes\DataProvider('dataRenderAppIcon')] + public function testGetFavicon(string $app, string $color, string $file): void { $this->checkImagick(); $this->imageManager->expects($this->once()) ->method('shouldReplaceIcons') @@ -166,10 +123,10 @@ class IconBuilderTest extends TestCase { ->willReturn($color); $this->appData->expects($this->once()) ->method('getFolder') - ->with('images') + ->with('global/images') ->willThrowException(new NotFoundException()); - $expectedIcon = new \Imagick(realpath(dirname(__FILE__)). "/data/" . $file); + $expectedIcon = new \Imagick(realpath(__DIR__) . '/data/' . $file); $actualIcon = $this->iconBuilder->getFavicon($app); $icon = new \Imagick(); @@ -185,10 +142,9 @@ class IconBuilderTest extends TestCase { // cloud be something like $expectedIcon->compareImages($icon, Imagick::METRIC_MEANABSOLUTEERROR)[1]) } - public function testGetFaviconNotFound() { + public function testGetFaviconNotFound(): void { $this->checkImagick(); - $this->expectException(Warning::class); - $util = $this->getMockBuilder(Util::class)->disableOriginalConstructor()->getMock(); + $util = $this->createMock(Util::class); $iconBuilder = new IconBuilder($this->themingDefaults, $util, $this->imageManager); $this->imageManager->expects($this->once()) ->method('shouldReplaceIcons') @@ -199,10 +155,9 @@ class IconBuilderTest extends TestCase { $this->assertFalse($iconBuilder->getFavicon('noapp')); } - public function testGetTouchIconNotFound() { + public function testGetTouchIconNotFound(): void { $this->checkImagick(); - $this->expectException(Warning::class); - $util = $this->getMockBuilder(Util::class)->disableOriginalConstructor()->getMock(); + $util = $this->createMock(Util::class); $iconBuilder = new IconBuilder($this->themingDefaults, $util, $this->imageManager); $util->expects($this->once()) ->method('getAppIcon') @@ -210,14 +165,13 @@ class IconBuilderTest extends TestCase { $this->assertFalse($iconBuilder->getTouchIcon('noapp')); } - public function testColorSvgNotFound() { + public function testColorSvgNotFound(): void { $this->checkImagick(); - $this->expectException(Warning::class); - $util = $this->getMockBuilder(Util::class)->disableOriginalConstructor()->getMock(); + $util = $this->createMock(Util::class); $iconBuilder = new IconBuilder($this->themingDefaults, $util, $this->imageManager); $util->expects($this->once()) ->method('getAppImage') ->willReturn('notexistingfile'); - $this->assertFalse($iconBuilder->colorSvg('noapp','noimage')); + $this->assertFalse($iconBuilder->colorSvg('noapp', 'noimage')); } } diff --git a/apps/theming/tests/ImageManagerTest.php b/apps/theming/tests/ImageManagerTest.php index 08a8fe821f0..0c4d555cc00 100644 --- a/apps/theming/tests/ImageManagerTest.php +++ b/apps/theming/tests/ImageManagerTest.php @@ -1,62 +1,35 @@ <?php + +declare(strict_types=1); /** - * @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Julius Haertl <jus@bitgrid.net> - * @author Julius Härtl <jus@bitgrid.net> - * @author Michael Weimann <mail@michael-weimann.eu> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ - namespace OCA\Theming\Tests; use OCA\Theming\ImageManager; +use OCA\Theming\Service\BackgroundService; use OCP\Files\IAppData; use OCP\Files\NotFoundException; use OCP\Files\SimpleFS\ISimpleFile; use OCP\Files\SimpleFS\ISimpleFolder; use OCP\ICacheFactory; use OCP\IConfig; -use OCP\ILogger; use OCP\ITempManager; use OCP\IURLGenerator; use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; use Test\TestCase; class ImageManagerTest extends TestCase { - - /** @var IConfig|MockObject */ - protected $config; - /** @var IAppData|MockObject */ - protected $appData; - /** @var ImageManager */ - protected $imageManager; - /** @var IURLGenerator|MockObject */ - private $urlGenerator; - /** @var ICacheFactory|MockObject */ - private $cacheFactory; - /** @var ILogger|MockObject */ - private $logger; - /** @var ITempManager|MockObject */ - private $tempManager; + protected IConfig&MockObject $config; + protected IAppData&MockObject $appData; + private IURLGenerator&MockObject $urlGenerator; + private ICacheFactory&MockObject $cacheFactory; + private LoggerInterface&MockObject $logger; + private ITempManager&MockObject $tempManager; + private ISimpleFolder&MockObject $rootFolder; + protected ImageManager $imageManager; protected function setUp(): void { parent::setUp(); @@ -64,16 +37,24 @@ class ImageManagerTest extends TestCase { $this->appData = $this->createMock(IAppData::class); $this->urlGenerator = $this->createMock(IURLGenerator::class); $this->cacheFactory = $this->createMock(ICacheFactory::class); - $this->logger = $this->createMock(ILogger::class); + $this->logger = $this->createMock(LoggerInterface::class); $this->tempManager = $this->createMock(ITempManager::class); + $this->rootFolder = $this->createMock(ISimpleFolder::class); + $backgroundService = $this->createMock(BackgroundService::class); $this->imageManager = new ImageManager( $this->config, $this->appData, $this->urlGenerator, $this->cacheFactory, $this->logger, - $this->tempManager + $this->tempManager, + $backgroundService, ); + $this->appData + ->expects($this->any()) + ->method('getFolder') + ->with('global') + ->willReturn($this->rootFolder); } private function checkImagick() { @@ -101,57 +82,51 @@ class ImageManagerTest extends TestCase { $file->expects($this->once()) ->method('getContent') ->willReturn(file_get_contents(__DIR__ . '/../../../tests/data/testimage.png')); - $folder->expects($this->at(0)) - ->method('fileExists') - ->with('logo') - ->willReturn(true); - $folder->expects($this->at(1)) + $folder->expects($this->exactly(2)) ->method('fileExists') - ->with('logo.png') - ->willReturn(false); - $folder->expects($this->at(2)) + ->willReturnMap([ + ['logo', true], + ['logo.png', false], + ]); + $folder->expects($this->once()) ->method('getFile') ->with('logo') ->willReturn($file); $newFile = $this->createMock(ISimpleFile::class); - $folder->expects($this->at(3)) + $folder->expects($this->once()) ->method('newFile') ->with('logo.png') ->willReturn($newFile); $newFile->expects($this->once()) ->method('putContent'); - $this->appData->expects($this->once()) + $this->rootFolder->expects($this->once()) ->method('getFolder') ->with('images') ->willReturn($folder); } } - public function testGetImageUrl() { + public function testGetImageUrl(): void { $this->checkImagick(); - $file = $this->createMock(ISimpleFile::class); $this->config->expects($this->exactly(2)) ->method('getAppValue') - ->withConsecutive( - ['theming', 'cachebuster', '0'], - ['theming', 'logoMime', ''] - ) - ->willReturn(0); - $this->mockGetImage('logo', $file); + ->willReturnMap([ + ['theming', 'cachebuster', '0', '0'], + ['theming', 'logoMime', '', '0'], + ]); $this->urlGenerator->expects($this->once()) ->method('linkToRoute') ->willReturn('url-to-image'); $this->assertEquals('url-to-image?v=0', $this->imageManager->getImageUrl('logo', false)); } - public function testGetImageUrlDefault() { + public function testGetImageUrlDefault(): void { $this->config->expects($this->exactly(2)) ->method('getAppValue') - ->withConsecutive( - ['theming', 'cachebuster', '0'], - ['theming', 'logoMime', false] - ) - ->willReturnOnConsecutiveCalls(0, false); + ->willReturnMap([ + ['theming', 'cachebuster', '0', '0'], + ['theming', 'logoMime', '', ''], + ]); $this->urlGenerator->expects($this->once()) ->method('imagePath') ->with('core', 'logo/logo.png') @@ -159,30 +134,21 @@ class ImageManagerTest extends TestCase { $this->assertEquals('logo/logo.png?v=0', $this->imageManager->getImageUrl('logo')); } - public function testGetImageUrlAbsolute() { + public function testGetImageUrlAbsolute(): void { $this->checkImagick(); - $file = $this->createMock(ISimpleFile::class); $this->config->expects($this->exactly(2)) ->method('getAppValue') - ->withConsecutive( - ['theming', 'cachebuster', '0'], - ['theming', 'logoMime', ''] - ) - ->willReturn(0); - $this->mockGetImage('logo', $file); - $this->urlGenerator->expects($this->at(0)) - ->method('getBaseUrl') - ->willReturn('baseurl'); - $this->urlGenerator->expects($this->at(1)) - ->method('getAbsoluteUrl') - ->willReturn('url-to-image-absolute?v=0'); - $this->urlGenerator->expects($this->at(2)) + ->willReturnMap([ + ['theming', 'cachebuster', '0', '0'], + ['theming', 'logoMime', '', ''], + ]); + $this->urlGenerator->expects($this->any()) ->method('getAbsoluteUrl') ->willReturn('url-to-image-absolute?v=0'); $this->assertEquals('url-to-image-absolute?v=0', $this->imageManager->getImageUrlAbsolute('logo', false)); } - public function testGetImage() { + public function testGetImage(): void { $this->checkImagick(); $this->config->expects($this->once()) ->method('getAppValue')->with('theming', 'logoMime', false) @@ -193,8 +159,8 @@ class ImageManagerTest extends TestCase { } - public function testGetImageUnset() { - $this->expectException(\OCP\Files\NotFoundException::class); + public function testGetImageUnset(): void { + $this->expectException(NotFoundException::class); $this->config->expects($this->once()) ->method('getAppValue')->with('theming', 'logoMime', false) @@ -202,42 +168,42 @@ class ImageManagerTest extends TestCase { $this->imageManager->getImage('logo'); } - public function testGetCacheFolder() { + public function testGetCacheFolder(): void { $folder = $this->createMock(ISimpleFolder::class); $this->config->expects($this->once()) ->method('getAppValue') ->with('theming', 'cachebuster', '0') ->willReturn('0'); - $this->appData->expects($this->at(0)) + $this->rootFolder->expects($this->once()) ->method('getFolder') ->with('0') ->willReturn($folder); $this->assertEquals($folder, $this->imageManager->getCacheFolder()); } - public function testGetCacheFolderCreate() { + public function testGetCacheFolderCreate(): void { $folder = $this->createMock(ISimpleFolder::class); $this->config->expects($this->exactly(2)) ->method('getAppValue') ->with('theming', 'cachebuster', '0') ->willReturn('0'); - $this->appData->expects($this->at(0)) + $this->rootFolder->expects($this->exactly(2)) ->method('getFolder') - ->willThrowException(new NotFoundException()); - $this->appData->expects($this->at(1)) - ->method('newFolder') ->with('0') - ->willReturn($folder); - $this->appData->expects($this->at(2)) - ->method('getFolder') + ->willReturnOnConsecutiveCalls( + $this->throwException(new NotFoundException()), + $folder, + ); + $this->rootFolder->expects($this->once()) + ->method('newFolder') ->with('0') ->willReturn($folder); - $this->appData->expects($this->once()) + $this->rootFolder->expects($this->once()) ->method('getDirectoryListing') ->willReturn([]); $this->assertEquals($folder, $this->imageManager->getCacheFolder()); } - public function testGetCachedImage() { + public function testGetCachedImage(): void { $expected = $this->createMock(ISimpleFile::class); $folder = $this->setupCacheFolder(); $folder->expects($this->once()) @@ -248,18 +214,18 @@ class ImageManagerTest extends TestCase { } - public function testGetCachedImageNotFound() { - $this->expectException(\OCP\Files\NotFoundException::class); + public function testGetCachedImageNotFound(): void { + $this->expectException(NotFoundException::class); $folder = $this->setupCacheFolder(); $folder->expects($this->once()) ->method('getFile') ->with('filename') - ->will($this->throwException(new \OCP\Files\NotFoundException())); + ->willThrowException(new NotFoundException()); $image = $this->imageManager->getCachedImage('filename'); } - public function testSetCachedImage() { + public function testSetCachedImage(): void { $folder = $this->setupCacheFolder(); $file = $this->createMock(ISimpleFile::class); $folder->expects($this->once()) @@ -276,7 +242,7 @@ class ImageManagerTest extends TestCase { $this->assertEquals($file, $this->imageManager->setCachedImage('filename', 'filecontent')); } - public function testSetCachedImageCreate() { + public function testSetCachedImageCreate(): void { $folder = $this->setupCacheFolder(); $file = $this->createMock(ISimpleFile::class); $folder->expects($this->once()) @@ -299,14 +265,14 @@ class ImageManagerTest extends TestCase { ->method('getAppValue') ->with('theming', 'cachebuster', '0') ->willReturn('0'); - $this->appData->expects($this->at(0)) + $this->rootFolder->expects($this->once()) ->method('getFolder') ->with('0') ->willReturn($folder); return $folder; } - public function testCleanup() { + public function testCleanup(): void { $folders = [ $this->createMock(ISimpleFolder::class), $this->createMock(ISimpleFolder::class), @@ -315,19 +281,19 @@ class ImageManagerTest extends TestCase { foreach ($folders as $index => $folder) { $folder->expects($this->any()) ->method('getName') - ->willReturn($index); + ->willReturn("$index"); } $folders[0]->expects($this->once())->method('delete'); $folders[1]->expects($this->once())->method('delete'); $folders[2]->expects($this->never())->method('delete'); $this->config->expects($this->once()) ->method('getAppValue') - ->with('theming','cachebuster','0') + ->with('theming', 'cachebuster', '0') ->willReturn('2'); - $this->appData->expects($this->once()) + $this->rootFolder->expects($this->once()) ->method('getDirectoryListing') ->willReturn($folders); - $this->appData->expects($this->once()) + $this->rootFolder->expects($this->once()) ->method('getFolder') ->with('2') ->willReturn($folders[2]); @@ -335,43 +301,46 @@ class ImageManagerTest extends TestCase { } - public function dataUpdateImage() { + public static function dataUpdateImage(): array { return [ - ['background', __DIR__ . '/../../../tests/data/testimage.png', true, true], - ['background', __DIR__ . '/../../../tests/data/testimage.png', false, true], - ['background', __DIR__ . '/../../../tests/data/testimage.jpg', true, true], + ['background', __DIR__ . '/../../../tests/data/testimage.png', true, false], + ['background', __DIR__ . '/../../../tests/data/testimage.png', false, false], + ['background', __DIR__ . '/../../../tests/data/testimage.jpg', true, false], + ['background', __DIR__ . '/../../../tests/data/testimage.webp', true, false], + ['background', __DIR__ . '/../../../tests/data/testimage-large.jpg', true, true], + ['background', __DIR__ . '/../../../tests/data/testimage-wide.png', true, true], ['logo', __DIR__ . '/../../../tests/data/testimagelarge.svg', true, false], ]; } - /** - * @dataProvider dataUpdateImage - */ - public function testUpdateImage($key, $tmpFile, $folderExists, $shouldConvert) { + #[\PHPUnit\Framework\Attributes\DataProvider('dataUpdateImage')] + public function testUpdateImage(string $key, string $tmpFile, bool $folderExists, bool $shouldConvert): void { $file = $this->createMock(ISimpleFile::class); $folder = $this->createMock(ISimpleFolder::class); $oldFile = $this->createMock(ISimpleFile::class); $folder->expects($this->any()) ->method('getFile') ->willReturn($oldFile); + if ($folderExists) { - $this->appData + $this->rootFolder ->expects($this->any()) ->method('getFolder') ->with('images') ->willReturn($folder); } else { - $this->appData + $this->rootFolder ->expects($this->any()) ->method('getFolder') ->with('images') ->willThrowException(new NotFoundException()); - $this->appData + $this->rootFolder ->expects($this->any()) ->method('newFolder') ->with('images') ->willReturn($folder); } + $folder->expects($this->once()) ->method('newFile') ->with($key) @@ -385,4 +354,30 @@ class ImageManagerTest extends TestCase { $this->imageManager->updateImage($key, $tmpFile); } + + public function testUnsupportedImageType(): void { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Unsupported image type: text/plain'); + + $file = $this->createMock(ISimpleFile::class); + $folder = $this->createMock(ISimpleFolder::class); + $oldFile = $this->createMock(ISimpleFile::class); + + $folder->expects($this->any()) + ->method('getFile') + ->willReturn($oldFile); + + $this->rootFolder + ->expects($this->any()) + ->method('getFolder') + ->with('images') + ->willReturn($folder); + + $folder->expects($this->once()) + ->method('newFile') + ->with('favicon') + ->willReturn($file); + + $this->imageManager->updateImage('favicon', __DIR__ . '/../../../tests/data/lorem.txt'); + } } diff --git a/apps/theming/tests/Migration/Version2006Date20240905111627Test.php b/apps/theming/tests/Migration/Version2006Date20240905111627Test.php new file mode 100644 index 00000000000..5f7458db11a --- /dev/null +++ b/apps/theming/tests/Migration/Version2006Date20240905111627Test.php @@ -0,0 +1,182 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Theming\Tests\Migration; + +use OCA\Theming\Migration\Version2006Date20240905111627; +use OCP\BackgroundJob\IJobList; +use OCP\Config\IUserConfig; +use OCP\IAppConfig; +use OCP\IDBConnection; +use OCP\IUserManager; +use OCP\Migration\IOutput; +use OCP\Server; +use PHPUnit\Framework\MockObject\MockObject; +use Test\TestCase; + +/** + * @group DB + */ +class Version2006Date20240905111627Test extends TestCase { + + private IAppConfig&MockObject $appConfig; + private IDBConnection&MockObject $connection; + private IJobList&MockObject $jobList; + private Version2006Date20240905111627 $migration; + + protected function setUp(): void { + parent::setUp(); + + $this->appConfig = $this->createMock(IAppConfig::class); + $this->connection = $this->createMock(IDBConnection::class); + $this->jobList = $this->createMock(IJobList::class); + $this->migration = new Version2006Date20240905111627( + $this->jobList, + $this->appConfig, + $this->connection, + ); + } + + public function testRestoreSystemColors(): void { + $this->appConfig->expects(self::once()) + ->method('getValueString') + ->with('theming', 'color', '') + ->willReturn('ffab00'); + $this->appConfig->expects(self::once()) + ->method('getValueBool') + ->with('theming', 'disable-user-theming') + ->willReturn(true); + + // expect the color value to be deleted + $this->appConfig->expects(self::once()) + ->method('deleteKey') + ->with('theming', 'color'); + // expect the correct calls to setValueString (setting the new values) + $setValueCalls = []; + $this->appConfig->expects(self::exactly(2)) + ->method('setValueString') + ->willReturnCallback(function () use (&$setValueCalls) { + $setValueCalls[] = func_get_args(); + return true; + }); + + $output = $this->createMock(IOutput::class); + $this->migration->changeSchema($output, fn () => null, []); + + $this->assertEquals([ + ['theming', 'background_color', 'ffab00', false, false], + ['theming', 'primary_color', 'ffab00', false, false], + ], $setValueCalls); + } + + /** + * @group DB + */ + public function testRestoreUserColors(): void { + $this->appConfig->expects(self::once()) + ->method('getValueString') + ->with('theming', 'color', '') + ->willReturn(''); + $this->appConfig->expects(self::once()) + ->method('getValueBool') + ->with('theming', 'disable-user-theming') + ->willReturn(false); + + // Create a user + $manager = Server::get(IUserManager::class); + $user = $manager->createUser('theming_legacy', 'theming_legacy'); + self::assertNotFalse($user); + // Set the users theming value to legacy key + $config = Server::get(IUserConfig::class); + $config->setValueString('theming_legacy', 'theming', 'background_color', 'ffab00'); + + // expect some output + $output = $this->createMock(IOutput::class); + $output->expects(self::exactly(3)) + ->method('info') + ->willReturnCallback(fn ($txt) => match($txt) { + 'No custom system color configured - skipping' => true, + 'Restoring user primary color' => true, + 'Primary color of users restored' => true, + default => self::fail('output.info called with unexpected argument: ' . $txt) + }); + // Create the migration class + $migration = new Version2006Date20240905111627( + $this->jobList, + $this->appConfig, + Server::get(IDBConnection::class), + ); + // Run the migration + $migration->changeSchema($output, fn () => null, []); + + // See new value + $config->clearCache('theming_legacy'); + $newValue = $config->getValueString('theming_legacy', 'theming', 'primary_color'); + self::assertEquals('ffab00', $newValue); + + // cleanup + $user->delete(); + } + + /** + * Ensure only users with background color but no primary color are migrated + * @group DB + */ + public function testRestoreUserColorsWithConflicts(): void { + $this->appConfig->expects(self::once()) + ->method('getValueString') + ->with('theming', 'color', '') + ->willReturn(''); + $this->appConfig->expects(self::once()) + ->method('getValueBool') + ->with('theming', 'disable-user-theming') + ->willReturn(false); + + // Create a user + $manager = Server::get(IUserManager::class); + $legacyUser = $manager->createUser('theming_legacy', 'theming_legacy'); + self::assertNotFalse($legacyUser); + $user = $manager->createUser('theming_no_legacy', 'theming_no_legacy'); + self::assertNotFalse($user); + // Set the users theming value to legacy key + $config = Server::get(IUserConfig::class); + $config->setValueString($user->getUID(), 'theming', 'primary_color', '999999'); + $config->setValueString($user->getUID(), 'theming', 'background_color', '111111'); + $config->setValueString($legacyUser->getUID(), 'theming', 'background_color', 'ffab00'); + + // expect some output + $output = $this->createMock(IOutput::class); + $output->expects(self::exactly(3)) + ->method('info') + ->willReturnCallback(fn ($txt) => match($txt) { + 'No custom system color configured - skipping' => true, + 'Restoring user primary color' => true, + 'Primary color of users restored' => true, + default => self::fail('output.info called with unexpected argument: ' . $txt) + }); + // Create the migration class + $migration = new Version2006Date20240905111627( + $this->jobList, + $this->appConfig, + Server::get(IDBConnection::class), + ); + // Run the migration + $migration->changeSchema($output, fn () => null, []); + + // See new value of only the legacy user + $config->clearCacheAll(); + self::assertEquals('111111', $config->getValueString($user->getUID(), 'theming', 'background_color')); + self::assertEquals('999999', $config->getValueString($user->getUID(), 'theming', 'primary_color')); + self::assertEquals('ffab00', $config->getValueString($legacyUser->getUID(), 'theming', 'primary_color')); + + // cleanup + $legacyUser->delete(); + $user->delete(); + } +} diff --git a/apps/theming/tests/Service/ThemesServiceTest.php b/apps/theming/tests/Service/ThemesServiceTest.php new file mode 100644 index 00000000000..354ed1dec85 --- /dev/null +++ b/apps/theming/tests/Service/ThemesServiceTest.php @@ -0,0 +1,369 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Theming\Tests\Service; + +use OCA\Theming\AppInfo\Application; +use OCA\Theming\ImageManager; +use OCA\Theming\ITheme; +use OCA\Theming\Service\ThemesService; +use OCA\Theming\Themes\DarkHighContrastTheme; +use OCA\Theming\Themes\DarkTheme; +use OCA\Theming\Themes\DefaultTheme; +use OCA\Theming\Themes\DyslexiaFont; +use OCA\Theming\Themes\HighContrastTheme; +use OCA\Theming\Themes\LightTheme; +use OCA\Theming\ThemingDefaults; +use OCA\Theming\Util; +use OCP\App\IAppManager; +use OCP\IConfig; +use OCP\IL10N; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\IUserSession; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; +use Test\TestCase; + +class ThemesServiceTest extends TestCase { + private IUserSession&MockObject $userSession; + private IConfig&MockObject $config; + private LoggerInterface&MockObject $logger; + + private ThemingDefaults&MockObject $themingDefaults; + private ThemesService $themesService; + + /** @var ITheme[] */ + private array $themes; + + protected function setUp(): void { + $this->userSession = $this->createMock(IUserSession::class); + $this->config = $this->createMock(IConfig::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->themingDefaults = $this->createMock(ThemingDefaults::class); + + $this->themingDefaults->expects($this->any()) + ->method('getColorPrimary') + ->willReturn('#0082c9'); + + $this->themingDefaults->expects($this->any()) + ->method('getDefaultColorPrimary') + ->willReturn('#0082c9'); + + $this->initThemes(); + + $this->themesService = new ThemesService( + $this->userSession, + $this->config, + $this->logger, + ...array_values($this->themes) + ); + + parent::setUp(); + } + + public function testGetThemes(): void { + $expected = [ + 'default', + 'light', + 'dark', + 'light-highcontrast', + 'dark-highcontrast', + 'opendyslexic', + ]; + $this->assertEquals($expected, array_keys($this->themesService->getThemes())); + } + + public function testGetThemesEnforced(): void { + $this->config->expects($this->once()) + ->method('getSystemValueString') + ->with('enforce_theme', '') + ->willReturn('dark'); + $this->logger->expects($this->never()) + ->method('error'); + + $expected = [ + 'default', + 'dark', + ]; + + $this->assertEquals($expected, array_keys($this->themesService->getThemes())); + } + + public function testGetThemesEnforcedInvalid(): void { + $this->config->expects($this->once()) + ->method('getSystemValueString') + ->with('enforce_theme', '') + ->willReturn('invalid'); + $this->logger->expects($this->once()) + ->method('error') + ->with('Enforced theme not found', ['theme' => 'invalid']); + + $expected = [ + 'default', + 'light', + 'dark', + 'light-highcontrast', + 'dark-highcontrast', + 'opendyslexic', + ]; + + $this->assertEquals($expected, array_keys($this->themesService->getThemes())); + } + + public static function dataTestEnableTheme(): array { + return [ + ['default', ['default'], ['default']], + ['dark', ['default'], ['dark']], + ['dark', ['dark'], ['dark']], + ['opendyslexic', ['dark'], ['dark', 'opendyslexic']], + ['dark', ['light-highcontrast', 'opendyslexic'], ['opendyslexic', 'dark']], + ]; + } + + /** + * + * @param string[] $enabledThemes + * @param string[] $expectedEnabled + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestEnableTheme')] + public function testEnableTheme(string $toEnable, array $enabledThemes, array $expectedEnabled): void { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->any()) + ->method('getUser') + ->willReturn($user); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('user'); + + $this->config->expects($this->once()) + ->method('getUserValue') + ->with('user', Application::APP_ID, 'enabled-themes', '["default"]') + ->willReturn(json_encode($enabledThemes)); + + $this->assertEquals($expectedEnabled, $this->themesService->enableTheme($this->themes[$toEnable])); + } + + + public static function dataTestDisableTheme(): array { + return [ + ['dark', ['default'], ['default']], + ['dark', ['dark'], []], + ['opendyslexic', ['dark', 'opendyslexic'], ['dark'], ], + ['light-highcontrast', ['opendyslexic'], ['opendyslexic']], + ]; + } + + /** + * + * @param string[] $enabledThemes + * @param string[] $expectedEnabled + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestDisableTheme')] + public function testDisableTheme(string $toDisable, array $enabledThemes, array $expectedEnabled): void { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->any()) + ->method('getUser') + ->willReturn($user); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('user'); + + $this->config->expects($this->once()) + ->method('getUserValue') + ->with('user', Application::APP_ID, 'enabled-themes', '["default"]') + ->willReturn(json_encode($enabledThemes)); + + + $this->assertEquals($expectedEnabled, $this->themesService->disableTheme($this->themes[$toDisable])); + } + + + public static function dataTestIsEnabled(): array { + return [ + ['dark', [], false], + ['dark', ['dark'], true], + ['opendyslexic', ['dark', 'opendyslexic'], true], + ['light-highcontrast', ['opendyslexic'], false], + ]; + } + + /** + * @param string[] $enabledThemes + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestIsEnabled')] + public function testIsEnabled(string $themeId, array $enabledThemes, bool $expected): void { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->any()) + ->method('getUser') + ->willReturn($user); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('user'); + + $this->config->expects($this->once()) + ->method('getUserValue') + ->with('user', Application::APP_ID, 'enabled-themes', '["default"]') + ->willReturn(json_encode($enabledThemes)); + + + $this->assertEquals($expected, $this->themesService->isEnabled($this->themes[$themeId])); + } + + public function testGetEnabledThemes(): void { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->any()) + ->method('getUser') + ->willReturn($user); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('user'); + + + $this->config->expects($this->once()) + ->method('getUserValue') + ->with('user', Application::APP_ID, 'enabled-themes', '["default"]') + ->willReturn(json_encode(['default'])); + $this->config->expects($this->once()) + ->method('getSystemValueString') + ->with('enforce_theme', '') + ->willReturn(''); + + $this->assertEquals(['default'], $this->themesService->getEnabledThemes()); + } + + public function testGetEnabledThemesEnforced(): void { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->any()) + ->method('getUser') + ->willReturn($user); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('user'); + + + $this->config->expects($this->once()) + ->method('getUserValue') + ->with('user', Application::APP_ID, 'enabled-themes', '["default"]') + ->willReturn(json_encode([])); + $this->config->expects($this->once()) + ->method('getSystemValueString') + ->with('enforce_theme', '') + ->willReturn('light'); + + $this->assertEquals(['light'], $this->themesService->getEnabledThemes()); + } + + + public static function dataTestSetEnabledThemes(): array { + return [ + [[], []], + [['light'], ['light']], + [['dark'], ['dark']], + [['dark', 'dark', 'opendyslexic'], ['dark', 'opendyslexic']], + ]; + } + + /** + * + * @param string[] $enabledThemes + * @param string[] $expected + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestSetEnabledThemes')] + public function testSetEnabledThemes(array $enabledThemes, array $expected): void { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->any()) + ->method('getUser') + ->willReturn($user); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('user'); + + $this->config->expects($this->once()) + ->method('setUserValue') + ->with('user', Application::APP_ID, 'enabled-themes', json_encode($expected)); + + $this->invokePrivate($this->themesService, 'setEnabledThemes', [$enabledThemes]); + } + + private function initThemes() { + $util = $this->createMock(Util::class); + $urlGenerator = $this->createMock(IURLGenerator::class); + $imageManager = $this->createMock(ImageManager::class); + $l10n = $this->createMock(IL10N::class); + $appManager = $this->createMock(IAppManager::class); + + $this->themes = [ + 'default' => new DefaultTheme( + $util, + $this->themingDefaults, + $this->userSession, + $urlGenerator, + $imageManager, + $this->config, + $l10n, + $appManager, + null, + ), + 'light' => new LightTheme( + $util, + $this->themingDefaults, + $this->userSession, + $urlGenerator, + $imageManager, + $this->config, + $l10n, + $appManager, + null, + ), + 'dark' => new DarkTheme( + $util, + $this->themingDefaults, + $this->userSession, + $urlGenerator, + $imageManager, + $this->config, + $l10n, + $appManager, + null, + ), + 'light-highcontrast' => new HighContrastTheme( + $util, + $this->themingDefaults, + $this->userSession, + $urlGenerator, + $imageManager, + $this->config, + $l10n, + $appManager, + null, + ), + 'dark-highcontrast' => new DarkHighContrastTheme( + $util, + $this->themingDefaults, + $this->userSession, + $urlGenerator, + $imageManager, + $this->config, + $l10n, + $appManager, + null, + ), + 'opendyslexic' => new DyslexiaFont( + $util, + $this->themingDefaults, + $this->userSession, + $urlGenerator, + $imageManager, + $this->config, + $l10n, + $appManager, + null, + ), + ]; + } +} diff --git a/apps/theming/tests/ServicesTest.php b/apps/theming/tests/ServicesTest.php index 70d8f402afa..3971c9b6698 100644 --- a/apps/theming/tests/ServicesTest.php +++ b/apps/theming/tests/ServicesTest.php @@ -1,38 +1,20 @@ <?php + +declare(strict_types=1); /** - * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ - namespace OCA\Theming\Tests; use OCA\Theming\Capabilities; use OCA\Theming\Controller\ThemingController; use OCA\Theming\Settings\Admin; -use OCA\Theming\Settings\Section; +use OCA\Theming\Settings\PersonalSection; use OCA\Theming\ThemingDefaults; use OCA\Theming\Util; use OCP\AppFramework\App; +use OCP\AppFramework\IAppContainer; use OCP\Capabilities\ICapability; use OCP\IL10N; use OCP\Settings\IIconSection; @@ -46,11 +28,9 @@ use Test\TestCase; * @package OCA\Theming\Tests */ class ServicesTest extends TestCase { - /** @var \OCA\Activity\AppInfo\Application */ - protected $app; + protected App $app; - /** @var \OCP\AppFramework\IAppContainer */ - protected $container; + protected IAppContainer $container; protected function setUp(): void { parent::setUp(); @@ -58,7 +38,7 @@ class ServicesTest extends TestCase { $this->container = $this->app->getContainer(); } - public function queryData() { + public static function queryData(): array { return [ [IL10N::class], @@ -75,20 +55,16 @@ class ServicesTest extends TestCase { // Settings [Admin::class], [Admin::class, ISettings::class], - [Section::class], - [Section::class, IIconSection::class], + [PersonalSection::class], + [PersonalSection::class, IIconSection::class], ]; } - /** - * @dataProvider queryData - * @param string $service - * @param string $expected - */ - public function testContainerQuery($service, $expected = null) { + #[\PHPUnit\Framework\Attributes\DataProvider('queryData')] + public function testContainerQuery(string $service, ?string $expected = null): void { if ($expected === null) { $expected = $service; } - $this->assertTrue($this->container->query($service) instanceof $expected); + $this->assertInstanceOf($expected, $this->container->query($service)); } } diff --git a/apps/theming/tests/Settings/AdminSectionTest.php b/apps/theming/tests/Settings/AdminSectionTest.php new file mode 100644 index 00000000000..ecb889f264b --- /dev/null +++ b/apps/theming/tests/Settings/AdminSectionTest.php @@ -0,0 +1,60 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Theming\Tests\Settings; + +use OCA\Theming\AppInfo\Application; +use OCA\Theming\Settings\AdminSection; +use OCP\IL10N; +use OCP\IURLGenerator; +use PHPUnit\Framework\MockObject\MockObject; +use Test\TestCase; + +class AdminSectionTest extends TestCase { + private IURLGenerator&MockObject $url; + private IL10N&MockObject $l; + private AdminSection $section; + + protected function setUp(): void { + parent::setUp(); + $this->url = $this->createMock(IURLGenerator::class); + $this->l = $this->createMock(IL10N::class); + + $this->section = new AdminSection( + Application::APP_ID, + $this->url, + $this->l + ); + } + + public function testGetID(): void { + $this->assertSame('theming', $this->section->getID()); + } + + public function testGetName(): void { + $this->l + ->expects($this->once()) + ->method('t') + ->with('Theming') + ->willReturn('Theming'); + + $this->assertSame('Theming', $this->section->getName()); + } + + public function testGetPriority(): void { + $this->assertSame(30, $this->section->getPriority()); + } + + public function testGetIcon(): void { + $this->url->expects($this->once()) + ->method('imagePath') + ->with('theming', 'app-dark.svg') + ->willReturn('icon'); + + $this->assertSame('icon', $this->section->getIcon()); + } +} diff --git a/apps/theming/tests/Settings/AdminTest.php b/apps/theming/tests/Settings/AdminTest.php index e5de7093f8f..277b94900a8 100644 --- a/apps/theming/tests/Settings/AdminTest.php +++ b/apps/theming/tests/Settings/AdminTest.php @@ -1,74 +1,58 @@ <?php + +declare(strict_types=1); /** - * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Jan-Christoph Borchardt <hey@jancborchardt.net> - * @author Julius Härtl <jus@bitgrid.net> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ - namespace OCA\Theming\Tests\Settings; +use OCA\Theming\AppInfo\Application; use OCA\Theming\ImageManager; use OCA\Theming\Settings\Admin; use OCA\Theming\ThemingDefaults; use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Services\IInitialState; use OCP\IConfig; use OCP\IL10N; +use OCP\INavigationManager; use OCP\IURLGenerator; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class AdminTest extends TestCase { - /** @var Admin */ - private $admin; - /** @var IConfig */ - private $config; - /** @var ThemingDefaults */ - private $themingDefaults; - /** @var IURLGenerator */ - private $urlGenerator; - /** @var ImageManager */ - private $imageManager; - /** @var IL10N */ - private $l10n; + private Admin $admin; + private IConfig&MockObject $config; + private ThemingDefaults&MockObject $themingDefaults; + private IInitialState&MockObject $initialState; + private IURLGenerator&MockObject $urlGenerator; + private ImageManager&MockObject $imageManager; + private IL10N&MockObject $l10n; + private INavigationManager&MockObject $navigationManager; protected function setUp(): void { parent::setUp(); $this->config = $this->createMock(IConfig::class); $this->l10n = $this->createMock(IL10N::class); $this->themingDefaults = $this->createMock(ThemingDefaults::class); + $this->initialState = $this->createMock(IInitialState::class); $this->urlGenerator = $this->createMock(IURLGenerator::class); $this->imageManager = $this->createMock(ImageManager::class); + $this->navigationManager = $this->createMock(INavigationManager::class); $this->admin = new Admin( + Application::APP_ID, $this->config, $this->l10n, $this->themingDefaults, + $this->initialState, $this->urlGenerator, - $this->imageManager + $this->imageManager, + $this->navigationManager, ); } - public function testGetFormNoErrors() { + public function testGetFormNoErrors(): void { $this->config ->expects($this->once()) ->method('getSystemValue') @@ -96,33 +80,14 @@ class AdminTest extends TestCase { ->willReturn('MySlogan'); $this->themingDefaults ->expects($this->once()) - ->method('getColorPrimary') + ->method('getDefaultColorPrimary') ->willReturn('#fff'); - $this->urlGenerator - ->expects($this->once()) - ->method('linkToRoute') - ->with('theming.Theming.uploadImage') - ->willReturn('/my/route'); - $params = [ - 'themable' => true, - 'errorMessage' => '', - 'name' => 'MyEntity', - 'url' => 'https://example.com', - 'slogan' => 'MySlogan', - 'color' => '#fff', - 'uploadLogoRoute' => '/my/route', - 'canThemeIcons' => null, - 'iconDocs' => null, - 'images' => [], - 'imprintUrl' => '', - 'privacyUrl' => '', - ]; - $expected = new TemplateResponse('theming', 'settings-admin', $params, ''); + $expected = new TemplateResponse('theming', 'settings-admin'); $this->assertEquals($expected, $this->admin->getForm()); } - public function testGetFormWithErrors() { + public function testGetFormWithErrors(): void { $this->config ->expects($this->once()) ->method('getSystemValue') @@ -155,37 +120,18 @@ class AdminTest extends TestCase { ->willReturn('MySlogan'); $this->themingDefaults ->expects($this->once()) - ->method('getColorPrimary') + ->method('getDefaultColorPrimary') ->willReturn('#fff'); - $this->urlGenerator - ->expects($this->once()) - ->method('linkToRoute') - ->with('theming.Theming.uploadImage') - ->willReturn('/my/route'); - $params = [ - 'themable' => false, - 'errorMessage' => 'You are already using a custom theme. Theming app settings might be overwritten by that.', - 'name' => 'MyEntity', - 'url' => 'https://example.com', - 'slogan' => 'MySlogan', - 'color' => '#fff', - 'uploadLogoRoute' => '/my/route', - 'canThemeIcons' => null, - 'iconDocs' => '', - 'images' => [], - 'imprintUrl' => '', - 'privacyUrl' => '', - ]; - $expected = new TemplateResponse('theming', 'settings-admin', $params, ''); + $expected = new TemplateResponse('theming', 'settings-admin'); $this->assertEquals($expected, $this->admin->getForm()); } - public function testGetSection() { + public function testGetSection(): void { $this->assertSame('theming', $this->admin->getSection()); } - public function testGetPriority() { + public function testGetPriority(): void { $this->assertSame(5, $this->admin->getPriority()); } } diff --git a/apps/theming/tests/Settings/PersonalTest.php b/apps/theming/tests/Settings/PersonalTest.php new file mode 100644 index 00000000000..9216450ec9c --- /dev/null +++ b/apps/theming/tests/Settings/PersonalTest.php @@ -0,0 +1,236 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Theming\Tests\Settings; + +use OCA\Theming\AppInfo\Application; +use OCA\Theming\ImageManager; +use OCA\Theming\ITheme; +use OCA\Theming\Service\BackgroundService; +use OCA\Theming\Service\ThemesService; +use OCA\Theming\Settings\Personal; +use OCA\Theming\Themes\DarkHighContrastTheme; +use OCA\Theming\Themes\DarkTheme; +use OCA\Theming\Themes\DefaultTheme; +use OCA\Theming\Themes\DyslexiaFont; +use OCA\Theming\Themes\HighContrastTheme; +use OCA\Theming\Themes\LightTheme; +use OCA\Theming\ThemingDefaults; +use OCA\Theming\Util; +use OCP\App\IAppManager; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Services\IInitialState; +use OCP\IConfig; +use OCP\IL10N; +use OCP\INavigationManager; +use OCP\IURLGenerator; +use OCP\IUserSession; +use PHPUnit\Framework\MockObject\MockObject; +use Test\TestCase; + +class PersonalTest extends TestCase { + private IConfig&MockObject $config; + private ThemesService&MockObject $themesService; + private IInitialState&MockObject $initialStateService; + private ThemingDefaults&MockObject $themingDefaults; + private INavigationManager&MockObject $navigationManager; + private Personal $admin; + + /** @var ITheme[] */ + private array $themes; + + protected function setUp(): void { + parent::setUp(); + $this->config = $this->createMock(IConfig::class); + $this->themesService = $this->createMock(ThemesService::class); + $this->initialStateService = $this->createMock(IInitialState::class); + $this->themingDefaults = $this->createMock(ThemingDefaults::class); + $this->navigationManager = $this->createMock(INavigationManager::class); + + $this->initThemes(); + + $this->themesService + ->expects($this->any()) + ->method('getThemes') + ->willReturn($this->themes); + + $this->admin = new Personal( + Application::APP_ID, + 'admin', + $this->config, + $this->themesService, + $this->initialStateService, + $this->themingDefaults, + $this->navigationManager, + ); + } + + public function dataTestGetForm(): array { + return [ + ['', [ + $this->formatThemeForm('default'), + $this->formatThemeForm('light'), + $this->formatThemeForm('dark'), + $this->formatThemeForm('light-highcontrast'), + $this->formatThemeForm('dark-highcontrast'), + $this->formatThemeForm('opendyslexic'), + ]], + ['dark', [ + $this->formatThemeForm('dark'), + $this->formatThemeForm('opendyslexic'), + ]], + ]; + } + + /** + * @param string[] $enabledThemes + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestGetForm')] + public function testGetForm(string $enforcedTheme, array $themesState): void { + $this->config->expects($this->once()) + ->method('getSystemValueString') + ->with('enforce_theme', '') + ->willReturn($enforcedTheme); + + $this->config->expects($this->any()) + ->method('getUserValue') + ->willReturnMap([ + ['admin', 'core', 'apporder', '[]', '[]'], + ['admin', 'theming', 'background_image', BackgroundService::BACKGROUND_DEFAULT], + ]); + + $this->navigationManager->expects($this->once()) + ->method('getDefaultEntryIdForUser') + ->willReturn('forced_id'); + + $this->initialStateService->expects($this->exactly(8)) + ->method('provideInitialState') + ->willReturnMap([ + ['shippedBackgrounds', BackgroundService::SHIPPED_BACKGROUNDS], + ['themingDefaults'], + ['enableBlurFilter', ''], + ['userBackgroundImage'], + ['themes', $themesState], + ['enforceTheme', $enforcedTheme], + ['isUserThemingDisabled', false], + ['navigationBar', ['userAppOrder' => [], 'enforcedDefaultApp' => 'forced_id']], + ]); + + $expected = new TemplateResponse('theming', 'settings-personal'); + $this->assertEquals($expected, $this->admin->getForm()); + } + + public function testGetSection(): void { + $this->assertSame('theming', $this->admin->getSection()); + } + + public function testGetPriority(): void { + $this->assertSame(40, $this->admin->getPriority()); + } + + private function initThemes() { + $util = $this->createMock(Util::class); + $themingDefaults = $this->createMock(ThemingDefaults::class); + $userSession = $this->createMock(IUserSession::class); + $urlGenerator = $this->createMock(IURLGenerator::class); + $imageManager = $this->createMock(ImageManager::class); + $config = $this->createMock(IConfig::class); + $l10n = $this->createMock(IL10N::class); + $appManager = $this->createMock(IAppManager::class); + + $themingDefaults->expects($this->any()) + ->method('getColorPrimary') + ->willReturn('#0082c9'); + + $themingDefaults->expects($this->any()) + ->method('getDefaultColorPrimary') + ->willReturn('#0082c9'); + + $this->themes = [ + 'default' => new DefaultTheme( + $util, + $themingDefaults, + $userSession, + $urlGenerator, + $imageManager, + $config, + $l10n, + $appManager, + null, + ), + 'light' => new LightTheme( + $util, + $themingDefaults, + $userSession, + $urlGenerator, + $imageManager, + $config, + $l10n, + $appManager, + null, + ), + 'dark' => new DarkTheme( + $util, + $themingDefaults, + $userSession, + $urlGenerator, + $imageManager, + $config, + $l10n, + $appManager, + null, + ), + 'light-highcontrast' => new HighContrastTheme( + $util, + $themingDefaults, + $userSession, + $urlGenerator, + $imageManager, + $config, + $l10n, + $appManager, + null, + ), + 'dark-highcontrast' => new DarkHighContrastTheme( + $util, + $themingDefaults, + $userSession, + $urlGenerator, + $imageManager, + $config, + $l10n, + $appManager, + null, + ), + 'opendyslexic' => new DyslexiaFont( + $util, + $themingDefaults, + $userSession, + $urlGenerator, + $imageManager, + $config, + $l10n, + $appManager, + null, + ), + ]; + } + + private function formatThemeForm(string $themeId): array { + $this->initThemes(); + + $theme = $this->themes[$themeId]; + return [ + 'id' => $theme->getId(), + 'type' => $theme->getType(), + 'title' => $theme->getTitle(), + 'enableLabel' => $theme->getEnableLabel(), + 'description' => $theme->getDescription(), + 'enabled' => false, + ]; + } +} diff --git a/apps/theming/tests/Settings/SectionTest.php b/apps/theming/tests/Settings/SectionTest.php deleted file mode 100644 index 81fb3f07409..00000000000 --- a/apps/theming/tests/Settings/SectionTest.php +++ /dev/null @@ -1,77 +0,0 @@ -<?php -/** - * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch> - * - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - */ - -namespace OCA\Theming\Tests\Settings; - -use OCA\Theming\Settings\Section; -use OCP\IL10N; -use OCP\IURLGenerator; -use Test\TestCase; - -class SectionTest extends TestCase { - /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ - private $url; - /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ - private $l; - /** @var Section */ - private $section; - - protected function setUp(): void { - parent::setUp(); - $this->url = $this->createMock(IURLGenerator::class); - $this->l = $this->createMock(IL10N::class); - - $this->section = new Section( - $this->url, - $this->l - ); - } - - public function testGetID() { - $this->assertSame('theming', $this->section->getID()); - } - - public function testGetName() { - $this->l - ->expects($this->once()) - ->method('t') - ->with('Theming') - ->willReturn('Theming'); - - $this->assertSame('Theming', $this->section->getName()); - } - - public function testGetPriority() { - $this->assertSame(30, $this->section->getPriority()); - } - - public function testGetIcon() { - $this->url->expects($this->once()) - ->method('imagePath') - ->with('theming', 'app-dark.svg') - ->willReturn('icon'); - - $this->assertSame('icon', $this->section->getIcon()); - } -} diff --git a/apps/theming/tests/Themes/AccessibleThemeTestCase.php b/apps/theming/tests/Themes/AccessibleThemeTestCase.php new file mode 100644 index 00000000000..f516e1f5116 --- /dev/null +++ b/apps/theming/tests/Themes/AccessibleThemeTestCase.php @@ -0,0 +1,172 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Theming\Tests\Themes; + +use OCA\Theming\ITheme; +use OCA\Theming\Util; +use Test\TestCase; + +class AccessibleThemeTestCase extends TestCase { + protected ITheme $theme; + protected Util $util; + + /** + * Set to true to check for WCAG AAA level accessibility + */ + protected static bool $WCAGaaa = false; + + public static function dataAccessibilityPairs(): array { + $textContrast = self::$WCAGaaa ? 7.0 : 4.5; + $elementContrast = 3.0; + + return [ + 'primary-element on background' => [ + [ + '--color-primary-element', + '--color-primary-element-hover', + ], + [ + '--color-main-background', + '--color-background-hover', + '--color-background-dark', + '--color-background-darker', + '--color-main-background-blur', + ], + $elementContrast, + ], + 'status color elements on background' => [ + [ + '--color-error', + '--color-error-hover', + '--color-warning', + '--color-warning-hover', + '--color-info', + '--color-info-hover', + '--color-success', + '--color-success-hover', + '--color-favorite', + ], + [ + '--color-main-background', + '--color-background-hover', + '--color-background-dark', + '--color-background-darker', + '--color-main-background-blur', + ], + $elementContrast, + ], + 'border-colors' => [ + [ + '--color-border-maxcontrast', + ], + [ + '--color-main-background', + '--color-background-hover', + '--color-background-dark', + '--color-main-background-blur', + ], + $elementContrast, + ], + // Those two colors are used for borders which will be `color-main-text` on focussed state, thus need 3:1 contrast to it + 'success-error-border-colors' => [ + [ + '--color-error', + '--color-success', + ], + [ + '--color-main-text', + ], + $elementContrast, + ], + 'primary-element-text' => [ + [ + '--color-primary-element-text', + '--color-primary-element-text-dark', + ], + [ + '--color-primary-element', + '--color-primary-element-hover', + ], + $textContrast, + ], + 'primary-element-light-text' => [ + ['--color-primary-element-light-text'], + [ + '--color-primary-element-light', + '--color-primary-element-light-hover', + ], + $textContrast, + ], + 'main-text' => [ + ['--color-main-text'], + [ + '--color-main-background', + '--color-background-hover', + '--color-background-dark', + '--color-background-darker', + '--color-main-background-blur', + ], + $textContrast, + ], + 'max-contrast-text' => [ + ['--color-text-maxcontrast'], + [ + '--color-main-background', + '--color-background-hover', + '--color-background-dark', + ], + $textContrast, + ], + 'max-contrast text-on blur' => [ + ['--color-text-maxcontrast-background-blur'], + [ + '--color-main-background-blur', + ], + $textContrast, + ], + 'status-text' => [ + [ + '--color-error-text', + '--color-warning-text', + '--color-success-text', + '--color-info-text', + ], + [ + '--color-main-background', + '--color-background-hover', + '--color-background-dark', + '--color-main-background-blur', + ], + $textContrast, + ], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataAccessibilityPairs')] + public function testAccessibilityOfVariables(array $mainColors, array $backgroundColors, float $minContrast): void { + if (!isset($this->theme)) { + $this->markTestSkipped('You need to setup $this->theme in your setUp function'); + } elseif (!isset($this->util)) { + $this->markTestSkipped('You need to setup $this->util in your setUp function'); + } + + $variables = $this->theme->getCSSVariables(); + + // Blur effect does not work so we mockup the color - worst supported case is the default "clouds" background image (on dark themes the clouds with white color are bad on bright themes the primary color as sky is bad) + $variables['--color-main-background-blur'] = $this->util->mix($variables['--color-main-background'], $this->util->isBrightColor($variables['--color-main-background']) ? '#000000' : '#ffffff', 75); + + foreach ($backgroundColors as $background) { + $this->assertStringStartsWith('#', $variables[$background], 'Is not a plain color variable - consider to remove or fix this test'); + foreach ($mainColors as $main) { + $this->assertStringStartsWith('#', $variables[$main], 'Is not a plain color variable - consider to remove or fix this test'); + $realContrast = $this->util->colorContrast($variables[$main], $variables[$background]); + $this->assertGreaterThanOrEqual($minContrast, $realContrast, "Contrast is not high enough for $main (" . $variables[$main] . ") on $background (" . $variables[$background] . ')'); + } + } + } +} diff --git a/apps/theming/tests/Themes/DarkHighContrastThemeTest.php b/apps/theming/tests/Themes/DarkHighContrastThemeTest.php new file mode 100644 index 00000000000..d03e8b13300 --- /dev/null +++ b/apps/theming/tests/Themes/DarkHighContrastThemeTest.php @@ -0,0 +1,130 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Theming\Tests\Themes; + +use OCA\Theming\AppInfo\Application; +use OCA\Theming\ImageManager; +use OCA\Theming\ITheme; +use OCA\Theming\Service\BackgroundService; +use OCA\Theming\Themes\DarkHighContrastTheme; +use OCA\Theming\ThemingDefaults; +use OCA\Theming\Util; +use OCP\App\IAppManager; +use OCP\Files\IAppData; +use OCP\IConfig; +use OCP\IL10N; +use OCP\IURLGenerator; +use OCP\IUserSession; +use OCP\ServerVersion; +use PHPUnit\Framework\MockObject\MockObject; + +class DarkHighContrastThemeTest extends AccessibleThemeTestCase { + private ThemingDefaults&MockObject $themingDefaults; + private IUserSession&MockObject $userSession; + private IURLGenerator&MockObject $urlGenerator; + private ImageManager&MockObject $imageManager; + private IConfig&MockObject $config; + private IL10N&MockObject $l10n; + private IAppManager&MockObject $appManager; + + // !! important: Enable WCAG AAA tests + protected static bool $WCAGaaa = true; + + protected function setUp(): void { + $this->themingDefaults = $this->createMock(ThemingDefaults::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->imageManager = $this->createMock(ImageManager::class); + $this->config = $this->createMock(IConfig::class); + $this->l10n = $this->createMock(IL10N::class); + $this->appManager = $this->createMock(IAppManager::class); + + $this->util = new Util( + $this->createMock(ServerVersion::class), + $this->config, + $this->appManager, + $this->createMock(IAppData::class), + $this->imageManager + ); + + $this->themingDefaults + ->expects($this->any()) + ->method('getColorPrimary') + ->willReturn('#0082c9'); + + $this->themingDefaults + ->expects($this->any()) + ->method('getDefaultColorPrimary') + ->willReturn('#0082c9'); + $this->themingDefaults + ->expects($this->any()) + ->method('getColorBackground') + ->willReturn('#0082c9'); + $this->themingDefaults + ->expects($this->any()) + ->method('getDefaultColorBackground') + ->willReturn('#0082c9'); + + $this->themingDefaults + ->expects($this->any()) + ->method('getBackground') + ->willReturn('/apps/' . Application::APP_ID . '/img/background/' . BackgroundService::DEFAULT_BACKGROUND_IMAGE); + + $this->l10n + ->expects($this->any()) + ->method('t') + ->willReturnCallback(function ($text, $parameters = []) { + return vsprintf($text, $parameters); + }); + + $this->urlGenerator + ->expects($this->any()) + ->method('imagePath') + ->willReturnCallback(function ($app = 'core', $filename = '') { + return "/$app/img/$filename"; + }); + + $this->theme = new DarkHighContrastTheme( + $this->util, + $this->themingDefaults, + $this->userSession, + $this->urlGenerator, + $this->imageManager, + $this->config, + $this->l10n, + $this->appManager, + null, + ); + + parent::setUp(); + } + + + public function testGetId(): void { + $this->assertEquals('dark-highcontrast', $this->theme->getId()); + } + + public function testGetType(): void { + $this->assertEquals(ITheme::TYPE_THEME, $this->theme->getType()); + } + + public function testGetTitle(): void { + $this->assertEquals('Dark theme with high contrast mode', $this->theme->getTitle()); + } + + public function testGetEnableLabel(): void { + $this->assertEquals('Enable dark high contrast mode', $this->theme->getEnableLabel()); + } + + public function testGetDescription(): void { + $this->assertEquals('Similar to the high contrast mode, but with dark colours.', $this->theme->getDescription()); + } + + public function testGetMediaQuery(): void { + $this->assertEquals('(prefers-color-scheme: dark) and (prefers-contrast: more)', $this->theme->getMediaQuery()); + } +} diff --git a/apps/theming/tests/Themes/DarkThemeTest.php b/apps/theming/tests/Themes/DarkThemeTest.php new file mode 100644 index 00000000000..656779b5b24 --- /dev/null +++ b/apps/theming/tests/Themes/DarkThemeTest.php @@ -0,0 +1,132 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Theming\Tests\Themes; + +use OCA\Theming\AppInfo\Application; +use OCA\Theming\ImageManager; +use OCA\Theming\ITheme; +use OCA\Theming\Service\BackgroundService; +use OCA\Theming\Themes\DarkTheme; +use OCA\Theming\ThemingDefaults; +use OCA\Theming\Util; +use OCP\App\IAppManager; +use OCP\Files\IAppData; +use OCP\IConfig; +use OCP\IL10N; +use OCP\IURLGenerator; +use OCP\IUserSession; +use OCP\ServerVersion; +use PHPUnit\Framework\MockObject\MockObject; + +class DarkThemeTest extends AccessibleThemeTestCase { + private ThemingDefaults&MockObject $themingDefaults; + private IUserSession&MockObject $userSession; + private IURLGenerator&MockObject $urlGenerator; + private ImageManager&MockObject $imageManager; + private IConfig&MockObject $config; + private IL10N&MockObject $l10n; + private IAppManager&MockObject $appManager; + + protected function setUp(): void { + $this->themingDefaults = $this->createMock(ThemingDefaults::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->imageManager = $this->createMock(ImageManager::class); + $this->config = $this->createMock(IConfig::class); + $this->l10n = $this->createMock(IL10N::class); + $this->appManager = $this->createMock(IAppManager::class); + + $this->util = new Util( + $this->createMock(ServerVersion::class), + $this->config, + $this->appManager, + $this->createMock(IAppData::class), + $this->imageManager + ); + + $this->themingDefaults + ->expects($this->any()) + ->method('getColorPrimary') + ->willReturn('#0082c9'); + + $this->themingDefaults + ->expects($this->any()) + ->method('getDefaultColorPrimary') + ->willReturn('#0082c9'); + $this->themingDefaults + ->expects($this->any()) + ->method('getColorBackground') + ->willReturn('#0082c9'); + $this->themingDefaults + ->expects($this->any()) + ->method('getDefaultColorBackground') + ->willReturn('#0082c9'); + + $this->themingDefaults + ->expects($this->any()) + ->method('getBackground') + ->willReturn('/apps/' . Application::APP_ID . '/img/background/' . BackgroundService::DEFAULT_BACKGROUND_IMAGE); + + $this->l10n + ->expects($this->any()) + ->method('t') + ->willReturnCallback(function ($text, $parameters = []) { + return vsprintf($text, $parameters); + }); + + $this->urlGenerator + ->expects($this->any()) + ->method('imagePath') + ->willReturnCallback(function ($app = 'core', $filename = '') { + return "/$app/img/$filename"; + }); + + $this->theme = new DarkTheme( + $this->util, + $this->themingDefaults, + $this->userSession, + $this->urlGenerator, + $this->imageManager, + $this->config, + $this->l10n, + $this->appManager, + null, + ); + + parent::setUp(); + } + + + public function testGetId(): void { + $this->assertEquals('dark', $this->theme->getId()); + } + + public function testGetType(): void { + $this->assertEquals(ITheme::TYPE_THEME, $this->theme->getType()); + } + + public function testGetTitle(): void { + $this->assertEquals('Dark theme', $this->theme->getTitle()); + } + + public function testGetEnableLabel(): void { + $this->assertEquals('Enable dark theme', $this->theme->getEnableLabel()); + } + + public function testGetDescription(): void { + $this->assertEquals('A dark theme to ease your eyes by reducing the overall luminosity and brightness.', $this->theme->getDescription()); + } + + public function testGetMediaQuery(): void { + $this->assertEquals('(prefers-color-scheme: dark)', $this->theme->getMediaQuery()); + } + + public function testGetCustomCss(): void { + $this->assertEquals('', $this->theme->getCustomCss()); + } +} diff --git a/apps/theming/tests/Themes/DefaultThemeTest.php b/apps/theming/tests/Themes/DefaultThemeTest.php new file mode 100644 index 00000000000..d2606ffc275 --- /dev/null +++ b/apps/theming/tests/Themes/DefaultThemeTest.php @@ -0,0 +1,157 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Theming\Tests\Themes; + +use OCA\Theming\AppInfo\Application; +use OCA\Theming\ImageManager; +use OCA\Theming\ITheme; +use OCA\Theming\Service\BackgroundService; +use OCA\Theming\Themes\DefaultTheme; +use OCA\Theming\ThemingDefaults; +use OCA\Theming\Util; +use OCP\App\IAppManager; +use OCP\Files\IAppData; +use OCP\IConfig; +use OCP\IL10N; +use OCP\IURLGenerator; +use OCP\IUserSession; +use OCP\ServerVersion; +use PHPUnit\Framework\MockObject\MockObject; + +class DefaultThemeTest extends AccessibleThemeTestCase { + private ThemingDefaults&MockObject $themingDefaults; + private IUserSession&MockObject $userSession; + private IURLGenerator&MockObject $urlGenerator; + private ImageManager&MockObject $imageManager; + private IConfig&MockObject $config; + private IL10N&MockObject $l10n; + private IAppManager&MockObject $appManager; + + protected function setUp(): void { + $this->themingDefaults = $this->createMock(ThemingDefaults::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->imageManager = $this->createMock(ImageManager::class); + $this->config = $this->createMock(IConfig::class); + $this->l10n = $this->createMock(IL10N::class); + $this->appManager = $this->createMock(IAppManager::class); + + $this->util = new Util( + $this->createMock(ServerVersion::class), + $this->config, + $this->appManager, + $this->createMock(IAppData::class), + $this->imageManager + ); + + $defaultBackground = BackgroundService::SHIPPED_BACKGROUNDS[BackgroundService::DEFAULT_BACKGROUND_IMAGE]; + + $this->themingDefaults + ->expects($this->any()) + ->method('getColorPrimary') + ->willReturn($defaultBackground['primary_color']); + + $this->themingDefaults + ->expects($this->any()) + ->method('getColorBackground') + ->willReturn($defaultBackground['background_color']); + + $this->themingDefaults + ->expects($this->any()) + ->method('getDefaultColorPrimary') + ->willReturn($defaultBackground['primary_color']); + + $this->themingDefaults + ->expects($this->any()) + ->method('getDefaultColorBackground') + ->willReturn($defaultBackground['background_color']); + + $this->themingDefaults + ->expects($this->any()) + ->method('getBackground') + ->willReturn('/apps/' . Application::APP_ID . '/img/background/' . BackgroundService::DEFAULT_BACKGROUND_IMAGE); + + $this->l10n + ->expects($this->any()) + ->method('t') + ->willReturnCallback(function ($text, $parameters = []) { + return vsprintf($text, $parameters); + }); + + $this->urlGenerator + ->expects($this->any()) + ->method('imagePath') + ->willReturnCallback(function ($app = 'core', $filename = '') { + return "/$app/img/$filename"; + }); + + $this->theme = new DefaultTheme( + $this->util, + $this->themingDefaults, + $this->userSession, + $this->urlGenerator, + $this->imageManager, + $this->config, + $this->l10n, + $this->appManager, + null, + ); + + parent::setUp(); + } + + + public function testGetId(): void { + $this->assertEquals('default', $this->theme->getId()); + } + + public function testGetType(): void { + $this->assertEquals(ITheme::TYPE_THEME, $this->theme->getType()); + } + + public function testGetTitle(): void { + $this->assertEquals('System default theme', $this->theme->getTitle()); + } + + public function testGetEnableLabel(): void { + $this->assertEquals('Enable the system default', $this->theme->getEnableLabel()); + } + + public function testGetDescription(): void { + $this->assertEquals('Using the default system appearance.', $this->theme->getDescription()); + } + + public function testGetMediaQuery(): void { + $this->assertEquals('', $this->theme->getMediaQuery()); + } + + public function testGetCustomCss(): void { + $this->assertEquals('', $this->theme->getCustomCss()); + } + + /** + * Ensure parity between the default theme and the static generated file + * @see ThemingController.php:313 + */ + public function testThemindDisabledFallbackCss(): void { + // Generate variables + $variables = ''; + foreach ($this->theme->getCSSVariables() as $variable => $value) { + $variables .= " $variable: $value;" . PHP_EOL; + }; + + $css = "\n:root {" . PHP_EOL . "$variables}" . PHP_EOL; + $fallbackCss = file_get_contents(__DIR__ . '/../../css/default.css'); + // Remove comments + $fallbackCss = preg_replace('/\s*\/\*[\s\S]*?\*\//m', '', $fallbackCss); + // Remove blank lines + $fallbackCss = preg_replace('/\s*\n\n/', "\n", $fallbackCss); + + $this->assertEquals($css, $fallbackCss); + } +} diff --git a/apps/theming/tests/Themes/DyslexiaFontTest.php b/apps/theming/tests/Themes/DyslexiaFontTest.php new file mode 100644 index 00000000000..7d56fb4b1be --- /dev/null +++ b/apps/theming/tests/Themes/DyslexiaFontTest.php @@ -0,0 +1,163 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Theming\Tests\Service; + +use OC\Route\Router; +use OC\URLGenerator; +use OCA\Theming\ImageManager; +use OCA\Theming\ITheme; +use OCA\Theming\Themes\DyslexiaFont; +use OCA\Theming\ThemingDefaults; +use OCA\Theming\Util; +use OCP\App\IAppManager; +use OCP\Files\IAppData; +use OCP\ICacheFactory; +use OCP\IConfig; +use OCP\IL10N; +use OCP\IRequest; +use OCP\IURLGenerator; +use OCP\IUserSession; +use OCP\ServerVersion; +use PHPUnit\Framework\MockObject\MockObject; +use Test\TestCase; + +class DyslexiaFontTest extends TestCase { + private ThemingDefaults&MockObject $themingDefaults; + private IUserSession&MockObject $userSession; + private IURLGenerator $urlGenerator; + private ImageManager&MockObject $imageManager; + private IConfig&MockObject $config; + private IL10N&MockObject $l10n; + private IAppManager&MockObject $appManager; + + private DyslexiaFont $dyslexiaFont; + + protected function setUp(): void { + $this->themingDefaults = $this->createMock(ThemingDefaults::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->imageManager = $this->createMock(ImageManager::class); + $this->config = $this->createMock(IConfig::class); + $this->l10n = $this->createMock(IL10N::class); + $this->appManager = $this->createMock(IAppManager::class); + + $util = new Util( + $this->createMock(ServerVersion::class), + $this->config, + $this->appManager, + $this->createMock(IAppData::class), + $this->imageManager + ); + + $userSession = $this->createMock(IUserSession::class); + $cacheFactory = $this->createMock(ICacheFactory::class); + $request = $this->createMock(IRequest::class); + $router = $this->createMock(Router::class); + $this->urlGenerator = new URLGenerator( + $this->config, + $userSession, + $cacheFactory, + $request, + $router + ); + + $this->themingDefaults + ->expects($this->any()) + ->method('getColorPrimary') + ->willReturn('#0082c9'); + + $this->themingDefaults + ->expects($this->any()) + ->method('getDefaultColorPrimary') + ->willReturn('#0082c9'); + + $this->themingDefaults + ->expects($this->any()) + ->method('getColorBackground') + ->willReturn('#0082c9'); + + $this->themingDefaults + ->expects($this->any()) + ->method('getDefaultColorBackground') + ->willReturn('#0082c9'); + + $this->l10n + ->expects($this->any()) + ->method('t') + ->willReturnCallback(function ($text, $parameters = []) { + return vsprintf($text, $parameters); + }); + + $this->dyslexiaFont = new DyslexiaFont( + $util, + $this->themingDefaults, + $this->userSession, + $this->urlGenerator, + $this->imageManager, + $this->config, + $this->l10n, + $this->appManager, + null, + ); + + parent::setUp(); + } + + + public function testGetId(): void { + $this->assertEquals('opendyslexic', $this->dyslexiaFont->getId()); + } + + public function testGetType(): void { + $this->assertEquals(ITheme::TYPE_FONT, $this->dyslexiaFont->getType()); + } + + public function testGetTitle(): void { + $this->assertNotEmpty($this->dyslexiaFont->getTitle()); + } + + public function testGetEnableLabel(): void { + $this->assertNotEmpty($this->dyslexiaFont->getEnableLabel()); + } + + public function testGetDescription(): void { + $this->assertNotEmpty($this->dyslexiaFont->getDescription()); + } + + public function testGetMediaQuery(): void { + $this->assertEquals('', $this->dyslexiaFont->getMediaQuery()); + } + + public function testGetCSSVariables(): void { + $this->assertStringStartsWith('OpenDyslexic', $this->dyslexiaFont->getCSSVariables()['--font-face']); + } + + public static function dataTestGetCustomCss(): array { + return [ + ['', true], + ['', false], + ['/subfolder', true], + ['/subfolder', false], + ]; + } + + /** + * Ensure the fonts are always loaded from the web root + * despite having url rewriting enabled or not + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestGetCustomCss')] + public function testGetCustomCss(string $webRoot, bool $prettyUrlsEnabled): void { + \OC::$WEBROOT = $webRoot; + $this->config->expects($this->any()) + ->method('getSystemValue') + ->with('htaccess.IgnoreFrontController', false) + ->willReturn($prettyUrlsEnabled); + + $this->assertStringContainsString("'$webRoot/apps/theming/fonts/OpenDyslexic-Regular.otf'", $this->dyslexiaFont->getCustomCss()); + $this->assertStringNotContainsString('index.php', $this->dyslexiaFont->getCustomCss()); + } +} diff --git a/apps/theming/tests/Themes/HighContrastThemeTest.php b/apps/theming/tests/Themes/HighContrastThemeTest.php new file mode 100644 index 00000000000..94f87d7433b --- /dev/null +++ b/apps/theming/tests/Themes/HighContrastThemeTest.php @@ -0,0 +1,131 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Theming\Tests\Themes; + +use OCA\Theming\AppInfo\Application; +use OCA\Theming\ImageManager; +use OCA\Theming\ITheme; +use OCA\Theming\Service\BackgroundService; +use OCA\Theming\Themes\HighContrastTheme; +use OCA\Theming\ThemingDefaults; +use OCA\Theming\Util; +use OCP\App\IAppManager; +use OCP\Files\IAppData; +use OCP\IConfig; +use OCP\IL10N; +use OCP\IURLGenerator; +use OCP\IUserSession; +use OCP\ServerVersion; +use PHPUnit\Framework\MockObject\MockObject; + +class HighContrastThemeTest extends AccessibleThemeTestCase { + private ThemingDefaults&MockObject $themingDefaults; + private IUserSession&MockObject $userSession; + private IURLGenerator&MockObject $urlGenerator; + private ImageManager&MockObject $imageManager; + private IConfig&MockObject $config; + private IL10N&MockObject $l10n; + private IAppManager&MockObject $appManager; + + // !! important: Enable WCAG AAA tests + protected static bool $WCAGaaa = true; + + protected function setUp(): void { + $this->themingDefaults = $this->createMock(ThemingDefaults::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->imageManager = $this->createMock(ImageManager::class); + $this->config = $this->createMock(IConfig::class); + $this->l10n = $this->createMock(IL10N::class); + $this->appManager = $this->createMock(IAppManager::class); + + $this->util = new Util( + $this->createMock(ServerVersion::class), + $this->config, + $this->appManager, + $this->createMock(IAppData::class), + $this->imageManager + ); + + $this->themingDefaults + ->expects($this->any()) + ->method('getColorPrimary') + ->willReturn('#0082c9'); + + $this->themingDefaults + ->expects($this->any()) + ->method('getDefaultColorPrimary') + ->willReturn('#0082c9'); + $this->themingDefaults + ->expects($this->any()) + ->method('getColorBackground') + ->willReturn('#0082c9'); + $this->themingDefaults + ->expects($this->any()) + ->method('getDefaultColorBackground') + ->willReturn('#0082c9'); + + $this->themingDefaults + ->expects($this->any()) + ->method('getBackground') + ->willReturn('/apps/' . Application::APP_ID . '/img/background/' . BackgroundService::DEFAULT_BACKGROUND_IMAGE); + + $this->l10n + ->expects($this->any()) + ->method('t') + ->willReturnCallback(function ($text, $parameters = []) { + return vsprintf($text, $parameters); + }); + + $this->urlGenerator + ->expects($this->any()) + ->method('imagePath') + ->willReturnCallback(function ($app = 'core', $filename = '') { + return "/$app/img/$filename"; + }); + + $this->theme = new HighContrastTheme( + $this->util, + $this->themingDefaults, + $this->userSession, + $this->urlGenerator, + $this->imageManager, + $this->config, + $this->l10n, + $this->appManager, + null, + ); + + parent::setUp(); + } + + + public function testGetId(): void { + $this->assertEquals('light-highcontrast', $this->theme->getId()); + } + + public function testGetType(): void { + $this->assertEquals(ITheme::TYPE_THEME, $this->theme->getType()); + } + + public function testGetTitle(): void { + $this->assertEquals('High contrast mode', $this->theme->getTitle()); + } + + public function testGetEnableLabel(): void { + $this->assertEquals('Enable high contrast mode', $this->theme->getEnableLabel()); + } + + public function testGetDescription(): void { + $this->assertEquals('A high contrast mode to ease your navigation. Visual quality will be reduced but clarity will be increased.', $this->theme->getDescription()); + } + + public function testGetMediaQuery(): void { + $this->assertEquals('(prefers-contrast: more)', $this->theme->getMediaQuery()); + } +} diff --git a/apps/theming/tests/ThemingDefaultsTest.php b/apps/theming/tests/ThemingDefaultsTest.php index 4db5dad2c0d..1acd12f12fa 100644 --- a/apps/theming/tests/ThemingDefaultsTest.php +++ b/apps/theming/tests/ThemingDefaultsTest.php @@ -1,84 +1,52 @@ <?php + +declare(strict_types=1); /** - * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Guillaume COMPAGNON <gcompagnon@outlook.com> - * @author Jan-Christoph Borchardt <hey@jancborchardt.net> - * @author Joas Schilling <coding@schilljs.com> - * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> - * @author Julius Haertl <jus@bitgrid.net> - * @author Julius Härtl <jus@bitgrid.net> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Michael Weimann <mail@michael-weimann.eu> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ - namespace OCA\Theming\Tests; use OCA\Theming\ImageManager; +use OCA\Theming\Service\BackgroundService; use OCA\Theming\ThemingDefaults; use OCA\Theming\Util; use OCP\App\IAppManager; -use OCP\Files\IAppData; use OCP\Files\NotFoundException; +use OCP\IAppConfig; use OCP\ICache; use OCP\ICacheFactory; use OCP\IConfig; use OCP\IL10N; use OCP\INavigationManager; use OCP\IURLGenerator; +use OCP\IUser; +use OCP\IUserSession; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class ThemingDefaultsTest extends TestCase { - /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ - private $config; - /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ - private $l10n; - /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ - private $urlGenerator; - /** @var \OC_Defaults|\PHPUnit\Framework\MockObject\MockObject */ - private $defaults; - /** @var IAppData|\PHPUnit\Framework\MockObject\MockObject */ - private $appData; - /** @var ICacheFactory|\PHPUnit\Framework\MockObject\MockObject */ - private $cacheFactory; - /** @var ThemingDefaults */ - private $template; - /** @var Util|\PHPUnit\Framework\MockObject\MockObject */ - private $util; - /** @var ICache|\PHPUnit\Framework\MockObject\MockObject */ - private $cache; - /** @var IAppManager|\PHPUnit\Framework\MockObject\MockObject */ - private $appManager; - /** @var ImageManager|\PHPUnit\Framework\MockObject\MockObject */ - private $imageManager; - /** @var INavigationManager|\PHPUnit\Framework\MockObject\MockObject */ - private $navigationManager; + private IAppConfig&MockObject $appConfig; + private IConfig&MockObject $config; + private \OC_Defaults $defaults; + private IL10N|MockObject $l10n; + private IUserSession&MockObject $userSession; + private IURLGenerator&MockObject $urlGenerator; + private ICacheFactory&MockObject $cacheFactory; + private Util&MockObject $util; + private ICache&MockObject $cache; + private IAppManager&MockObject $appManager; + private ImageManager&MockObject $imageManager; + private INavigationManager&MockObject $navigationManager; + private BackgroundService&MockObject $backgroundService; + private ThemingDefaults $template; protected function setUp(): void { parent::setUp(); + $this->appConfig = $this->createMock(IAppConfig::class); $this->config = $this->createMock(IConfig::class); $this->l10n = $this->createMock(IL10N::class); + $this->userSession = $this->createMock(IUserSession::class); $this->urlGenerator = $this->createMock(IURLGenerator::class); $this->cacheFactory = $this->createMock(ICacheFactory::class); $this->cache = $this->createMock(ICache::class); @@ -86,6 +54,7 @@ class ThemingDefaultsTest extends TestCase { $this->imageManager = $this->createMock(ImageManager::class); $this->appManager = $this->createMock(IAppManager::class); $this->navigationManager = $this->createMock(INavigationManager::class); + $this->backgroundService = $this->createMock(BackgroundService::class); $this->defaults = new \OC_Defaults(); $this->urlGenerator ->expects($this->any()) @@ -93,17 +62,20 @@ class ThemingDefaultsTest extends TestCase { ->willReturn(''); $this->template = new ThemingDefaults( $this->config, + $this->appConfig, $this->l10n, + $this->userSession, $this->urlGenerator, $this->cacheFactory, $this->util, $this->imageManager, $this->appManager, - $this->navigationManager + $this->navigationManager, + $this->backgroundService, ); } - public function testGetNameWithDefault() { + public function testGetNameWithDefault(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -113,7 +85,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('Nextcloud', $this->template->getName()); } - public function testGetNameWithCustom() { + public function testGetNameWithCustom(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -123,7 +95,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('MyCustomCloud', $this->template->getName()); } - public function testGetHTMLNameWithDefault() { + public function testGetHTMLNameWithDefault(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -133,7 +105,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('Nextcloud', $this->template->getHTMLName()); } - public function testGetHTMLNameWithCustom() { + public function testGetHTMLNameWithCustom(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -143,7 +115,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('MyCustomCloud', $this->template->getHTMLName()); } - public function testGetTitleWithDefault() { + public function testGetTitleWithDefault(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -153,7 +125,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('Nextcloud', $this->template->getTitle()); } - public function testGetTitleWithCustom() { + public function testGetTitleWithCustom(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -164,7 +136,7 @@ class ThemingDefaultsTest extends TestCase { } - public function testGetEntityWithDefault() { + public function testGetEntityWithDefault(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -174,7 +146,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('Nextcloud', $this->template->getEntity()); } - public function testGetEntityWithCustom() { + public function testGetEntityWithCustom(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -184,7 +156,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('MyCustomCloud', $this->template->getEntity()); } - public function testGetBaseUrlWithDefault() { + public function testGetBaseUrlWithDefault(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -194,7 +166,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals($this->defaults->getBaseUrl(), $this->template->getBaseUrl()); } - public function testGetBaseUrlWithCustom() { + public function testGetBaseUrlWithCustom(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -204,18 +176,15 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('https://example.com/', $this->template->getBaseUrl()); } - public function legalUrlProvider() { + public static function legalUrlProvider(): array { return [ - [ '' ], - [ 'https://example.com/legal.html'] + [''], + ['https://example.com/legal.html'], ]; } - /** - * @param $imprintUrl - * @dataProvider legalUrlProvider - */ - public function testGetImprintURL($imprintUrl) { + #[\PHPUnit\Framework\Attributes\DataProvider('legalUrlProvider')] + public function testGetImprintURL(string $imprintUrl): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -225,11 +194,8 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals($imprintUrl, $this->template->getImprintUrl()); } - /** - * @param $privacyUrl - * @dataProvider legalUrlProvider - */ - public function testGetPrivacyURL($privacyUrl) { + #[\PHPUnit\Framework\Attributes\DataProvider('legalUrlProvider')] + public function testGetPrivacyURL(string $privacyUrl): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -239,7 +205,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals($privacyUrl, $this->template->getPrivacyUrl()); } - public function testGetSloganWithDefault() { + public function testGetSloganWithDefault(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -249,7 +215,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals($this->defaults->getSlogan(), $this->template->getSlogan()); } - public function testGetSloganWithCustom() { + public function testGetSloganWithCustom(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -259,7 +225,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('My custom Slogan', $this->template->getSlogan()); } - public function testGetShortFooter() { + public function testGetShortFooter(): void { $this->config ->expects($this->exactly(5)) ->method('getAppValue') @@ -274,7 +240,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('<a href="url" target="_blank" rel="noreferrer noopener" class="entity-name">Name</a> – Slogan', $this->template->getShortFooter()); } - public function testGetShortFooterEmptyUrl() { + public function testGetShortFooterEmptyUrl(): void { $this->navigationManager->expects($this->once())->method('getAll')->with(INavigationManager::TYPE_GUEST)->willReturn([]); $this->config ->expects($this->exactly(5)) @@ -290,7 +256,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('<span class="entity-name">Name</span> – Slogan', $this->template->getShortFooter()); } - public function testGetShortFooterEmptySlogan() { + public function testGetShortFooterEmptySlogan(): void { $this->navigationManager->expects($this->once())->method('getAll')->with(INavigationManager::TYPE_GUEST)->willReturn([]); $this->config ->expects($this->exactly(5)) @@ -306,7 +272,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('<a href="url" target="_blank" rel="noreferrer noopener" class="entity-name">Name</a>', $this->template->getShortFooter()); } - public function testGetShortFooterImprint() { + public function testGetShortFooterImprint(): void { $this->navigationManager->expects($this->once())->method('getAll')->with(INavigationManager::TYPE_GUEST)->willReturn([]); $this->config ->expects($this->exactly(5)) @@ -324,10 +290,10 @@ class ThemingDefaultsTest extends TestCase { ->method('t') ->willReturnArgument(0); - $this->assertEquals('<a href="url" target="_blank" rel="noreferrer noopener" class="entity-name">Name</a> – Slogan<br/><a href="https://example.com/imprint" class="legal" target="_blank" rel="noreferrer noopener">Legal notice</a>', $this->template->getShortFooter()); + $this->assertEquals('<a href="url" target="_blank" rel="noreferrer noopener" class="entity-name">Name</a> – Slogan<br/><span class="footer__legal-links"><a href="https://example.com/imprint" class="legal" target="_blank" rel="noreferrer noopener">Legal notice</a></span>', $this->template->getShortFooter()); } - public function testGetShortFooterPrivacy() { + public function testGetShortFooterPrivacy(): void { $this->navigationManager->expects($this->once())->method('getAll')->with(INavigationManager::TYPE_GUEST)->willReturn([]); $this->config ->expects($this->exactly(5)) @@ -345,10 +311,10 @@ class ThemingDefaultsTest extends TestCase { ->method('t') ->willReturnArgument(0); - $this->assertEquals('<a href="url" target="_blank" rel="noreferrer noopener" class="entity-name">Name</a> – Slogan<br/><a href="https://example.com/privacy" class="legal" target="_blank" rel="noreferrer noopener">Privacy policy</a>', $this->template->getShortFooter()); + $this->assertEquals('<a href="url" target="_blank" rel="noreferrer noopener" class="entity-name">Name</a> – Slogan<br/><span class="footer__legal-links"><a href="https://example.com/privacy" class="legal" target="_blank" rel="noreferrer noopener">Privacy policy</a></span>', $this->template->getShortFooter()); } - public function testGetShortFooterAllLegalLinks() { + public function testGetShortFooterAllLegalLinks(): void { $this->navigationManager->expects($this->once())->method('getAll')->with(INavigationManager::TYPE_GUEST)->willReturn([]); $this->config ->expects($this->exactly(5)) @@ -366,21 +332,18 @@ class ThemingDefaultsTest extends TestCase { ->method('t') ->willReturnArgument(0); - $this->assertEquals('<a href="url" target="_blank" rel="noreferrer noopener" class="entity-name">Name</a> – Slogan<br/><a href="https://example.com/imprint" class="legal" target="_blank" rel="noreferrer noopener">Legal notice</a> · <a href="https://example.com/privacy" class="legal" target="_blank" rel="noreferrer noopener">Privacy policy</a>', $this->template->getShortFooter()); + $this->assertEquals('<a href="url" target="_blank" rel="noreferrer noopener" class="entity-name">Name</a> – Slogan<br/><span class="footer__legal-links"><a href="https://example.com/imprint" class="legal" target="_blank" rel="noreferrer noopener">Legal notice</a> · <a href="https://example.com/privacy" class="legal" target="_blank" rel="noreferrer noopener">Privacy policy</a></span>', $this->template->getShortFooter()); } - public function invalidLegalUrlProvider() { + public static function invalidLegalUrlProvider(): array { return [ ['example.com/legal'], # missing scheme ['https:///legal'], # missing host ]; } - /** - * @param $invalidImprintUrl - * @dataProvider invalidLegalUrlProvider - */ - public function testGetShortFooterInvalidImprint($invalidImprintUrl) { + #[\PHPUnit\Framework\Attributes\DataProvider('invalidLegalUrlProvider')] + public function testGetShortFooterInvalidImprint(string $invalidImprintUrl): void { $this->navigationManager->expects($this->once())->method('getAll')->with(INavigationManager::TYPE_GUEST)->willReturn([]); $this->config ->expects($this->exactly(5)) @@ -396,11 +359,8 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('<a href="url" target="_blank" rel="noreferrer noopener" class="entity-name">Name</a> – Slogan', $this->template->getShortFooter()); } - /** - * @param $invalidPrivacyUrl - * @dataProvider invalidLegalUrlProvider - */ - public function testGetShortFooterInvalidPrivacy($invalidPrivacyUrl) { + #[\PHPUnit\Framework\Attributes\DataProvider('invalidLegalUrlProvider')] + public function testGetShortFooterInvalidPrivacy(string $invalidPrivacyUrl): void { $this->navigationManager->expects($this->once())->method('getAll')->with(INavigationManager::TYPE_GUEST)->willReturn([]); $this->config ->expects($this->exactly(5)) @@ -416,50 +376,130 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('<a href="url" target="_blank" rel="noreferrer noopener" class="entity-name">Name</a> – Slogan', $this->template->getShortFooter()); } - public function testgetColorPrimaryWithDefault() { - $this->config - ->expects($this->once()) - ->method('getAppValue') - ->with('theming', 'color', $this->defaults->getColorPrimary()) + public function testGetColorPrimaryWithDefault(): void { + $this->appConfig + ->expects(self::once()) + ->method('getValueBool') + ->with('theming', 'disable-user-theming') + ->willReturn(false); + $this->appConfig + ->expects(self::once()) + ->method('getValueString') + ->with('theming', 'primary_color', '') ->willReturn($this->defaults->getColorPrimary()); $this->assertEquals($this->defaults->getColorPrimary(), $this->template->getColorPrimary()); } - public function testgetColorPrimaryWithCustom() { - $this->config - ->expects($this->once()) - ->method('getAppValue') - ->with('theming', 'color', $this->defaults->getColorPrimary()) + public function testGetColorPrimaryWithCustom(): void { + $this->appConfig + ->expects(self::once()) + ->method('getValueBool') + ->with('theming', 'disable-user-theming') + ->willReturn(false); + $this->appConfig + ->expects(self::once()) + ->method('getValueString') + ->with('theming', 'primary_color', '') ->willReturn('#fff'); $this->assertEquals('#fff', $this->template->getColorPrimary()); } - public function testSet() { + public static function dataGetColorPrimary(): array { + return [ + 'with fallback default' => [ + 'disableTheming' => false, + 'primaryColor' => '', + 'userPrimaryColor' => '', + 'expected' => BackgroundService::DEFAULT_COLOR, + ], + 'with custom admin primary' => [ + 'disableTheming' => false, + 'primaryColor' => '#aaa', + 'userPrimaryColor' => '', + 'expected' => '#aaa', + ], + 'with custom invalid admin primary' => [ + 'disableTheming' => false, + 'primaryColor' => 'invalid', + 'userPrimaryColor' => '', + 'expected' => BackgroundService::DEFAULT_COLOR, + ], + 'with custom invalid user primary' => [ + 'disableTheming' => false, + 'primaryColor' => '', + 'userPrimaryColor' => 'invalid-name', + 'expected' => BackgroundService::DEFAULT_COLOR, + ], + 'with custom user primary' => [ + 'disableTheming' => false, + 'primaryColor' => '', + 'userPrimaryColor' => '#bbb', + 'expected' => '#bbb', + ], + 'with disabled user theming primary' => [ + 'disableTheming' => true, + 'primaryColor' => '#aaa', + 'userPrimaryColor' => '#bbb', + 'expected' => '#aaa', + ], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataGetColorPrimary')] + public function testGetColorPrimary(bool $disableTheming, string $primaryColor, string $userPrimaryColor, string $expected): void { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->any()) + ->method('getUser') + ->willReturn($user); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('user'); + $this->appConfig + ->expects(self::any()) + ->method('getValueBool') + ->with('theming', 'disable-user-theming') + ->willReturn($disableTheming); + $this->appConfig + ->expects(self::any()) + ->method('getValueString') + ->with('theming', 'primary_color', '') + ->willReturn($primaryColor); $this->config - ->expects($this->at(0)) + ->expects($this->any()) + ->method('getUserValue') + ->with('user', 'theming', 'primary_color', '') + ->willReturn($userPrimaryColor); + + $this->assertEquals($expected, $this->template->getColorPrimary()); + } + + public function testSet(): void { + $expectedCalls = [ + ['theming', 'MySetting', 'MyValue'], + ['theming', 'cachebuster', 16], + ]; + $i = 0; + $this->config + ->expects($this->exactly(2)) ->method('setAppValue') - ->with('theming', 'MySetting', 'MyValue'); + ->willReturnCallback(function () use ($expectedCalls, &$i): void { + $this->assertEquals($expectedCalls[$i], func_get_args()); + $i++; + }); $this->config - ->expects($this->at(1)) + ->expects($this->once()) ->method('getAppValue') ->with('theming', 'cachebuster', '0') ->willReturn('15'); - $this->config - ->expects($this->at(2)) - ->method('setAppValue') - ->with('theming', 'cachebuster', 16); $this->cacheFactory - ->expects($this->at(0)) + ->expects($this->exactly(2)) ->method('createDistributed') - ->with('theming-') - ->willReturn($this->cache); - $this->cacheFactory - ->expects($this->at(1)) - ->method('createDistributed') - ->with('imagePath') - ->willReturn($this->cache); + ->willReturnMap([ + ['theming-', $this->cache], + ['imagePath', $this->cache], + ]); $this->cache ->expects($this->any()) ->method('clear') @@ -467,117 +507,103 @@ class ThemingDefaultsTest extends TestCase { $this->template->set('MySetting', 'MyValue'); } - public function testUndoName() { + public function testUndoName(): void { $this->config - ->expects($this->at(0)) + ->expects($this->once()) ->method('deleteAppValue') ->with('theming', 'name'); $this->config - ->expects($this->at(1)) + ->expects($this->exactly(2)) ->method('getAppValue') - ->with('theming', 'cachebuster', '0') - ->willReturn('15'); + ->willReturnMap([ + ['theming', 'cachebuster', '0', '15'], + ['theming', 'name', 'Nextcloud', 'Nextcloud'], + ]); $this->config - ->expects($this->at(2)) + ->expects($this->once()) ->method('setAppValue') ->with('theming', 'cachebuster', 16); - $this->config - ->expects($this->at(3)) - ->method('getAppValue') - ->with('theming', 'name', 'Nextcloud') - ->willReturn('Nextcloud'); $this->assertSame('Nextcloud', $this->template->undo('name')); } - public function testUndoBaseUrl() { + public function testUndoBaseUrl(): void { $this->config - ->expects($this->at(0)) + ->expects($this->once()) ->method('deleteAppValue') ->with('theming', 'url'); $this->config - ->expects($this->at(1)) + ->expects($this->exactly(2)) ->method('getAppValue') - ->with('theming', 'cachebuster', '0') - ->willReturn('15'); + ->willReturnMap([ + ['theming', 'cachebuster', '0', '15'], + ['theming', 'url', $this->defaults->getBaseUrl(), $this->defaults->getBaseUrl()], + ]); $this->config - ->expects($this->at(2)) + ->expects($this->once()) ->method('setAppValue') ->with('theming', 'cachebuster', 16); - $this->config - ->expects($this->at(3)) - ->method('getAppValue') - ->with('theming', 'url', $this->defaults->getBaseUrl()) - ->willReturn($this->defaults->getBaseUrl()); $this->assertSame($this->defaults->getBaseUrl(), $this->template->undo('url')); } - public function testUndoSlogan() { + public function testUndoSlogan(): void { $this->config - ->expects($this->at(0)) + ->expects($this->once()) ->method('deleteAppValue') ->with('theming', 'slogan'); $this->config - ->expects($this->at(1)) + ->expects($this->exactly(2)) ->method('getAppValue') - ->with('theming', 'cachebuster', '0') - ->willReturn('15'); + ->willReturnMap([ + ['theming', 'cachebuster', '0', '15'], + ['theming', 'slogan', $this->defaults->getSlogan(), $this->defaults->getSlogan()], + ]); $this->config - ->expects($this->at(2)) + ->expects($this->once()) ->method('setAppValue') ->with('theming', 'cachebuster', 16); - $this->config - ->expects($this->at(3)) - ->method('getAppValue') - ->with('theming', 'slogan', $this->defaults->getSlogan()) - ->willReturn($this->defaults->getSlogan()); $this->assertSame($this->defaults->getSlogan(), $this->template->undo('slogan')); } - public function testUndoColor() { + public function testUndoPrimaryColor(): void { $this->config - ->expects($this->at(0)) + ->expects($this->once()) ->method('deleteAppValue') - ->with('theming', 'color'); + ->with('theming', 'primary_color'); $this->config - ->expects($this->at(1)) + ->expects($this->once()) ->method('getAppValue') ->with('theming', 'cachebuster', '0') ->willReturn('15'); $this->config - ->expects($this->at(2)) + ->expects($this->once()) ->method('setAppValue') ->with('theming', 'cachebuster', 16); - $this->config - ->expects($this->at(3)) - ->method('getAppValue') - ->with('theming', 'color', $this->defaults->getColorPrimary()) - ->willReturn($this->defaults->getColorPrimary()); - $this->assertSame($this->defaults->getColorPrimary(), $this->template->undo('color')); + $this->assertSame($this->defaults->getColorPrimary(), $this->template->undo('primary_color')); } - public function testUndoDefaultAction() { + public function testUndoDefaultAction(): void { $this->config - ->expects($this->at(0)) + ->expects($this->once()) ->method('deleteAppValue') ->with('theming', 'defaultitem'); $this->config - ->expects($this->at(1)) + ->expects($this->once()) ->method('getAppValue') ->with('theming', 'cachebuster', '0') ->willReturn('15'); $this->config - ->expects($this->at(2)) + ->expects($this->once()) ->method('setAppValue') ->with('theming', 'cachebuster', 16); $this->assertSame('', $this->template->undo('defaultitem')); } - public function testGetBackground() { + public function testGetBackground(): void { $this->imageManager ->expects($this->once()) ->method('getImageUrl') @@ -592,15 +618,12 @@ class ThemingDefaultsTest extends TestCase { ->with('logo') ->willThrowException(new NotFoundException()); $this->config - ->expects($this->at(0)) + ->expects($this->exactly(2)) ->method('getAppValue') - ->with('theming', 'logoMime') - ->willReturn(''); - $this->config - ->expects($this->at(1)) - ->method('getAppValue') - ->with('theming', 'cachebuster', '0') - ->willReturn('0'); + ->willReturnMap([ + ['theming', 'logoMime', '', ''], + ['theming', 'cachebuster', '0', '0'], + ]); $this->urlGenerator->expects($this->once()) ->method('imagePath') ->with('core', $withName) @@ -608,25 +631,22 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('core-logo?v=0', $this->template->getLogo($useSvg)); } - public function testGetLogoDefaultWithSvg() { + public function testGetLogoDefaultWithSvg(): void { $this->getLogoHelper('logo/logo.svg', true); } - public function testGetLogoDefaultWithoutSvg() { + public function testGetLogoDefaultWithoutSvg(): void { $this->getLogoHelper('logo/logo.png', false); } - public function testGetLogoCustom() { + public function testGetLogoCustom(): void { $this->config - ->expects($this->at(0)) + ->expects($this->exactly(2)) ->method('getAppValue') - ->with('theming', 'logoMime', false) - ->willReturn('image/svg+xml'); - $this->config - ->expects($this->at(1)) - ->method('getAppValue') - ->with('theming', 'cachebuster', '0') - ->willReturn('0'); + ->willReturnMap([ + ['theming', 'logoMime', '', 'image/svg+xml'], + ['theming', 'cachebuster', '0', '0'], + ]); $this->urlGenerator->expects($this->once()) ->method('linkToRoute') ->with('theming.Theming.getImage') @@ -634,7 +654,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('custom-logo' . '?v=0', $this->template->getLogo()); } - public function testGetScssVariablesCached() { + public function testGetScssVariablesCached(): void { $this->config->expects($this->any())->method('getAppValue')->with('theming', 'cachebuster', '0')->willReturn('1'); $this->cacheFactory->expects($this->once()) ->method('createDistributed') @@ -644,17 +664,25 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals(['foo' => 'bar'], $this->template->getScssVariables()); } - public function testGetScssVariables() { - $this->config->expects($this->at(0))->method('getAppValue')->with('theming', 'cachebuster', '0')->willReturn('0'); - $this->config->expects($this->at(1))->method('getAppValue')->with('theming', 'logoMime', false)->willReturn('jpeg'); - $this->config->expects($this->at(2))->method('getAppValue')->with('theming', 'backgroundMime', false)->willReturn('jpeg'); - $this->config->expects($this->at(3))->method('getAppValue')->with('theming', 'logoheaderMime', false)->willReturn('jpeg'); - $this->config->expects($this->at(4))->method('getAppValue')->with('theming', 'faviconMime', false)->willReturn('jpeg'); + public function testGetScssVariables(): void { + $this->config + ->expects($this->any()) + ->method('getAppValue') + ->willReturnMap([ + ['theming', 'cachebuster', '0', '0'], + ['theming', 'logoMime', '', 'jpeg'], + ['theming', 'backgroundMime', '', 'jpeg'], + ['theming', 'logoheaderMime', '', 'jpeg'], + ['theming', 'faviconMime', '', 'jpeg'], + ]); - $this->config->expects($this->at(5))->method('getAppValue')->with('theming', 'color', null)->willReturn($this->defaults->getColorPrimary()); - $this->config->expects($this->at(6))->method('getAppValue')->with('theming', 'color', $this->defaults->getColorPrimary())->willReturn($this->defaults->getColorPrimary()); - $this->config->expects($this->at(7))->method('getAppValue')->with('theming', 'color', $this->defaults->getColorPrimary())->willReturn($this->defaults->getColorPrimary()); - $this->config->expects($this->at(8))->method('getAppValue')->with('theming', 'color', $this->defaults->getColorPrimary())->willReturn($this->defaults->getColorPrimary()); + $this->appConfig + ->expects(self::atLeastOnce()) + ->method('getValueString') + ->willReturnMap([ + ['theming', 'primary_color', '', false, $this->defaults->getColorPrimary()], + ['theming', 'primary_color', $this->defaults->getColorPrimary(), false, $this->defaults->getColorPrimary()], + ]); $this->util->expects($this->any())->method('invertTextColor')->with($this->defaults->getColorPrimary())->willReturn(false); $this->util->expects($this->any())->method('elementColor')->with($this->defaults->getColorPrimary())->willReturn('#aaaaaa'); @@ -663,10 +691,14 @@ class ThemingDefaultsTest extends TestCase { ->with('theming-0-') ->willReturn($this->cache); $this->cache->expects($this->once())->method('get')->with('getScssVariables')->willReturn(null); - $this->imageManager->expects($this->at(0))->method('getImageUrl')->with('logo')->willReturn('custom-logo?v=0'); - $this->imageManager->expects($this->at(1))->method('getImageUrl')->with('logoheader')->willReturn('custom-logoheader?v=0'); - $this->imageManager->expects($this->at(2))->method('getImageUrl')->with('favicon')->willReturn('custom-favicon?v=0'); - $this->imageManager->expects($this->at(3))->method('getImageUrl')->with('background')->willReturn('custom-background?v=0'); + $this->imageManager->expects($this->exactly(4)) + ->method('getImageUrl') + ->willReturnMap([ + ['logo', 'custom-logo?v=0'], + ['logoheader', 'custom-logoheader?v=0'], + ['favicon', 'custom-favicon?v=0'], + ['background', 'custom-background?v=0'], + ]); $expected = [ 'theming-cachebuster' => '\'0\'', @@ -687,7 +719,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals($expected, $this->template->getScssVariables()); } - public function testGetDefaultAndroidURL() { + public function testGetDefaultAndroidURL(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -697,7 +729,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('https://play.google.com/store/apps/details?id=com.nextcloud.client', $this->template->getAndroidClientUrl()); } - public function testGetCustomAndroidURL() { + public function testGetCustomAndroidURL(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -707,7 +739,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('https://play.google.com/store/apps/details?id=com.mycloud.client', $this->template->getAndroidClientUrl()); } - public function testGetDefaultiOSURL() { + public function testGetDefaultiOSURL(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -717,7 +749,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('https://geo.itunes.apple.com/us/app/nextcloud/id1125420102?mt=8', $this->template->getiOSClientUrl()); } - public function testGetCustomiOSURL() { + public function testGetCustomiOSURL(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -727,7 +759,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('https://geo.itunes.apple.com/us/app/nextcloud/id1234567890?mt=8', $this->template->getiOSClientUrl()); } - public function testGetDefaultiTunesAppId() { + public function testGetDefaultiTunesAppId(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -737,7 +769,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('1125420102', $this->template->getiTunesAppId()); } - public function testGetCustomiTunesAppId() { + public function testGetCustomiTunesAppId(): void { $this->config ->expects($this->once()) ->method('getAppValue') @@ -747,7 +779,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('1234567890', $this->template->getiTunesAppId()); } - public function dataReplaceImagePath() { + public static function dataReplaceImagePath(): array { return [ ['core', 'test.png', false], ['core', 'manifest.json'], @@ -756,8 +788,8 @@ class ThemingDefaultsTest extends TestCase { ]; } - /** @dataProvider dataReplaceImagePath */ - public function testReplaceImagePath($app, $image, $result = 'themingRoute?v=0') { + #[\PHPUnit\Framework\Attributes\DataProvider('dataReplaceImagePath')] + public function testReplaceImagePath(string $app, string $image, string|bool $result = 'themingRoute?v=1234abcd'): void { $this->cache->expects($this->any()) ->method('get') ->with('shouldReplaceIcons') @@ -771,6 +803,12 @@ class ThemingDefaultsTest extends TestCase { ->expects($this->any()) ->method('linkToRoute') ->willReturn('themingRoute'); + if ($result) { + $this->util + ->expects($this->once()) + ->method('getCacheBuster') + ->willReturn('1234abcd'); + } $this->assertEquals($result, $this->template->replaceImagePath($app, $image)); } } diff --git a/apps/theming/tests/UtilTest.php b/apps/theming/tests/UtilTest.php index d6fe318cbca..1e944027e32 100644 --- a/apps/theming/tests/UtilTest.php +++ b/apps/theming/tests/UtilTest.php @@ -1,33 +1,13 @@ <?php + +declare(strict_types=1); /** - * @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Julius Haertl <jus@bitgrid.net> - * @author Julius Härtl <jus@bitgrid.net> - * @author Michael Weimann <mail@michael-weimann.eu> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ - namespace OCA\Theming\Tests; +use OCA\Theming\ImageManager; use OCA\Theming\Util; use OCP\App\IAppManager; use OCP\Files\IAppData; @@ -35,124 +15,149 @@ use OCP\Files\NotFoundException; use OCP\Files\SimpleFS\ISimpleFile; use OCP\Files\SimpleFS\ISimpleFolder; use OCP\IConfig; +use OCP\Server; +use OCP\ServerVersion; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class UtilTest extends TestCase { - /** @var Util */ - protected $util; - /** @var IConfig */ - protected $config; - /** @var IAppData */ - protected $appData; - /** @var IAppManager */ - protected $appManager; + protected Util $util; + protected IConfig&MockObject $config; + protected IAppData&MockObject $appData; + protected IAppManager $appManager; + protected ImageManager&MockObject $imageManager; protected function setUp(): void { parent::setUp(); $this->config = $this->createMock(IConfig::class); $this->appData = $this->createMock(IAppData::class); - $this->appManager = $this->createMock(IAppManager::class); - $this->util = new Util($this->config, $this->appManager, $this->appData); + $this->appManager = Server::get(IAppManager::class); + $this->imageManager = $this->createMock(ImageManager::class); + $this->util = new Util($this->createMock(ServerVersion::class), $this->config, $this->appManager, $this->appData, $this->imageManager); } - public function dataInvertTextColor() { + public static function dataColorContrast(): array { + return [ + ['#ffffff', '#FFFFFF', 1], + ['#000000', '#000000', 1], + ['#ffffff', '#000000', 21], + ['#000000', '#FFFFFF', 21], + ['#9E9E9E', '#353535', 4.578], + ['#353535', '#9E9E9E', 4.578], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataColorContrast')] + public function testColorContrast(string $color1, string $color2, int|float $contrast): void { + $this->assertEqualsWithDelta($contrast, $this->util->colorContrast($color1, $color2), .001); + } + + public static function dataInvertTextColor(): array { return [ ['#ffffff', true], ['#000000', false], - ['#0082C9', false], + ['#00679e', false], ['#ffff00', true], ]; } - /** - * @dataProvider dataInvertTextColor - */ - public function testInvertTextColor($color, $expected) { + #[\PHPUnit\Framework\Attributes\DataProvider('dataInvertTextColor')] + public function testInvertTextColor(string $color, bool $expected): void { $invert = $this->util->invertTextColor($color); $this->assertEquals($expected, $invert); } - public function testCalculateLuminanceLight() { + public function testCalculateLuminanceLight(): void { $luminance = $this->util->calculateLuminance('#ffffff'); $this->assertEquals(1, $luminance); } - public function testCalculateLuminanceDark() { + public function testCalculateLuminanceDark(): void { $luminance = $this->util->calculateLuminance('#000000'); $this->assertEquals(0, $luminance); } - public function testCalculateLuminanceLightShorthand() { + public function testCalculateLuminanceLightShorthand(): void { $luminance = $this->util->calculateLuminance('#fff'); $this->assertEquals(1, $luminance); } - public function testCalculateLuminanceDarkShorthand() { + public function testCalculateLuminanceDarkShorthand(): void { $luminance = $this->util->calculateLuminance('#000'); $this->assertEquals(0, $luminance); } - public function testInvertTextColorInvalid() { - $invert = $this->util->invertTextColor('aaabbbcccddd123'); - $this->assertEquals(false, $invert); + + public function testInvertTextColorInvalid(): void { + $this->expectException(\Exception::class); + $this->util->invertTextColor('aaabbbcccddd123'); } - public function testInvertTextColorEmpty() { - $invert = $this->util->invertTextColor(''); - $this->assertEquals(false, $invert); + public function testInvertTextColorEmpty(): void { + $this->expectException(\Exception::class); + $this->util->invertTextColor(''); } - public function testElementColorDefault() { - $elementColor = $this->util->elementColor("#000000"); + public function testElementColorDefaultBlack(): void { + $elementColor = $this->util->elementColor('#000000'); + $this->assertEquals('#4d4d4d', $elementColor); + } + + public function testElementColorDefaultWhite(): void { + $elementColor = $this->util->elementColor('#ffffff'); + $this->assertEquals('#b3b3b3', $elementColor); + } + + public function testElementColorBlackOnDarkBackground(): void { + $elementColor = $this->util->elementColor('#000000', false); + $this->assertEquals('#4d4d4d', $elementColor); + } + + public function testElementColorBlackOnBrightBackground(): void { + $elementColor = $this->util->elementColor('#000000', true); $this->assertEquals('#000000', $elementColor); } - public function testElementColorOnDarkBackground() { - $elementColor = $this->util->elementColor("#000000", false); - $this->assertEquals('#555555', $elementColor); + public function testElementColorWhiteOnBrightBackground(): void { + $elementColor = $this->util->elementColor('#ffffff', true); + $this->assertEquals('#b3b3b3', $elementColor); } - public function testElementColorOnBrightBackground() { - $elementColor = $this->util->elementColor('#ffffff'); - $this->assertEquals('#aaaaaa', $elementColor); + public function testElementColorWhiteOnDarkBackground(): void { + $elementColor = $this->util->elementColor('#ffffff', false); + $this->assertEquals('#ffffff', $elementColor); } - public function testGenerateRadioButtonWhite() { + public function testGenerateRadioButtonWhite(): void { $button = $this->util->generateRadioButton('#ffffff'); $expected = 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTYiIHdpZHRoPSIxNiI+PHBhdGggZD0iTTggMWE3IDcgMCAwIDAtNyA3IDcgNyAwIDAgMCA3IDcgNyA3IDAgMCAwIDctNyA3IDcgMCAwIDAtNy03em0wIDFhNiA2IDAgMCAxIDYgNiA2IDYgMCAwIDEtNiA2IDYgNiAwIDAgMS02LTYgNiA2IDAgMCAxIDYtNnptMCAyYTQgNCAwIDEgMCAwIDggNCA0IDAgMCAwIDAtOHoiIGZpbGw9IiNmZmZmZmYiLz48L3N2Zz4='; $this->assertEquals($expected, $button); } - public function testGenerateRadioButtonBlack() { + public function testGenerateRadioButtonBlack(): void { $button = $this->util->generateRadioButton('#000000'); $expected = 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTYiIHdpZHRoPSIxNiI+PHBhdGggZD0iTTggMWE3IDcgMCAwIDAtNyA3IDcgNyAwIDAgMCA3IDcgNyA3IDAgMCAwIDctNyA3IDcgMCAwIDAtNy03em0wIDFhNiA2IDAgMCAxIDYgNiA2IDYgMCAwIDEtNiA2IDYgNiAwIDAgMS02LTYgNiA2IDAgMCAxIDYtNnptMCAyYTQgNCAwIDEgMCAwIDggNCA0IDAgMCAwIDAtOHoiIGZpbGw9IiMwMDAwMDAiLz48L3N2Zz4='; $this->assertEquals($expected, $button); } - /** - * @dataProvider dataGetAppIcon - */ - public function testGetAppIcon($app, $expected) { + #[\PHPUnit\Framework\Attributes\DataProvider('dataGetAppIcon')] + public function testGetAppIcon(string $app, string $expected): void { $this->appData->expects($this->any()) ->method('getFolder') - ->with('images') + ->with('global/images') ->willThrowException(new NotFoundException()); - $this->appManager->expects($this->once()) - ->method('getAppPath') - ->with($app) - ->willReturn(\OC_App::getAppPath($app)); $icon = $this->util->getAppIcon($app); $this->assertEquals($expected, $icon); } - public function dataGetAppIcon() { + public static function dataGetAppIcon(): array { return [ - ['user_ldap', \OC_App::getAppPath('user_ldap') . '/img/app.svg'], + ['user_ldap', Server::get(IAppManager::class)->getAppPath('user_ldap') . '/img/app.svg'], ['noapplikethis', \OC::$SERVERROOT . '/core/img/logo/logo.svg'], - ['comments', \OC_App::getAppPath('comments') . '/img/comments.svg'], + ['comments', Server::get(IAppManager::class)->getAppPath('comments') . '/img/comments.svg'], ]; } - public function testGetAppIconThemed() { + public function testGetAppIconThemed(): void { $file = $this->createMock(ISimpleFile::class); $folder = $this->createMock(ISimpleFolder::class); $folder->expects($this->once()) @@ -161,42 +166,34 @@ class UtilTest extends TestCase { ->willReturn($file); $this->appData->expects($this->once()) ->method('getFolder') - ->with('images') + ->with('global/images') ->willReturn($folder); $icon = $this->util->getAppIcon('noapplikethis'); $this->assertEquals($file, $icon); } - /** - * @dataProvider dataGetAppImage - */ - public function testGetAppImage($app, $image, $expected) { - if ($app !== 'core') { - $this->appManager->expects($this->once()) - ->method('getAppPath') - ->with($app) - ->willReturn(\OC_App::getAppPath($app)); - } + #[\PHPUnit\Framework\Attributes\DataProvider('dataGetAppImage')] + public function testGetAppImage(string $app, string $image, string|bool $expected): void { $this->assertEquals($expected, $this->util->getAppImage($app, $image)); } - public function dataGetAppImage() { + public static function dataGetAppImage(): array { return [ ['core', 'logo/logo.svg', \OC::$SERVERROOT . '/core/img/logo/logo.svg'], - ['files', 'external', \OC::$SERVERROOT . '/apps/files/img/external.svg'], - ['files', 'external.svg', \OC::$SERVERROOT . '/apps/files/img/external.svg'], + ['files', 'folder', \OC::$SERVERROOT . '/apps/files/img/folder.svg'], + ['files', 'folder.svg', \OC::$SERVERROOT . '/apps/files/img/folder.svg'], ['noapplikethis', 'foobar.svg', false], ]; } - public function testColorizeSvg() { - $input = "#0082c9 #0082C9 #000000 #FFFFFF"; - $expected = "#AAAAAA #AAAAAA #000000 #FFFFFF"; + public function testColorizeSvg(): void { + $input = '#0082c9 #0082C9 #000000 #FFFFFF'; + $expected = '#AAAAAA #AAAAAA #000000 #FFFFFF'; $result = $this->util->colorizeSvg($input, '#AAAAAA'); $this->assertEquals($expected, $result); } - public function testIsAlreadyThemedFalse() { + public function testIsAlreadyThemedFalse(): void { $this->config->expects($this->once()) ->method('getSystemValue') ->with('theme', '') @@ -205,7 +202,7 @@ class UtilTest extends TestCase { $this->assertFalse($actual); } - public function testIsAlreadyThemedTrue() { + public function testIsAlreadyThemedTrue(): void { $this->config->expects($this->once()) ->method('getSystemValue') ->with('theme', '') @@ -214,17 +211,15 @@ class UtilTest extends TestCase { $this->assertTrue($actual); } - public function dataIsBackgroundThemed() { + public static function dataIsBackgroundThemed(): array { return [ ['', false], ['png', true], ['backgroundColor', false], ]; } - /** - * @dataProvider dataIsBackgroundThemed - */ - public function testIsBackgroundThemed($backgroundMime, $expected) { + #[\PHPUnit\Framework\Attributes\DataProvider('dataIsBackgroundThemed')] + public function testIsBackgroundThemed(string $backgroundMime, bool $expected): void { $this->config->expects($this->once()) ->method('getAppValue') ->with('theming', 'backgroundMime', '') |