diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..179bcde --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: CI + +on: + - pull_request + - push + +jobs: + php-cs-fixer: + name: CS Fixer + runs-on: ubuntu-latest + container: + image: ghcr.io/elbformat/php-cs-fixer:edge-php8.2 + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Check src + run: php-cs-fixer fix --dry-run src + - name: Check tests + run: php-cs-fixer fix --dry-run tests + + phpunit: + name: Unittest + runs-on: ubuntu-latest + container: + image: ghcr.io/elbformat/ibexa-icon-fieldtype/php + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install dependencies + run: composer install + - name: Run tests + run: vendor/bin/phpunit + + phpstan: + name: Static code analysis + runs-on: ubuntu-latest + container: + image: ghcr.io/elbformat/ibexa-icon-fieldtype/php + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install dependencies + run: composer install + - name: Analyse + run: vendor/bin/phpstan \ No newline at end of file diff --git a/.gitignore b/.gitignore index a040318..210f39a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /.idea -vendor/ +/build +/vendor +.phpunit.result.cache composer.lock docker-compose.override.yml diff --git a/composer.json b/composer.json index e75fb77..27f4a70 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ ], "autoload": { "psr-4": { - "Elbformat\\IconBundle\\": "src/" + "Elbformat\\IconBundle\\": "src/", + "Elbformat\\IconBundle\\Test\\": "tests/" } }, "require": { @@ -27,7 +28,8 @@ "symfony/finder": "^5.4|^6.4" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.6" }, "config": { "sort-packages": true, diff --git a/doc/development.md b/doc/development.md index 3f7b1a5..b14a5d6 100644 --- a/doc/development.md +++ b/doc/development.md @@ -16,6 +16,17 @@ Run tests vendor/bin/phpunit ``` +Run static code analysis +```bash +vendor/bin/phpstan +``` + +Fix styles (from outside the container) +```bash +docker-compose run phpcsfixer fix src +docker-compose run phpcsfixer fix tests +``` + ## In-Place development If you want to test out how it integrates into ibexa, it's the easiest way to integrate the bundle into your project directly. By adding it as "vcs" your are able to push the changes you made right from your vendor folder. diff --git a/docker-compose.yml b/docker-compose.yml index 7ba0d98..dd82453 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,3 +4,7 @@ services: image: ghcr.io/elbformat/ibexa-icon-fieldtype/php volumes: - ./:/var/www + phpcsfixer: + image: ghcr.io/elbformat/php-cs-fixer:edge-php8.2 + volumes: + - ./:/code diff --git a/docker/Dockerfile b/docker/Dockerfile index 2d7f176..3b4325f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -23,13 +23,12 @@ RUN apk add --no-cache --virtual .build-deps autoconf g++ make linux-headers && apk del .build-deps && \ rm -rf /tmp/* -# ext-gd -RUN apk add --no-cache libpng libjpeg-turbo freetype && \ - apk add --no-cache --virtual .build-deps libpng-dev libjpeg-turbo-dev freetype-dev && \ - docker-php-ext-configure gd && \ - docker-php-ext-install gd && \ +# pcov +RUN apk add --no-cache --virtual .build-deps autoconf g++ make && \ + pecl install pcov-1.0.11 && \ apk del .build-deps && \ - rm -rf /tmp/* + rm -rf /tmp/* &&\ + docker-php-ext-enable pcov RUN rmdir /var/www/html WORKDIR /var/www \ No newline at end of file diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..478362b --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: max + paths: + - src \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..7b44633 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,24 @@ + + + + + src/ + + + + + + + + + tests/ + + + + + + + + + + diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 54a14eb..3fcb357 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -1,4 +1,5 @@ getRootNode() ->arrayPrototype() // Name of the set ->normalizeKeys(false) @@ -29,4 +30,4 @@ public function getConfigTreeBuilder(): TreeBuilder return $treeBuilder; } -} \ No newline at end of file +} diff --git a/src/DependencyInjection/ElbformatIconExtension.php b/src/DependencyInjection/ElbformatIconExtension.php index 5c4af52..3f29f0a 100644 --- a/src/DependencyInjection/ElbformatIconExtension.php +++ b/src/DependencyInjection/ElbformatIconExtension.php @@ -1,4 +1,5 @@ $configs */ public function load(array $configs, ContainerBuilder $container): void { $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../../config/')); @@ -28,8 +30,12 @@ public function load(array $configs, ContainerBuilder $container): void public function prepend(ContainerBuilder $container): void { // Add template for rendering - $configFile = __DIR__.'/../../config/ezplatform.yaml'; - $config = Yaml::parse(file_get_contents($configFile)); + $configFile = file_get_contents(__DIR__.'/../../config/ezplatform.yaml'); + if (false === $configFile) { + throw new \RuntimeException(sprintf('%s not found or not readable', __DIR__.'/../../config/ezplatform.yaml')); + } + /** @var array{'ezplatform':array} $config */ + $config = Yaml::parse($configFile); $container->prependExtensionConfig('ezpublish', $config['ezplatform']); // Register namespace (as this is not done automatically. Maybe the missing "bundle" in path?) @@ -39,4 +45,4 @@ public function prepend(ContainerBuilder $container): void $container->prependExtensionConfig('framework', ['translator' => ['paths' => [__DIR__.'/../../translations']]]); } -} \ No newline at end of file +} diff --git a/src/ElbformatIconBundle.php b/src/ElbformatIconBundle.php index 4021ce0..151be4d 100644 --- a/src/ElbformatIconBundle.php +++ b/src/ElbformatIconBundle.php @@ -1,4 +1,5 @@ false, ]); } -} \ No newline at end of file +} diff --git a/src/FieldType/Icon/Value.php b/src/FieldType/Icon/Value.php index d140df8..ee69763 100644 --- a/src/FieldType/Icon/Value.php +++ b/src/FieldType/Icon/Value.php @@ -1,4 +1,5 @@ icon = $icon; } + public function __construct(?string $icon = null) + { + $this->icon = $icon; + } public function __toString(): string { - return $this->icon; + return $this->icon ?? ''; } public function getIcon(): ?string @@ -25,4 +29,4 @@ public function setIcon(?string $icon): void { $this->icon = $icon; } -} \ No newline at end of file +} diff --git a/src/Form/Type/IconSettingsType.php b/src/Form/Type/IconSettingsType.php index 0d6b707..298f7de 100644 --- a/src/Form/Type/IconSettingsType.php +++ b/src/Form/Type/IconSettingsType.php @@ -1,4 +1,5 @@ add('iconset', ChoiceType::class,[ + $builder->add('iconset', ChoiceType::class, [ 'choices' => $this->iconSetManager->getSetList() ]); } -} \ No newline at end of file +} diff --git a/src/Form/Type/IconType.php b/src/Form/Type/IconType.php index 007dcee..d6f9e7f 100644 --- a/src/Form/Type/IconType.php +++ b/src/Form/Type/IconType.php @@ -1,4 +1,5 @@ iconSetManager->getSet($iconSet)->getList(); $iconTemplates = []; foreach($iconList as $icon) { - $iconTemplates[$icon] = $this->twig->render('@ElbformatIconFieldtype/icon.html.twig',['icon' => $icon,'iconset' => $iconSet]); + $iconTemplates[$icon] = $this->twig->render('@ElbformatIconFieldtype/icon.html.twig', ['icon' => $icon,'iconset' => $iconSet]); } $builder->add('icon', ChoiceType::class, [ 'choices' => $iconList, @@ -47,4 +48,4 @@ public function configureOptions(OptionsResolver $resolver): void ]); $resolver->addAllowedTypes('icon_set', 'string'); } -} \ No newline at end of file +} diff --git a/src/IconSet/IconSet.php b/src/IconSet/IconSet.php index 7f8b804..3aa7744 100644 --- a/src/IconSet/IconSet.php +++ b/src/IconSet/IconSet.php @@ -1,4 +1,5 @@ */ protected array $items; - /** @param string[] */ + /** @param string[] $items */ public function __construct(array $items) { // Convert to string => string array $this->items = array_flip($items); - array_walk($this->items, fn(&$val, $key) => $val = $key); + array_walk($this->items, fn (&$val, $key) => $val = $key); } /** @return string[] */ @@ -21,4 +22,4 @@ public function getList(): array { return $this->items; } -} \ No newline at end of file +} diff --git a/src/IconSet/IconSetManager.php b/src/IconSet/IconSetManager.php index b408816..5a9bbb5 100644 --- a/src/IconSet/IconSetManager.php +++ b/src/IconSet/IconSetManager.php @@ -1,4 +1,5 @@ */ - protected array $sets; + protected array $sets = []; - /** array $configs */ public function __construct(array $configs) { foreach ($configs as $setName => $setConfig) { $items = []; - if (null !== ($setConfig['items']??null)) { + if (null !== ($setConfig['items'] ?? null)) { $items = $setConfig['items']; } - if (null !== ($setConfig['folder']??null)) { - $finder = (new Finder())->files()->in($setConfig['folder'])->depth(0); - if (null !== ($setConfig['pattern']??null)) { + if (null !== ($setConfig['folder'] ?? null)) { + $finder = (new Finder())->files()->in($setConfig['folder'])->depth(0)->sortByName(); + if (null !== ($setConfig['pattern'] ?? null)) { $finder = $finder->name($setConfig['pattern']); } foreach ($finder as $file) { @@ -40,11 +41,12 @@ public function getSet(string $name): IconSet } /** @return array */ - public function getSetList():array + public function getSetList(): array { // Convert to string => string array $sets = array_flip(array_keys($this->sets)); - array_walk($sets, fn(&$val, $key) => $val = $key); + /** @var array $sets */ + array_walk($sets, fn (&$val, $key) => $val = $key); return $sets; } -} \ No newline at end of file +} diff --git a/tests/FieldType/Icon/ValueTest.php b/tests/FieldType/Icon/ValueTest.php new file mode 100644 index 0000000..74b081a --- /dev/null +++ b/tests/FieldType/Icon/ValueTest.php @@ -0,0 +1,24 @@ +assertSame('icon1', $value->getIcon()); + $this->assertSame('icon1', (string) $value); + $value->setIcon(null); + $this->assertNull($value->getIcon()); + $this->assertSame('', (string) $value); + $value->setIcon('value2'); + $this->assertSame('value2', $value->getIcon()); + $this->assertSame('value2', (string) $value); + } +} diff --git a/tests/IconSet/IconSetManagerTest.php b/tests/IconSet/IconSetManagerTest.php new file mode 100644 index 0000000..b6b0ef5 --- /dev/null +++ b/tests/IconSet/IconSetManagerTest.php @@ -0,0 +1,64 @@ +assertSame($setList, $ism->getSetList()); + if (null !== $setName) { + $this->assertSame($iconList, $ism->getSet($setName)->getList()); + } + } + + public function validConfigsProvider(): \Generator + { + // Simple list + yield [ + ['set1' => ['items' => ['entry1', 'entry2']]], + ['set1' => 'set1'], + 'set1', + ['entry1' => 'entry1', 'entry2' => 'entry2'], + ]; + // Multiple lists + yield [ + ['set1' => ['items' => ['entry1', 'entry2']],'set2' => []], + ['set1' => 'set1', 'set2' => 'set2'], + 'set2', + [], + ]; + // List from files + yield [ + ['set1' => ['folder' => __DIR__.'/../_fixtures/iconlist']], + ['set1' => 'set1'], + 'set1', + ['icon1.svg' => 'icon1.svg', 'icon2.png' => 'icon2.png'], + ]; + // List from files with filter + yield [ + ['set1' => ['folder' => __DIR__.'/../_fixtures/iconlist', 'pattern' => '*.svg']], + ['set1' => 'set1'], + 'set1', + ['icon1.svg' => 'icon1.svg'], + ]; + // No lists + yield [[], []]; + } + + public function testInvalidSet(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('IconSet not found'); + $ism = new IconSetManager([]); + $ism->getSet('nope'); + } +} diff --git a/tests/_fixtures/iconlist/icon1.svg b/tests/_fixtures/iconlist/icon1.svg new file mode 100644 index 0000000..e69de29 diff --git a/tests/_fixtures/iconlist/icon2.png b/tests/_fixtures/iconlist/icon2.png new file mode 100644 index 0000000..e69de29