From 4b789c8722e9f42f326a0ef12e6a22ef3ed6541b Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Sun, 28 Apr 2024 16:18:39 +0200 Subject: [PATCH] Update various functions and parameters for output and image processing This commit refactors several functions in different files, most notably modifying image processing in both GDImageProcessor and ImagickImageProcessor to handle non-square images more accurately. An array of favicons was also added to the test config, and various parameter types were updated in multiple files. Several functions have been streamlined for greater efficiency and clarity. --- composer.json | 1 + phpstan-baseline.neon | 34 +++-- src/Dto/Favicons.php | 41 +++++ src/ImageProcessor/GDImageProcessor.php | 10 +- src/ImageProcessor/ImagickImageProcessor.php | 18 ++- src/Resources/config/definition/favicons.php | 71 +++++++++ src/Resources/config/services.php | 14 ++ src/Service/Data.php | 6 +- src/Service/FaviconsBuilder.php | 34 +++++ src/Service/FaviconsCompiler.php | 151 +++++++++++++++++++ src/Service/FileCompilerInterface.php | 6 +- src/Service/ManifestCompiler.php | 27 +--- src/Service/ServiceWorkerCompiler.php | 37 ++--- src/Subscriber/FileCompileEventListener.php | 6 +- src/Subscriber/PwaDevServerSubscriber.php | 10 +- src/Twig/PwaRuntime.php | 48 ++++++ tests/Functional/ConfigurationTest.php | 65 ++++++++ tests/config.php | 4 + 18 files changed, 501 insertions(+), 82 deletions(-) create mode 100644 src/Dto/Favicons.php create mode 100644 src/Resources/config/definition/favicons.php create mode 100644 src/Service/FaviconsBuilder.php create mode 100644 src/Service/FaviconsCompiler.php create mode 100644 tests/Functional/ConfigurationTest.php diff --git a/composer.json b/composer.json index 3b8f8dc..c5bb537 100644 --- a/composer.json +++ b/composer.json @@ -49,6 +49,7 @@ "ekino/phpstan-banned-code": "^1.0", "ergebnis/phpunit-slow-test-detector": "^2.14", "infection/infection": "^0.28", + "matthiasnoback/symfony-config-test": "^5.1", "php-parallel-lint/php-parallel-lint": "^1.4", "phpstan/extension-installer": "^1.1", "phpstan/phpdoc-parser": "^1.28", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e252646..6db0444 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -180,6 +180,11 @@ parameters: count: 1 path: src/Dto/BackgroundSync.php + - + message: "#^Class SpomkyLabs\\\\PwaBundle\\\\Dto\\\\Favicons has an uninitialized property \\$src\\. Give it default value or assign it in the constructor\\.$#" + count: 1 + path: src/Dto/Favicons.php + - message: "#^Class SpomkyLabs\\\\PwaBundle\\\\Dto\\\\File has an uninitialized property \\$accept\\. Give it default value or assign it in the constructor\\.$#" count: 1 @@ -440,6 +445,16 @@ parameters: count: 1 path: src/ImageProcessor/GDImageProcessor.php + - + message: "#^Parameter \\#1 \\$dst_image of function imagecopyresampled expects GdImage, GdImage\\|false given\\.$#" + count: 1 + path: src/ImageProcessor/GDImageProcessor.php + + - + message: "#^Parameter \\#1 \\$image of function imagealphablending expects GdImage, GdImage\\|false given\\.$#" + count: 1 + path: src/ImageProcessor/GDImageProcessor.php + - message: "#^Parameter \\#1 \\$image of function imagepng expects GdImage, GdImage\\|false given\\.$#" count: 1 @@ -447,7 +462,7 @@ parameters: - message: "#^Parameter \\#1 \\$image of function imagesavealpha expects GdImage, GdImage\\|false given\\.$#" - count: 1 + count: 2 path: src/ImageProcessor/GDImageProcessor.php - @@ -485,6 +500,11 @@ parameters: count: 1 path: src/Normalizer/ServiceWorkerNormalizer.php + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:children\\(\\)\\.$#" + count: 1 + path: src/Resources/config/definition/favicons.php + - message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:children\\(\\)\\.$#" count: 1 @@ -623,14 +643,4 @@ parameters: - message: "#^Method SpomkyLabs\\\\PwaBundle\\\\SpomkyLabsPwaBundle\\:\\:loadExtension\\(\\) has parameter \\$config with no value type specified in iterable type array\\.$#" count: 1 - path: src/SpomkyLabsPwaBundle.php - - - - message: "#^Property SpomkyLabs\\\\PwaBundle\\\\Subscriber\\\\ManifestCompileEventListener\\:\\:\\$jsonOptions type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/Subscriber/ManifestCompileEventListener.php - - - - message: "#^Property SpomkyLabs\\\\PwaBundle\\\\Subscriber\\\\PwaDevServerSubscriber\\:\\:\\$jsonOptions type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/Subscriber/PwaDevServerSubscriber.php \ No newline at end of file + path: src/SpomkyLabsPwaBundle.php \ No newline at end of file diff --git a/src/Dto/Favicons.php b/src/Dto/Favicons.php new file mode 100644 index 0000000..17b3658 --- /dev/null +++ b/src/Dto/Favicons.php @@ -0,0 +1,41 @@ +|null + */ + #[SerializedName('border_radius')] + public null|int $borderRadius = null; + + /** + * @var int<1, 100>|null + */ + #[SerializedName('image_scale')] + public null|int $imageScale = null; + + #[SerializedName('only_high_resolution')] + public null|bool $onlyHighResolution = null; + + #[SerializedName('only_tile_silhouette')] + public null|bool $onlyTileSilhouette = null; +} diff --git a/src/ImageProcessor/GDImageProcessor.php b/src/ImageProcessor/GDImageProcessor.php index 4e4fd2a..a108a0e 100644 --- a/src/ImageProcessor/GDImageProcessor.php +++ b/src/ImageProcessor/GDImageProcessor.php @@ -17,7 +17,15 @@ public function process(string $image, ?int $width, ?int $height, ?string $forma assert($image !== false); imagealphablending($image, true); if ($width !== null && $height !== null) { - $image = imagescale($image, $width, $height); + if ($width === $height) { + $image = imagescale($image, $width, $height); + } else { + $newImage = imagecreatetruecolor($width, $height); + imagealphablending($newImage, false); + imagesavealpha($newImage, true); + imagecopyresampled($newImage, $image, 0, 0, 0, 0, $width, $height, imagesx($image), imagesy($image)); + $image = $newImage; + } } ob_start(); imagesavealpha($image, true); diff --git a/src/ImageProcessor/ImagickImageProcessor.php b/src/ImageProcessor/ImagickImageProcessor.php index ee7e40f..34730bc 100644 --- a/src/ImageProcessor/ImagickImageProcessor.php +++ b/src/ImageProcessor/ImagickImageProcessor.php @@ -9,12 +9,6 @@ final readonly class ImagickImageProcessor implements ImageProcessorInterface { - public function __construct( - private int $filters = Imagick::FILTER_LANCZOS2, - private float $blur = 1, - ) { - } - public function process(string $image, ?int $width, ?int $height, ?string $format): string { if ($width === null && $height === null) { @@ -23,7 +17,17 @@ public function process(string $image, ?int $width, ?int $height, ?string $forma $imagick = new Imagick(); $imagick->readImageBlob($image); if ($width !== null && $height !== null) { - $imagick->resizeImage($width, $height, $this->filters, $this->blur, true); + if ($width === $height) { + $imagick->scaleImage($width, $height); + } else { + $imagick->scaleImage(min($width, $height), min($width, $height)); + $imagick->extentImage( + $width, + $height, + -($width - min($width, $height)) / 2, + -($height - min($width, $height)) / 2 + ); + } } $imagick->setImageBackgroundColor(new ImagickPixel('transparent')); if ($format !== null) { diff --git a/src/Resources/config/definition/favicons.php b/src/Resources/config/definition/favicons.php new file mode 100644 index 0000000..dff6b7a --- /dev/null +++ b/src/Resources/config/definition/favicons.php @@ -0,0 +1,71 @@ +rootNode() + ->beforeNormalization() + ->ifTrue( + static fn (null|array $v): bool => $v !== null && isset($v['manifest']) && $v['manifest']['enabled'] === true && isset($v['favicons']) && $v['favicons']['enabled'] === true && isset($v['manifest']['theme_color']) + ) + ->then(static function (array $v): array { + $v['favicons']['background_color'] = $v['manifest']['theme_color']; + return $v; + }) + ->end() + ->children() + ->arrayNode('favicons') + ->canBeEnabled() + ->children() + ->scalarNode('src') + ->isRequired() + ->info('The source of the favicon. Shall be a SVG or large PNG.') + ->end() + ->scalarNode('background_color') + ->defaultNull() + ->info( + 'The background color of the application. If this value is not defined and that of the Manifest section is, the value of the latter will be used.' + ) + ->example(['red', '#f5ef06']) + ->end() + ->scalarNode('safari_pinned_tab_color') + ->defaultNull() + ->info('The color of the Safari pinned tab.') + ->example(['red', '#f5ef06']) + ->end() + ->scalarNode('tile_color') + ->defaultNull() + ->info('The color of the tile for Windows 8+.') + ->example(['red', '#f5ef06']) + ->end() + ->integerNode('border_radius') + ->defaultNull() + ->min(1) + ->max(50) + ->info('The border radius of the icon.') + ->end() + ->integerNode('image_scale') + ->defaultNull() + ->min(1) + ->max(100) + ->info('The scale of the icon.') + ->end() + ->booleanNode('generate_precomposed') + ->defaultFalse() + ->info('Generate precomposed icons. Useful for old iOS devices.') + ->end() + ->booleanNode('only_high_resolution') + ->defaultTrue() + ->info('Only high resolution icons.') + ->end() + ->booleanNode('only_tile_silhouette') + ->defaultTrue() + ->info('Only tile silhouette for Windows 8+.') + ->end() + ->end() + ->end() + ->end() + ->end(); +}; diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index 919c933..fb9d830 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -10,12 +10,15 @@ use SpomkyLabs\PwaBundle\Command\CreateScreenshotCommand; use SpomkyLabs\PwaBundle\Command\ListCacheStrategiesCommand; use SpomkyLabs\PwaBundle\DataCollector\PwaCollector; +use SpomkyLabs\PwaBundle\Dto\Favicons; use SpomkyLabs\PwaBundle\Dto\Manifest; use SpomkyLabs\PwaBundle\Dto\ServiceWorker; use SpomkyLabs\PwaBundle\EventSubscriber\ScreenshotSubscriber; use SpomkyLabs\PwaBundle\ImageProcessor\GDImageProcessor; use SpomkyLabs\PwaBundle\ImageProcessor\ImagickImageProcessor; use SpomkyLabs\PwaBundle\MatchCallbackHandler\MatchCallbackHandlerInterface; +use SpomkyLabs\PwaBundle\Service\FaviconsBuilder; +use SpomkyLabs\PwaBundle\Service\FaviconsCompiler; use SpomkyLabs\PwaBundle\Service\FileCompilerInterface; use SpomkyLabs\PwaBundle\Service\ManifestBuilder; use SpomkyLabs\PwaBundle\Service\ManifestCompiler; @@ -56,6 +59,17 @@ ; $container->set(ManifestCompiler::class); + /*** Favicons ***/ + $container->set(FaviconsBuilder::class) + ->args([ + '$config' => param('spomky_labs_pwa.favicons.config'), + ]) + ; + $container->set(Favicons::class) + ->factory([service(FaviconsBuilder::class), 'create']) + ; + $container->set(FaviconsCompiler::class); + /*** Service Worker ***/ $container->set(ServiceWorkerBuilder::class) ->args([ diff --git a/src/Service/Data.php b/src/Service/Data.php index be7c577..59276a9 100644 --- a/src/Service/Data.php +++ b/src/Service/Data.php @@ -10,17 +10,17 @@ final readonly class Data { /** - * @param string[] $headers + * @param array $headers */ public function __construct( public string $url, public string $data, public array $headers - ){ + ) { } /** - * @param array $headers + * @param array $headers */ public static function create(string $url, string $data, array $headers = []): self { diff --git a/src/Service/FaviconsBuilder.php b/src/Service/FaviconsBuilder.php new file mode 100644 index 0000000..49f55f6 --- /dev/null +++ b/src/Service/FaviconsBuilder.php @@ -0,0 +1,34 @@ + $config + */ + public function __construct( + private readonly DenormalizerInterface $denormalizer, + private readonly array $config, + ) { + } + + public function create(): Favicons + { + if ($this->favicons === null) { + $result = $this->denormalizer->denormalize($this->config, Favicons::class); + assert($result instanceof Favicons); + $this->favicons = $result; + } + + return $this->favicons; + } +} diff --git a/src/Service/FaviconsCompiler.php b/src/Service/FaviconsCompiler.php new file mode 100644 index 0000000..13d139f --- /dev/null +++ b/src/Service/FaviconsCompiler.php @@ -0,0 +1,151 @@ + + */ + private null|array $files = null; + + public function __construct( + private readonly null|ImageProcessorInterface $imageProcessor, + private readonly Favicons $favicons, + private readonly AssetMapperInterface $assetMapper, + #[Autowire('%kernel.debug%')] + public readonly bool $debug, + ) { + } + + /** + * @return array + */ + public function getFiles(): array + { + if ($this->files !== null) { + return $this->files; + } + if ($this->imageProcessor === null || $this->favicons->enabled === false) { + return []; + } + $asset = $this->assetMapper->getAsset($this->favicons->src->src); + assert($asset !== null, 'The asset does not exist.'); + $this->files = [ + '/favicon.ico' => $this->processIcon($asset, '/favicon.ico', 16, 16, 'ico', 'image/x-icon'), + ]; + $sizes = [16, 32, 36, 48, 57, 60, 70, 72, 76, 96, 114, 120, 144, 150, 152, 180, 192, 194, 256, 310, 384, 512]; + foreach ($sizes as $size) { + $this->files[sprintf('/favicons/icon-%dx%d.png', $size, $size)] = $this->processIcon( + $asset, + sprintf('/favicons/icon-%dx%d.{hash}.png', $size, $size), + $size, + $size, + 'png', + 'image/png' + ); + } + if ($this->favicons->tileColor !== null) { + $this->files['/favicons/icon-310x150.png'] = $this->processIcon( + $asset, + '/favicons/icon-310x150.{hash}.png', + 310, + 150, + 'png', + 'image/png' + ); + $this->files['/favicons/browserconfig.xml'] = $this->processBrowserConfig(); + } + + return $this->files; + } + + private function processIcon( + MappedAsset $asset, + string $publicUrl, + int $width, + int $height, + string $format, + string $mimeType + ): Data { + $content = file_get_contents($asset->sourcePath); + assert($content !== false); + if ($this->debug === true) { + $hash = hash('xxh128', $content); + return Data::create( + str_replace(['{hash}', '.png'], [$hash, '.svg'], $publicUrl), + $content, + [ + 'Cache-Control' => 'public, max-age=604800, immutable', + 'Content-Type' => 'image/svg+xml', + 'X-Favicons-Dev' => true, + 'Etag' => $hash, + ] + ); + } + assert($this->imageProcessor !== null); + $data = $this->imageProcessor->process($content, $width, $height, $format); + return Data::create( + str_replace('{hash}', hash('xxh128', $data), $publicUrl), + $data, + [ + 'Cache-Control' => 'public, max-age=604800, immutable', + 'Content-Type' => $mimeType, + 'X-Favicons-Dev' => true, + 'Etag' => hash('xxh128', $data), + ] + ); + } + + private function processBrowserConfig(): Data + { + $icon310x150 = $this->files['/favicons/icon-310x150.png'] ?? null; + $icon70x70 = $this->files['/favicons/icon-70x70.png'] ?? null; + $icon150x150 = $this->files['/favicons/icon-150x150.png'] ?? null; + $icon310x310 = $this->files['/favicons/icon-310x310.png'] ?? null; + assert($icon310x150 !== null); + assert($icon70x70 !== null); + assert($icon150x150 !== null); + assert($icon310x310 !== null); + if ($this->favicons->tileColor === null) { + $tileColor = ''; + } else { + $tileColor = sprintf(PHP_EOL . ' %s', $this->favicons->tileColor); + } + + $content = << + + + + + + + {$tileColor} + + + +XML; + $hash = hash('xxh128', $content); + return Data::create( + sprintf('/favicons/browserconfig.%s.xml', $hash), + $content, + [ + 'Cache-Control' => 'public, max-age=604800, immutable', + 'Content-Type' => 'application/xml', + 'X-Favicons-Dev' => true, + 'Etag' => $hash, + ] + ); + } +} diff --git a/src/Service/FileCompilerInterface.php b/src/Service/FileCompilerInterface.php index 8bb7372..bd88c1b 100644 --- a/src/Service/FileCompilerInterface.php +++ b/src/Service/FileCompilerInterface.php @@ -7,9 +7,7 @@ interface FileCompilerInterface { /** - * @return array + * @return iterable */ - public function supportedPublicUrls(): array; - - public function get(string $publicUrl): null|Data; + public function getFiles(): iterable; } diff --git a/src/Service/ManifestCompiler.php b/src/Service/ManifestCompiler.php index 6eaa641..a088053 100644 --- a/src/Service/ManifestCompiler.php +++ b/src/Service/ManifestCompiler.php @@ -61,40 +61,21 @@ public function __construct( } /** - * @return array + * @return iterable */ - public function supportedPublicUrls(): array + public function getFiles(): iterable { if ($this->manifest->enabled === false) { return []; } if ($this->locales === []) { - return [$this->manifestPublicUrl]; - } - - return array_map( - fn (string $locale) => str_replace('{locale}', $locale, $this->manifestPublicUrl), - $this->locales - ); - } - - public function get(string $publicUrl): null|Data - { - if ($this->manifest->enabled === false) { - return null; - } - if ($this->locales === []) { - return $this->compileManifest(null); + yield $this->manifestPublicUrl => $this->compileManifest(null); } foreach ($this->locales as $locale) { - if ($publicUrl === str_replace('{locale}', $locale, $this->manifestPublicUrl)) { - return $this->compileManifest($locale); - } + yield str_replace('{locale}', $locale, $this->manifestPublicUrl) => $this->compileManifest($locale); } - - return null; } private function compileManifest(null|string $locale): Data diff --git a/src/Service/ServiceWorkerCompiler.php b/src/Service/ServiceWorkerCompiler.php index 9969270..38eb36b 100644 --- a/src/Service/ServiceWorkerCompiler.php +++ b/src/Service/ServiceWorkerCompiler.php @@ -47,24 +47,13 @@ public function __construct( } } - public function supportedPublicUrls(): array - { - return [$this->serviceWorkerPublicUrl, ...$this->listWorkboxFiles()]; - } - - public function get(string $publicUrl): null|Data + /** + * @return iterable + */ + public function getFiles(): iterable { - if ($publicUrl === $this->serviceWorkerPublicUrl) { - return $this->compileSW(); - } - if ($this->workboxPublicUrl === null) { - return null; - } - if (! str_starts_with($publicUrl, $this->workboxPublicUrl)) { - return null; - } - - return $this->getWorkboxFile($publicUrl); + yield $this->serviceWorkerPublicUrl => $this->compileSW(); + yield from $this->getWorkboxFiles(); } private function compileSW(): Data @@ -107,9 +96,9 @@ private function includeRootSW(): string } /** - * @return array + * @return iterable */ - private function listWorkboxFiles(): array + private function getWorkboxFiles(): iterable { if ($this->serviceWorker->workbox->enabled === false) { return []; @@ -120,7 +109,6 @@ private function listWorkboxFiles(): array $fileLocator = new FileLocator(__DIR__ . '/../Resources'); $resourcePath = $fileLocator->locate(sprintf('workbox-v%s', $this->workboxVersion)); - $publicUrls = []; $files = scandir($resourcePath); assert(is_array($files), 'Unable to list the files.'); foreach ($files as $file) { @@ -135,10 +123,13 @@ private function listWorkboxFiles(): array if (! is_file($path) || ! is_readable($path)) { continue; } - $publicUrls[] = sprintf('%s/%s', $this->workboxPublicUrl, $file); + $publicUrl = sprintf('%s/%s', $this->workboxPublicUrl, $file); + $data = $this->getWorkboxFile($publicUrl); + if ($data === null) { + continue; + } + yield $publicUrl => $data; } - - return $publicUrls; } private function getWorkboxFile(string $publicUrl): null|Data diff --git a/src/Subscriber/FileCompileEventListener.php b/src/Subscriber/FileCompileEventListener.php index 14cbec8..bef7d9c 100644 --- a/src/Subscriber/FileCompileEventListener.php +++ b/src/Subscriber/FileCompileEventListener.php @@ -28,11 +28,7 @@ public function __construct( public function __invoke(PreAssetsCompileEvent $event): void { foreach ($this->fileCompilers as $fileCompiler) { - foreach ($fileCompiler->supportedPublicUrls() as $publicUrl) { - $data = $fileCompiler->get($publicUrl); - if ($data === null) { - continue; - } + foreach ($fileCompiler->getFiles() as $data) { $this->assetsFilesystem->write($data->url, $data->data); } } diff --git a/src/Subscriber/PwaDevServerSubscriber.php b/src/Subscriber/PwaDevServerSubscriber.php index f5ea1d4..1c79acc 100644 --- a/src/Subscriber/PwaDevServerSubscriber.php +++ b/src/Subscriber/PwaDevServerSubscriber.php @@ -13,7 +13,6 @@ use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\Profiler\Profiler; -use function in_array; final readonly class PwaDevServerSubscriber implements EventSubscriberInterface { @@ -36,10 +35,13 @@ public function onKernelRequest(RequestEvent $event): void $request = $event->getRequest(); $pathInfo = $request->getPathInfo(); foreach ($this->fileCompilers as $fileCompiler) { - if (in_array($pathInfo, $fileCompiler->supportedPublicUrls(), true)) { - $data = $fileCompiler->get($pathInfo); - assert($data !== null); + $files = iterator_to_array($fileCompiler->getFiles()); + foreach ($files as $data) { + if ($data->url !== $pathInfo) { + continue; + } $this->serveFile($event, $data); + return; } } } diff --git a/src/Twig/PwaRuntime.php b/src/Twig/PwaRuntime.php index 71d0f6d..1388ed9 100644 --- a/src/Twig/PwaRuntime.php +++ b/src/Twig/PwaRuntime.php @@ -5,8 +5,10 @@ namespace SpomkyLabs\PwaBundle\Twig; use InvalidArgumentException; +use SpomkyLabs\PwaBundle\Dto\Favicons; use SpomkyLabs\PwaBundle\Dto\Icon; use SpomkyLabs\PwaBundle\Dto\Manifest; +use SpomkyLabs\PwaBundle\Service\FaviconsCompiler; use Symfony\Component\AssetMapper\AssetMapperInterface; use Symfony\Component\AssetMapper\MappedAsset; use Symfony\Component\DependencyInjection\Attribute\Autowire; @@ -22,6 +24,8 @@ public function __construct( private AssetMapperInterface $assetMapper, private Manifest $manifest, + private Favicons $favicons, + private FaviconsCompiler $faviconsCompiler, #[Autowire('%spomky_labs_pwa.manifest.public_url%')] string $manifestPublicUrl, ) { @@ -34,6 +38,7 @@ public function __construct( public function load( bool $injectThemeColor = true, bool $injectIcons = true, + bool $injectFavicons = true, bool $injectSW = true, array $swAttributes = [], null|string $locale = null, @@ -46,6 +51,7 @@ public function load( $output = $this->injectServiceWorker($output, $injectSW, $swAttributes); } $output = $this->injectIcons($output, $injectIcons); + $output = $this->injectFavicons($output, $injectFavicons); return $this->injectThemeColor($output, $injectThemeColor); } @@ -211,4 +217,46 @@ private function createAttributesString(array $attributes): string return $attributeString; } + + private function injectFavicons(string $output, bool $injectFavicons): string + { + if ($this->favicons->enabled === false || $injectFavicons === false) { + return $output; + } + + $files = $this->faviconsCompiler->getFiles(); + + $output .= PHP_EOL . ''; + foreach ([57, 60, 72, 76, 114, 120, 144, 152, 180] as $size) { + $output .= PHP_EOL . sprintf( + '', + $size, + $size, + $files[sprintf('/favicons/icon-%dx%d.png', $size, $size)]->url + ); + } + foreach ([16, 32, 48, 96, 192, 256, 384, 512] as $size) { + $output .= PHP_EOL . sprintf( + '', + $size, + $size, + $files[sprintf('/favicons/icon-%dx%d.png', $size, $size)]->url + ); + } + if ($this->favicons->tileColor !== null) { + $output .= PHP_EOL . sprintf( + '', + $files['/favicons/browserconfig.xml']->url + ); + $output .= PHP_EOL . sprintf( + '', + $this->favicons->tileColor + ); + } + + return $output . (PHP_EOL . sprintf( + '', + $files['/favicons/icon-144x144.png']->url + )); + } } diff --git a/tests/Functional/ConfigurationTest.php b/tests/Functional/ConfigurationTest.php new file mode 100644 index 0000000..f051496 --- /dev/null +++ b/tests/Functional/ConfigurationTest.php @@ -0,0 +1,65 @@ +assertConfigurationIsValid($configuration); + } + + public static function dataConfigurationIsValid(): iterable + { + yield 'No configuration values' => [[]]; + yield 'Empty configuration' => [[ + 'pwa' => null, + ]]; + yield 'Image processor is defined' => [[ + 'pwa' => [ + 'image_processor' => ImagickImageProcessor::class, + 'web_client' => 'id_web_client', + 'user_agent' => 'user-agent/1.0', + ], + ]]; + } + + #[Test] + #[DataProvider('dataConfigurationIsInvalid')] + public function configurationIsInvalid(array $configuration, string $message): void + { + $this->assertConfigurationIsInvalid($configuration, $message); + } + + public static function dataConfigurationIsInvalid(): iterable + { + yield 'No configuration values' => [ + [ + 'pwa' => [ + 'favicons' => 10, + ], + ], + 'Invalid type for path "pwa.favicons". Expected "array", but got "int"', + ]; + } + + protected function getConfiguration(): ConfigurationInterface + { + return new Configuration(new SpomkyLabsPwaBundle(), null, 'pwa'); + } +} diff --git a/tests/config.php b/tests/config.php index b811592..e562821 100644 --- a/tests/config.php +++ b/tests/config.php @@ -55,6 +55,10 @@ ]); $container->extension('pwa', [ 'image_processor' => DummyImageProcessor::class, + 'favicons' => [ + 'enabled' => true, + 'src' => 'pwa/1920x1920.svg', + ], 'manifest' => [ 'enabled' => true, 'background_color' => 'red',