From bfeeb4553608b445179e70b8170f9bffa21d6a21 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 28 Feb 2024 15:05:39 +0100 Subject: [PATCH 01/13] dev commit for showcase --- .gitignore | 1 + composer.json | 17 ++- src/Component/ComponentItem.php | 14 +++ src/Component/ComponentItemFactory.php | 3 +- src/Configuration/ParserInterface.php | 8 ++ src/Configuration/YamlParser.php | 49 ++++++++ src/Controller/TwigDocController.php | 1 - .../Compiler/TwigDocPass.php | 76 ++++++++++++ src/DependencyInjection/Configuration.php | 9 +- src/DependencyInjection/TwigDocExtension.php | 2 + src/Service/ComponentService.php | 2 +- src/TwigDocBundle.php | 9 ++ templates/blocks/component_blocks.html.twig | 14 +++ templates/blocks/page_blocks.html.twig | 75 ++++++++++++ templates/component.html.twig | 19 ++- templates/component/_item.html.twig | 6 +- templates/component/_viewport.html.twig | 4 +- templates/documentation.html.twig | 108 ++---------------- 18 files changed, 290 insertions(+), 127 deletions(-) create mode 100644 src/Configuration/ParserInterface.php create mode 100644 src/Configuration/YamlParser.php create mode 100644 src/DependencyInjection/Compiler/TwigDocPass.php create mode 100644 templates/blocks/component_blocks.html.twig create mode 100644 templates/blocks/page_blocks.html.twig diff --git a/.gitignore b/.gitignore index 7c6c1fb..40bd46a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /var/ /vendor/ .phpunit.cache +.phpunit.result.cache composer.lock diff --git a/composer.json b/composer.json index 64e4816..0810405 100644 --- a/composer.json +++ b/composer.json @@ -15,13 +15,22 @@ }, "require": { "php": ">=8.2", - "symfony/framework-bundle": "^5.4|^6.0|^7.0", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", "symfony/validator": "^5.4|^6.0|^7.0", - "symfony/twig-bundle": "^5.4|^6.0|^7.0" + "symfony/twig-bundle": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "require-dev": { "phpunit/phpunit": "^10.5", - "symfony/browser-kit": "^5.4|^6.0|^7.0", - "symfony/css-selector": "^5.4|^6.0|^7.0" + "symfony/dom-crawler": "^5.4|^6.0|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0" } } diff --git a/src/Component/ComponentItem.php b/src/Component/ComponentItem.php index db9044a..f65766e 100644 --- a/src/Component/ComponentItem.php +++ b/src/Component/ComponentItem.php @@ -24,6 +24,8 @@ class ComponentItem private array $variations; #[Assert\Valid] private ComponentCategory $category; + #[Assert\Length(max: 4096)] + private ?string $projectPath = null; public function getName(): string { @@ -134,4 +136,16 @@ public function getMainCategory(): ComponentCategory { return $this->category->getParent() ?? $this->category; } + + public function getProjectPath(): ?string + { + return $this->projectPath; + } + + public function setProjectPath(?string $projectPath): ComponentItem + { + $this->projectPath = $projectPath; + + return $this; + } } diff --git a/src/Component/ComponentItemFactory.php b/src/Component/ComponentItemFactory.php index 802a54d..e8509de 100644 --- a/src/Component/ComponentItemFactory.php +++ b/src/Component/ComponentItemFactory.php @@ -52,7 +52,8 @@ private function createItem(array $data): ComponentItem ->setDescription($data['description'] ?? '') ->setTags($data['tags'] ?? []) ->setParameters($data['parameters'] ?? []) - ->setVariations($data['variations'] ?? []); + ->setVariations($data['variations'] ?? []) + ->setProjectPath($data['path'] ?? null); return $item; } diff --git a/src/Configuration/ParserInterface.php b/src/Configuration/ParserInterface.php new file mode 100644 index 0000000..4288eff --- /dev/null +++ b/src/Configuration/ParserInterface.php @@ -0,0 +1,8 @@ +fixIndentation($data); + + return Yaml::parse($content); + } + + private function fixIndentation(string $content): string + { + $fileObject = new \SplFileObject('php://memory', 'r+'); + $fileObject->fwrite($content); + $fileObject->rewind(); + + $firstLineDetected = false; + $indentationWhitespace = null; + + $lines = []; + + while ($fileObject->valid()) { + $line = $fileObject->current(); + if (empty(trim($line))) { + $fileObject->next(); + continue; + } + if ($firstLineDetected === false) { + $firstLineDetected = true; + # check for whitespaces at the beginning + if (!preg_match('#^(\s+)#', $line, $matches)) { + # no leading whitespaces, indentation seems to be fine + return $fileObject; + } + $indentationWhitespace = $matches[1]; + } + $line = substr($line, strlen($indentationWhitespace)); + $lines[] = $line; + $fileObject->next(); + } + + return implode("\n", $lines); + } +} diff --git a/src/Controller/TwigDocController.php b/src/Controller/TwigDocController.php index 1f3dc96..ce9ec12 100644 --- a/src/Controller/TwigDocController.php +++ b/src/Controller/TwigDocController.php @@ -8,7 +8,6 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Profiler\Profiler; use Twig\Environment; -use Qossmic\TwigDocBundle\Component\ComponentItem; use Qossmic\TwigDocBundle\Service\ComponentService; class TwigDocController diff --git a/src/DependencyInjection/Compiler/TwigDocPass.php b/src/DependencyInjection/Compiler/TwigDocPass.php new file mode 100644 index 0000000..3276427 --- /dev/null +++ b/src/DependencyInjection/Compiler/TwigDocPass.php @@ -0,0 +1,76 @@ +hasExtension('twig_doc')) { + return; + } + $config = $container->getParameter('twig_doc.config'); + $definition = $container->getDefinition('twig_doc.service.component'); + $componentConfig = $definition->getArgument('$componentsConfig'); + $projectDir = $container->getParameter('kernel.project_dir'); + + $twigPath = $container->getParameter('twig.default_path') . '/components'; + + $paths = [$twigPath]; + + $finder = new Finder(); + foreach ($finder->in($paths)->files() as $file) { + if (!$file->isFile()) { + continue; + } + + $doc = $this->parseDoc($file, $config['doc_identifier']); + + if ($doc === null) { + continue; + } + $filename = $file->getFilename(); + $componentName = substr($filename, 0, strpos($filename, '.')); + + if (array_filter($componentConfig, fn(array $data) => $data['name'] === $componentName)) { + throw new InvalidConfigException( + sprintf('component %s is configured twice, please configure either directly in the template or the general bundle configuration', $componentName)); + } + $itemConfig = [ + 'name' => $componentName, + 'path' => str_replace($projectDir.'/', '', $file->getRealPath()), + ]; + $componentConfig[] = array_merge($itemConfig, $doc); + } + + $definition->replaceArgument('$componentsConfig', $componentConfig); + + $container->getParameterBag()->remove('twig_doc.config'); + } + + private function parseDoc(SplFileInfo $file, string $docIdentifier): null|array + { + $content = $file->getContents(); + + $pattern = sprintf("/\{#%s\s(.*)%s#}/s", $docIdentifier, $docIdentifier); + + preg_match($pattern, $content, $matches); + + if (!isset($matches[1])) { + return null; + } + + return $this->parser->parse($matches[1]); + } +} diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 233019b..f170f7a 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -7,7 +7,6 @@ class Configuration implements ConfigurationInterface { - /** * @inheritDoc */ @@ -17,6 +16,12 @@ public function getConfigTreeBuilder(): TreeBuilder $treeBuilder->getRootNode() ->children() + ->scalarNode('doc_identifier')->defaultValue('TWIG_DOC') + ->validate() + ->ifTrue(fn($v) => !preg_match('#^\w+$#', $v)) + ->thenInvalid('The documentation identifier must match \w (regex)') + ->end() + ->end() ->arrayNode('categories') ->arrayPrototype() ->children() @@ -27,7 +32,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->end() ->end() - ->arrayNode('components') + ->arrayNode('components')->defaultValue([]) ->arrayPrototype() ->children() ->scalarNode('name')->isRequired()->cannotBeEmpty()->end() diff --git a/src/DependencyInjection/TwigDocExtension.php b/src/DependencyInjection/TwigDocExtension.php index c4ecf84..abe614f 100644 --- a/src/DependencyInjection/TwigDocExtension.php +++ b/src/DependencyInjection/TwigDocExtension.php @@ -18,6 +18,8 @@ public function load(array $configs, ContainerBuilder $container) $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); + $container->setParameter('twig_doc.config', $config); + $loader = new PhpFileLoader($container, new FileLocator(__DIR__ . '/../../config')); $loader->load('documentation.php'); diff --git a/src/Service/ComponentService.php b/src/Service/ComponentService.php index 356ac32..4020c48 100644 --- a/src/Service/ComponentService.php +++ b/src/Service/ComponentService.php @@ -27,7 +27,7 @@ class ComponentService public function __construct( private readonly ComponentItemFactory $itemFactory, - private readonly array $componentsConfig, + private readonly array $componentsConfig ) { $this->parse(); diff --git a/src/TwigDocBundle.php b/src/TwigDocBundle.php index 2e6a17f..e168b0a 100644 --- a/src/TwigDocBundle.php +++ b/src/TwigDocBundle.php @@ -3,10 +3,19 @@ namespace Qossmic\TwigDocBundle; +use Qossmic\TwigDocBundle\Configuration\YamlParser; +use Qossmic\TwigDocBundle\DependencyInjection\Compiler\TwigDocPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; class TwigDocBundle extends Bundle { + public function build(ContainerBuilder $container): void + { + parent::build($container); + + $container->addCompilerPass(new TwigDocPass(new YamlParser())); + } public function getPath(): string { return __DIR__.'/..'; diff --git a/templates/blocks/component_blocks.html.twig b/templates/blocks/component_blocks.html.twig new file mode 100644 index 0000000..55a0d6c --- /dev/null +++ b/templates/blocks/component_blocks.html.twig @@ -0,0 +1,14 @@ +{% block head %} + {% block stylesheets %} + {% endblock %} + {% block title %}Twig Component Documentation{% endblock %} + {% block javascripts %} + {% endblock %} + +{% endblock %} + +{% block component %} + {% for i in 1..quantity %} + {{ renderComponent(component, componentData ?? [])|raw }} + {% endfor %} +{% endblock%} diff --git a/templates/blocks/page_blocks.html.twig b/templates/blocks/page_blocks.html.twig new file mode 100644 index 0000000..b17b065 --- /dev/null +++ b/templates/blocks/page_blocks.html.twig @@ -0,0 +1,75 @@ +{% block head %} +{% block stylesheets %} + +{% endblock %} + {% block title %}Twig Component Documentation{% endblock %} +{% block javascripts %} + +{% endblock %} +{% endblock %} + +{% block body %} +
+ + {% include '@TwigDoc/component/_search.html.twig' %} + + {% if not components %} +
+ No components found +
+ {% endif %} +
+ + + + {% for items in components %} +
+ {% for item in items %} + {% include '@TwigDoc/component/_item.html.twig' with { component: item } %} + {% endfor %} +
+ {% endfor %} +
+
+{% endblock %} diff --git a/templates/component.html.twig b/templates/component.html.twig index a1ebd6f..2c6c260 100644 --- a/templates/component.html.twig +++ b/templates/component.html.twig @@ -1,16 +1,11 @@ - -{% block head %} - {% block stylesheets %} - {% endblock %} - {% block title %}Twig Component Documentation{% endblock %} - {% block javascripts %} - {% endblock %} - -{% endblock %} +{% use '@TwigDoc/blocks/component_blocks.html.twig' %} + + + + {{ block('head') }} + - {% for i in 1..quantity %} - {{ renderComponent(component, componentData ?? [])|raw }} - {% endfor %} + {{ block('component') }} diff --git a/templates/component/_item.html.twig b/templates/component/_item.html.twig index c6f79d8..c5565c7 100644 --- a/templates/component/_item.html.twig +++ b/templates/component/_item.html.twig @@ -1,6 +1,6 @@
-

{{ component.title }} ({{ component.name ~ '.html.twig' }})

+

{{ component.title }} ({{ component.projectPath ?? component.name ~ '.html.twig' }})

Description

@@ -52,9 +52,9 @@ {% endfor %} {% for name, variation in component.variations %}
- +
Viewport size: px
- {% include '@TwigDoc/component/_viewport.html.twig' with {component: component, componentData: variation ?? [], width: '1280px'} %} + {% include '@TwigDoc/component/_viewport.html.twig' with {component: component, componentData: variation ?? []} %}
    diff --git a/templates/component/_viewport.html.twig b/templates/component/_viewport.html.twig index 49fd312..ac3c1a2 100644 --- a/templates/component/_viewport.html.twig +++ b/templates/component/_viewport.html.twig @@ -1,6 +1,6 @@ -
    +
    -
    \ No newline at end of file +
    diff --git a/templates/documentation.html.twig b/templates/documentation.html.twig index f596ad7..f60a7c1 100644 --- a/templates/documentation.html.twig +++ b/templates/documentation.html.twig @@ -1,104 +1,10 @@ +{% use '@TwigDoc/blocks/blocks.html.twig' %} + -{% block head %} -{% block stylesheets %} - -{% endblock %} - {% block title %}Twig Component Documentation{% endblock %} -{% block javascripts %} - -{% endblock %} -{% endblock %} - - + + {{ block('head') }} + -{% block body %} -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - - {% include '@TwigDoc/component/_search.html.twig' %} - - {% if not components %} -
    - No components found -
    - {% endif %} -
    - - {# - - - #} - - - - {% for items in components %} -
    - {% for item in items %} - {% include '@TwigDoc/component/_item.html.twig' with { component: item } %} - {% endfor %} -
    - {% endfor %} -
    -
    -{% endblock %} + {{ block('body') }} + From 373cd25d0062f7a56b1387923b78cc31a9e08243 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 6 Mar 2024 14:37:33 +0100 Subject: [PATCH 02/13] add tests - minor fixes - directories can now be configured --- composer.json | 11 +- phpunit.xml.dist | 4 +- src/Component/ComponentItem.php | 14 +++ src/Component/ComponentItemFactory.php | 22 +++- src/Configuration/YamlParser.php | 2 +- src/Controller/TwigDocController.php | 5 +- ...DocPass.php => TwigDocCollectDocsPass.php} | 36 +++--- src/DependencyInjection/Configuration.php | 5 +- src/Service/ComponentService.php | 4 +- src/Twig/TwigDocExtension.php | 10 +- src/TwigDocBundle.php | 5 +- templates/blocks/component_blocks.html.twig | 1 + templates/component/_item.html.twig | 2 +- templates/documentation.html.twig | 2 +- .../Controller/TwigDocControllerTest.php | 52 +++++++- .../Service/ComponentItemFactoryTest.php | 15 +-- .../Service/ComponentServiceTest.php | 114 ++++++++++++++++++ .../Functional/Twig/TwigDocExtensionTest.php | 43 +++++++ tests/TestApp/Kernel.php | 1 + tests/TestApp/config/packages/twig_doc.php | 7 +- tests/TestApp/config/services.php | 4 + .../templates/components/Button.html.twig | 13 ++ .../{ => components}/ButtonAction.html.twig | 0 .../{ => components}/ButtonSubmit.html.twig | 0 .../templates/snippets/snippet.html.twig | 14 +++ .../Component/ComponentItemFactoryTest.php | 30 +++++ tests/Unit/Configuration/YamlParserTest.php | 72 +++++++++++ .../Compiler/TwigDocCollectDocsPassTest.php | 75 ++++++++++++ .../DependencyInjection/ConfigurationTest.php | 20 ++- .../TwigDocExtensionTest.php | 43 +++++++ tests/bootstrap.php | 14 +++ 31 files changed, 583 insertions(+), 57 deletions(-) rename src/DependencyInjection/Compiler/{TwigDocPass.php => TwigDocCollectDocsPass.php} (62%) create mode 100644 tests/Functional/Service/ComponentServiceTest.php create mode 100644 tests/Functional/Twig/TwigDocExtensionTest.php create mode 100644 tests/TestApp/templates/components/Button.html.twig rename tests/TestApp/templates/{ => components}/ButtonAction.html.twig (100%) rename tests/TestApp/templates/{ => components}/ButtonSubmit.html.twig (100%) create mode 100644 tests/TestApp/templates/snippets/snippet.html.twig create mode 100644 tests/Unit/Configuration/YamlParserTest.php create mode 100644 tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php create mode 100644 tests/Unit/DependencyInjection/TwigDocExtensionTest.php create mode 100644 tests/bootstrap.php diff --git a/composer.json b/composer.json index 0810405..28fc9e2 100644 --- a/composer.json +++ b/composer.json @@ -15,13 +15,7 @@ }, "require": { "php": ">=8.2", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0", "symfony/routing": "^5.4|^6.0|^7.0", "symfony/validator": "^5.4|^6.0|^7.0", "symfony/twig-bundle": "^5.4|^6.0|^7.0", @@ -31,6 +25,7 @@ "phpunit/phpunit": "^10.5", "symfony/dom-crawler": "^5.4|^6.0|^7.0", "symfony/css-selector": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0" + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/browser-kit": "5.4|^6.0|^7.0" } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 68e3ccf..b435231 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ - + diff --git a/src/Component/ComponentItem.php b/src/Component/ComponentItem.php index f65766e..d912971 100644 --- a/src/Component/ComponentItem.php +++ b/src/Component/ComponentItem.php @@ -26,6 +26,8 @@ class ComponentItem private ComponentCategory $category; #[Assert\Length(max: 4096)] private ?string $projectPath = null; + #[Assert\Length(max: 4096)] + private ?string $renderPath = null; public function getName(): string { @@ -148,4 +150,16 @@ public function setProjectPath(?string $projectPath): ComponentItem return $this; } + + public function getRenderPath(): ?string + { + return $this->renderPath; + } + + public function setRenderPath(?string $renderPath): ComponentItem + { + $this->renderPath = $renderPath; + + return $this; + } } diff --git a/src/Component/ComponentItemFactory.php b/src/Component/ComponentItemFactory.php index e8509de..7ca0e1c 100644 --- a/src/Component/ComponentItemFactory.php +++ b/src/Component/ComponentItemFactory.php @@ -53,8 +53,28 @@ private function createItem(array $data): ComponentItem ->setTags($data['tags'] ?? []) ->setParameters($data['parameters'] ?? []) ->setVariations($data['variations'] ?? []) - ->setProjectPath($data['path'] ?? null); + ->setProjectPath($data['path'] ?? null) + ->setRenderPath($data['renderPath'] ?? null); return $item; } + + public function getParamsFromVariables(array $variables): array + { + $r = []; + foreach ($variables as $dotted) { + $keys = explode('.', $dotted); + $c = &$r[array_shift($keys)]; + foreach ($keys as $key) { + if (isset($c[$key]) && $c[$key] === true) { + $c[$key] = []; + } + $c = &$c[$key]; + } + if ($c === null) { + $c = 'Scalar'; + } + } + return $r; + } } diff --git a/src/Configuration/YamlParser.php b/src/Configuration/YamlParser.php index 1165b24..c3d60f8 100644 --- a/src/Configuration/YamlParser.php +++ b/src/Configuration/YamlParser.php @@ -35,7 +35,7 @@ private function fixIndentation(string $content): string # check for whitespaces at the beginning if (!preg_match('#^(\s+)#', $line, $matches)) { # no leading whitespaces, indentation seems to be fine - return $fileObject; + return $content; } $indentationWhitespace = $matches[1]; } diff --git a/src/Controller/TwigDocController.php b/src/Controller/TwigDocController.php index ce9ec12..f52578a 100644 --- a/src/Controller/TwigDocController.php +++ b/src/Controller/TwigDocController.php @@ -52,11 +52,8 @@ public function componentView(Request $request): Response if (!$component) { throw new NotFoundHttpException(sprintf('Component %s is unknown', $name)); } - $breakpoint = $request->query->get('breakpoint'); // disable profiler to get rid of toolbar in dev - if($this->profiler) { - $this->profiler->disable(); - } + $this->profiler?->disable(); return new Response( $this->twig->render('@TwigDoc/component.html.twig', [ 'component' => $component, diff --git a/src/DependencyInjection/Compiler/TwigDocPass.php b/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php similarity index 62% rename from src/DependencyInjection/Compiler/TwigDocPass.php rename to src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php index 3276427..443211e 100644 --- a/src/DependencyInjection/Compiler/TwigDocPass.php +++ b/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php @@ -9,54 +9,62 @@ use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; -class TwigDocPass implements CompilerPassInterface +class TwigDocCollectDocsPass implements CompilerPassInterface { public function __construct(private readonly ParserInterface $parser) { } - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasExtension('twig_doc')) { return; } $config = $container->getParameter('twig_doc.config'); + $container->getParameterBag()->remove('twig_doc.config'); $definition = $container->getDefinition('twig_doc.service.component'); $componentConfig = $definition->getArgument('$componentsConfig'); $projectDir = $container->getParameter('kernel.project_dir'); + $templateDir = $container->getParameter('twig.default_path'); + $defaultComponentsDirectory = $container->getParameter('twig.default_path') . '/components'; + if (!in_array($defaultComponentsDirectory, $config['directories'])) { + $config['directories'][] = $defaultComponentsDirectory; + } + foreach ($config['directories'] as $idx => $dir) { + if (!is_dir($dir)) { + unset($config['directories'][$idx]); + } + } + $config['directories'] = array_unique($config['directories']); - $twigPath = $container->getParameter('twig.default_path') . '/components'; - - $paths = [$twigPath]; + if (empty($config['directories'])) { + return; + } $finder = new Finder(); - foreach ($finder->in($paths)->files() as $file) { - if (!$file->isFile()) { - continue; - } - + foreach ($finder->in($config['directories'])->files()->filter(fn(SplFileInfo $file) => $file->getExtension() === 'twig') as $file) { $doc = $this->parseDoc($file, $config['doc_identifier']); if ($doc === null) { continue; } + $filename = $file->getFilename(); $componentName = substr($filename, 0, strpos($filename, '.')); if (array_filter($componentConfig, fn(array $data) => $data['name'] === $componentName)) { throw new InvalidConfigException( - sprintf('component %s is configured twice, please configure either directly in the template or the general bundle configuration', $componentName)); + sprintf('component "%s" is configured twice, please configure either directly in the template or the general bundle configuration', $componentName)); } $itemConfig = [ 'name' => $componentName, - 'path' => str_replace($projectDir.'/', '', $file->getRealPath()), + 'path' => str_replace($projectDir . '/', '', $file->getRealPath()), + 'renderPath' => str_replace($templateDir . '/', '', $file->getRealPath()), ]; $componentConfig[] = array_merge($itemConfig, $doc); } $definition->replaceArgument('$componentsConfig', $componentConfig); - - $container->getParameterBag()->remove('twig_doc.config'); } private function parseDoc(SplFileInfo $file, string $docIdentifier): null|array diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index f170f7a..6abe676 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -19,9 +19,12 @@ public function getConfigTreeBuilder(): TreeBuilder ->scalarNode('doc_identifier')->defaultValue('TWIG_DOC') ->validate() ->ifTrue(fn($v) => !preg_match('#^\w+$#', $v)) - ->thenInvalid('The documentation identifier must match \w (regex)') + ->thenInvalid('The twig_doc documentation identifier must match \w (regex)') ->end() ->end() + ->arrayNode('directories')->defaultValue(['%twig.default_path%/components']) + ->scalarPrototype()->end() + ->end() ->arrayNode('categories') ->arrayPrototype() ->children() diff --git a/src/Service/ComponentService.php b/src/Service/ComponentService.php index 4020c48..2874aa6 100644 --- a/src/Service/ComponentService.php +++ b/src/Service/ComponentService.php @@ -125,7 +125,7 @@ private function filterComponents(string $filterQuery, string $filterType): arra } /** - * @return ComponentItem[] + * @return ComponentInvalid[] */ public function getInvalidComponents(): array { @@ -134,6 +134,6 @@ public function getInvalidComponents(): array public function getComponent(string $name): ?ComponentItem { - return array_values($this->filterComponents($name, 'name'))[0] ?? null; + return array_values(array_filter($this->components, fn (ComponentItem $c) => $c->getName() === $name))[0] ?? null; } } diff --git a/src/Twig/TwigDocExtension.php b/src/Twig/TwigDocExtension.php index 09a645f..abefe7a 100644 --- a/src/Twig/TwigDocExtension.php +++ b/src/Twig/TwigDocExtension.php @@ -38,6 +38,12 @@ public function getFunctions(): array ]; } + /** + * @param string $filterQuery + * @param string|null $type + * @return array + * @codeCoverageIgnore + */ public function filterComponents(string $filterQuery, string $type = null): array { return $this->componentService->filter($filterQuery, $type); @@ -59,6 +65,7 @@ public function renderComponent(ComponentItem $item, array $params): string /** * @return ComponentInvalid[] + * @codeCoverageIgnore */ public function getInvalidComponents(): array { @@ -67,6 +74,7 @@ public function getInvalidComponents(): array /** * @return ComponentCategory[] + * @codeCoverageIgnore */ public function getSubCategories(string $mainCategoryName = null): array { @@ -80,6 +88,6 @@ public function getSubCategories(string $mainCategoryName = null): array */ private function renderFallback(ComponentItem $item, array $params): string { - return $this->twig->render($item->getName().'.html.twig', $params); + return $this->twig->render($item->getRenderPath(), $params); } } diff --git a/src/TwigDocBundle.php b/src/TwigDocBundle.php index e168b0a..4844ecc 100644 --- a/src/TwigDocBundle.php +++ b/src/TwigDocBundle.php @@ -4,7 +4,7 @@ namespace Qossmic\TwigDocBundle; use Qossmic\TwigDocBundle\Configuration\YamlParser; -use Qossmic\TwigDocBundle\DependencyInjection\Compiler\TwigDocPass; +use Qossmic\TwigDocBundle\DependencyInjection\Compiler\TwigDocCollectDocsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -14,8 +14,9 @@ public function build(ContainerBuilder $container): void { parent::build($container); - $container->addCompilerPass(new TwigDocPass(new YamlParser())); + $container->addCompilerPass(new TwigDocCollectDocsPass(new YamlParser())); } + public function getPath(): string { return __DIR__.'/..'; diff --git a/templates/blocks/component_blocks.html.twig b/templates/blocks/component_blocks.html.twig index 55a0d6c..358c945 100644 --- a/templates/blocks/component_blocks.html.twig +++ b/templates/blocks/component_blocks.html.twig @@ -1,3 +1,4 @@ +{% block language %}en{% endblock %} {% block head %} {% block stylesheets %} {% endblock %} diff --git a/templates/component/_item.html.twig b/templates/component/_item.html.twig index c5565c7..1155970 100644 --- a/templates/component/_item.html.twig +++ b/templates/component/_item.html.twig @@ -1,6 +1,6 @@
    -

    {{ component.title }} ({{ component.projectPath ?? component.name ~ '.html.twig' }})

    +

    {{ component.title }} ({{ component.projectPath ?? (component.name ~ '.html.twig') }})

    Description

    diff --git a/templates/documentation.html.twig b/templates/documentation.html.twig index f60a7c1..b97d324 100644 --- a/templates/documentation.html.twig +++ b/templates/documentation.html.twig @@ -1,4 +1,4 @@ -{% use '@TwigDoc/blocks/blocks.html.twig' %} +{% use '@TwigDoc/blocks/page_blocks.html.twig' %} diff --git a/tests/Functional/Controller/TwigDocControllerTest.php b/tests/Functional/Controller/TwigDocControllerTest.php index a414896..06742e1 100644 --- a/tests/Functional/Controller/TwigDocControllerTest.php +++ b/tests/Functional/Controller/TwigDocControllerTest.php @@ -9,8 +9,10 @@ use Qossmic\TwigDocBundle\Service\CategoryService; use Qossmic\TwigDocBundle\Service\ComponentService; use Qossmic\TwigDocBundle\Twig\TwigDocExtension; +use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; #[CoversClass(TwigDocController::class)] #[UsesClass(ComponentItemFactory::class)] @@ -19,10 +21,17 @@ #[UsesClass(TwigDocExtension::class)] class TwigDocControllerTest extends WebTestCase { + private KernelBrowser $client; + + protected function setUp(): void + { + parent::setUp(); + + $this->client = static::createClient(); + } public function testIndexReturnsStatus200(): void { - $client = self::createClient(); - $crawler = $client->request(Request::METHOD_GET, '/'); + $crawler = $this->client->request(Request::METHOD_GET, '/'); static::assertResponseIsSuccessful(); static::assertCount(1, $crawler->filter('button.btn-primary')); @@ -30,8 +39,7 @@ public function testIndexReturnsStatus200(): void public function testFilterComponents(): void { - $client = self::createClient(); - $crawler = $client->request(Request::METHOD_GET, '/', ['filterQuery' => 'ButtonSubmit', 'filterType' => 'name']); + $crawler = $this->client->request(Request::METHOD_GET, '/', ['filterQuery' => 'ButtonSubmit', 'filterType' => 'name']); $node = $crawler->filter('div.twig-doc-component'); static::assertResponseIsSuccessful(); @@ -41,11 +49,43 @@ public function testFilterComponents(): void public function testInvalidComponentsRoute(): void { - $client = self::createClient(); - $crawler = $client->request(Request::METHOD_GET, '/invalid'); + $crawler = $this->client->request(Request::METHOD_GET, '/invalid'); $node = $crawler->filter('div.error > h2'); static::assertResponseIsSuccessful(); static::assertEquals('InvalidComponent', $node->getNode(0)->nodeValue); } + + public function testComponentViewRoute(): void + { + $crawler = $this->client->request( + Request::METHOD_GET, + '/component-view', + [ + 'quantity' => 1, + 'name' => 'Button', + 'data' => [ + 'type' => 'primary', + 'text' => 'btn-text' + ] + ] + ); + + $node = $crawler->filter('button.btn-primary'); + static::assertResponseIsSuccessful(); + static::assertEquals('btn-text', $node->getNode(0)->nodeValue); + } + + public function testComponentViewRouteReturns404(): void + { + $this->client->request( + Request::METHOD_GET, + '/component-view', + [ + 'name' => 'notExistingComponent' + ] + ); + + static::assertResponseStatusCodeSame(404); + } } diff --git a/tests/Functional/Service/ComponentItemFactoryTest.php b/tests/Functional/Service/ComponentItemFactoryTest.php index b862200..3f73520 100644 --- a/tests/Functional/Service/ComponentItemFactoryTest.php +++ b/tests/Functional/Service/ComponentItemFactoryTest.php @@ -2,14 +2,17 @@ namespace Qossmic\TwigDocBundle\Tests\Functional\Service; -use PHPUnit\Framework\Attributes\CoversNothing; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\UsesClass; use Qossmic\TwigDocBundle\Component\ComponentItemFactory; use Qossmic\TwigDocBundle\Exception\InvalidComponentConfigurationException; +use Qossmic\TwigDocBundle\Service\CategoryService; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use TypeError; -#[CoversNothing] +#[CoversClass(ComponentItemFactory::class)] +#[UsesClass(CategoryService::class)] class ComponentItemFactoryTest extends KernelTestCase { #[DataProvider('getInvalidComponentConfigurationTestCases')] @@ -34,14 +37,6 @@ public static function getInvalidComponentConfigurationTestCases(): iterable ] ]; - yield [ - [ - 'name' => 'InvalidComponentMissingTitleAndDescription', - 'category' => 'MainCategory', - 'title' => 'Component title' - ] - ]; - yield [ [ 'name' => 'InvalidComponentMissingDescription', diff --git a/tests/Functional/Service/ComponentServiceTest.php b/tests/Functional/Service/ComponentServiceTest.php new file mode 100644 index 0000000..b215c07 --- /dev/null +++ b/tests/Functional/Service/ComponentServiceTest.php @@ -0,0 +1,114 @@ +get(ComponentService::class); + + $components = $service->filter($query, $type); + + foreach ($expectedCounts as $category => $count) { + static::assertCount($count, $components[$category]); + } + } + + public function testGetComponent(): void + { + $service = static::getContainer()->get(ComponentService::class); + + $component = $service->getComponent('ButtonAction'); + + static::assertInstanceOf(ComponentItem::class, $component); + static::assertEquals('ButtonAction', $component->getName()); + } + + public function testGetComponentsByCategory(): void + { + $service = static::getContainer()->get(ComponentService::class); + + $result = $service->getComponentsByCategory('MainCategory'); + + static::assertCount(4, $result); + } + + public function testGetCategories() + { + $service = static::getContainer()->get(ComponentService::class); + + $categories = $service->getCategories(); + + static::assertCount(1, $categories); + } + + public function testGetInvalidComponents() + { + $service = static::getContainer()->get(ComponentService::class); + + $invalid = $service->getInvalidComponents(); + + static::assertCount(1, $invalid); + foreach ($invalid as $component) { + static::assertInstanceOf(ComponentInvalid::class, $component); + } + } + + public static function getFilterTestCases(): iterable + { + yield 'name' => [ + 'query' => 'button', + 'type' => 'name', + 'expectedCounts' => [ + 'MainCategory' => 3 + ] + ]; + + yield 'category' => [ + 'query' => 'MainCategory', + 'type' => 'category', + 'expectedCounts' => [ + 'MainCategory' => 4 + ] + ]; + + yield 'sub_category' => [ + 'query' => 'SubCategory2', + 'type' => 'sub_category', + 'expectedCounts' => [ + 'MainCategory' => 1 + ] + ]; + + yield 'tags' => [ + 'query' => 'snippet', + 'type' => 'tags', + 'expectedCounts' => [ + 'MainCategory' => 1 + ] + ]; + + yield 'any' => [ + 'query' => 'action', + 'type' => '', + 'expectedCounts' => [ + 'MainCategory' => 1 + ] + ]; + } +} diff --git a/tests/Functional/Twig/TwigDocExtensionTest.php b/tests/Functional/Twig/TwigDocExtensionTest.php new file mode 100644 index 0000000..9880a3c --- /dev/null +++ b/tests/Functional/Twig/TwigDocExtensionTest.php @@ -0,0 +1,43 @@ +get(TwigDocExtension::class); + $functions = $extension->getFunctions(); + + static::assertCount(4, $functions); + + foreach ($functions as $function) { + static::assertInstanceOf(TwigFunction::class, $function); + } + } + + public function testRenderComponentUsesFallbackWhenUXComponentsMissing(): void + { + $componentService = static::getContainer()->get(ComponentService::class); + $extension = static::getContainer()->get(TwigDocExtension::class); + + $result = $extension->renderComponent( + $componentService->getComponent('Button'), ['type' => 'primary', 'text' => 'some text'] + ); + + static::assertIsString($result); + } +} diff --git a/tests/TestApp/Kernel.php b/tests/TestApp/Kernel.php index 34224ef..0cf54f1 100644 --- a/tests/TestApp/Kernel.php +++ b/tests/TestApp/Kernel.php @@ -3,6 +3,7 @@ namespace Qossmic\TwigDocBundle\Tests\TestApp; +use Psr\Log\NullLogger; use Qossmic\TwigDocBundle\TwigDocBundle; use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; diff --git a/tests/TestApp/config/packages/twig_doc.php b/tests/TestApp/config/packages/twig_doc.php index 73cc784..70e8ce5 100644 --- a/tests/TestApp/config/packages/twig_doc.php +++ b/tests/TestApp/config/packages/twig_doc.php @@ -1,14 +1,17 @@ [ + '%twig.default_path%/snippets', + ], 'categories' => [ [ 'name' => 'MainCategory', 'sub_categories' => [ 'SubCategory1', 'SubCategory2', - ] - ] + ], + ], ], 'components' => [ [ diff --git a/tests/TestApp/config/services.php b/tests/TestApp/config/services.php index 0889b7f..044edda 100644 --- a/tests/TestApp/config/services.php +++ b/tests/TestApp/config/services.php @@ -1,5 +1,6 @@ defaults() ->autowire() ->autoconfigure(); + // add NullLogger due to http-exceptions not being caught, see https://github.com/symfony/symfony/issues/28023 + $container->services() + ->set('logger', NullLogger::class); }; diff --git a/tests/TestApp/templates/components/Button.html.twig b/tests/TestApp/templates/components/Button.html.twig new file mode 100644 index 0000000..e0bdbff --- /dev/null +++ b/tests/TestApp/templates/components/Button.html.twig @@ -0,0 +1,13 @@ +{#TWIG_DOC + title: Button + description: Button description + category: MainCategory + parameters: + type: String + text: String + variations: + default: + type: primary + text: Default button +TWIG_DOC#} + diff --git a/tests/TestApp/templates/ButtonAction.html.twig b/tests/TestApp/templates/components/ButtonAction.html.twig similarity index 100% rename from tests/TestApp/templates/ButtonAction.html.twig rename to tests/TestApp/templates/components/ButtonAction.html.twig diff --git a/tests/TestApp/templates/ButtonSubmit.html.twig b/tests/TestApp/templates/components/ButtonSubmit.html.twig similarity index 100% rename from tests/TestApp/templates/ButtonSubmit.html.twig rename to tests/TestApp/templates/components/ButtonSubmit.html.twig diff --git a/tests/TestApp/templates/snippets/snippet.html.twig b/tests/TestApp/templates/snippets/snippet.html.twig new file mode 100644 index 0000000..e8e8a66 --- /dev/null +++ b/tests/TestApp/templates/snippets/snippet.html.twig @@ -0,0 +1,14 @@ +{#TWIG_DOC + title: snippet + description: snippet description + category: MainCategory + sub_category: SubCategory2 + tags: + - snippet + parameters: + value: String + variations: + default: + value: dataValue +TWIG_DOC#} +
    Snippet
    diff --git a/tests/Unit/Component/ComponentItemFactoryTest.php b/tests/Unit/Component/ComponentItemFactoryTest.php index 806ab29..a4717cb 100644 --- a/tests/Unit/Component/ComponentItemFactoryTest.php +++ b/tests/Unit/Component/ComponentItemFactoryTest.php @@ -56,6 +56,36 @@ public function testInvalidCategory() $componentItemFactory->create(['category' => 'Category']); } + public function testGetParamsFromVariables(): void + { + $variables = [ + 'var.separated.by.dots', + 'second', + 'third.param' + ]; + + $componentItemFactory = new ComponentItemFactory( + static::createMock(ValidatorInterface::class), + static::createMock(CategoryService::class) + ); + + $result = $componentItemFactory->getParamsFromVariables($variables); + + static::assertEquals([ + 'var'=> [ + 'separated' => [ + 'by' => [ + 'dots' => 'Scalar' + ] + ] + ], + 'second' => 'Scalar', + 'third' => [ + 'param' => 'Scalar' + ], + ], $result); + } + public static function getValidComponents(): iterable { yield 'Component without sub-category' => [ diff --git a/tests/Unit/Configuration/YamlParserTest.php b/tests/Unit/Configuration/YamlParserTest.php new file mode 100644 index 0000000..57f347f --- /dev/null +++ b/tests/Unit/Configuration/YamlParserTest.php @@ -0,0 +1,72 @@ +parse($yaml); + } + + #[DataProvider('getIndentationTestCases')] + public function testParseFixesIndentation(string $yaml, array $expected): void + { + $parser = new YamlParser(); + + $result = $parser->parse($yaml); + + static::assertEquals($expected, $result); + } + + public static function getIndentationTestCases(): iterable + { + yield 'simple key-value test' => [ + 'yaml' => " key: yaml ain't markup language", + 'expected' => ["key" => "yaml ain't markup language"] + ]; + + yield 'indentationFix' => [ + 'yaml' => << 'value', + 'otherKey' => [ + 'sub' => 'subValue' + ] + ] + ]; + + yield 'correctly formatted yaml' => [ + 'yaml' => << 'value', + 'otherKey' => [ + 'sub' => 'subValue' + ] + ] + + ]; + } +} diff --git a/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php b/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php new file mode 100644 index 0000000..332f51d --- /dev/null +++ b/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php @@ -0,0 +1,75 @@ +process($container); + + static::assertTrue(true); + } + + public function testProcess(): void + { + $container = $this->getContainer(directories: [__DIR__.'/../../../TestApp/templates/snippets', 'notADirectory']); + + $pass = new TwigDocCollectDocsPass(new YamlParser()); + $pass->process($container); + + $service = $container->getDefinition('twig_doc.service.component'); + + static::assertCount(2, $service->getArgument('$componentsConfig')); + } + + public function testProcessThrowsExceptionForInvalidConfiguration() + { + static::expectException(InvalidConfigException::class); + $container = $this->getContainer([ + [ + 'name' => 'Button' + ] + ]); + + $pass = new TwigDocCollectDocsPass(new YamlParser()); + $pass->process($container); + } + + private function getContainer(array $componentsConfig = [], array $directories = []): ContainerBuilder + { + $container = new ContainerBuilder(); + $container->registerExtension(new TwigDocExtension()); + $container->setParameter('kernel.project_dir', __DIR__.'/../../../TestApp'); + $container->setParameter('twig.default_path', __DIR__.'/../../../TestApp/templates'); + $container->setParameter('twig_doc.config', [ + 'doc_identifier' => 'TWIG_DOC', + 'directories' => $directories, + 'categories' => ['category'], + ]); + $definition = new Definition(ComponentService::class, [ + '$componentsConfig' => $componentsConfig, + ]); + + $container->setDefinition('twig_doc.service.component', $definition); + + return $container; + } +} diff --git a/tests/Unit/DependencyInjection/ConfigurationTest.php b/tests/Unit/DependencyInjection/ConfigurationTest.php index cec01d1..a29c89d 100644 --- a/tests/Unit/DependencyInjection/ConfigurationTest.php +++ b/tests/Unit/DependencyInjection/ConfigurationTest.php @@ -18,13 +18,17 @@ public function testConfigTree(array $options, array $expectedResult) $configuration = new Configuration(); $config = $processor->processConfiguration($configuration, [$options]); - $this->assertEquals($expectedResult, $config); + $this->assertEqualsCanonicalizing($expectedResult, $config); } public static function getTestConfiguration(): iterable { yield 'Categories config' => [ [ + 'directories' => [ + __DIR__.'/../../TestApp/templates/components', + __DIR__.'/../../TestApp/templates/snippets', + ], 'categories' => [ [ 'name' => 'MainCategory', @@ -36,6 +40,11 @@ public static function getTestConfiguration(): iterable ], ], [ + 'doc_identifier' => 'TWIG_DOC', + 'directories' => [ + __DIR__.'/../../TestApp/templates/components', + __DIR__.'/../../TestApp/templates/snippets', + ], 'categories' => [ [ 'name' => 'MainCategory', @@ -51,6 +60,10 @@ public static function getTestConfiguration(): iterable yield 'Simple Component' => [ [ + 'directories' => [ + __DIR__.'/../../TestApp/templates/components', + __DIR__.'/../../TestApp/templates/snippets', + ], 'categories' => [ [ 'name' => 'MainCategory', @@ -86,6 +99,11 @@ public static function getTestConfiguration(): iterable ] ], [ + 'doc_identifier' => 'TWIG_DOC', + 'directories' => [ + __DIR__.'/../../TestApp/templates/components', + __DIR__.'/../../TestApp/templates/snippets', + ], 'categories' => [ [ 'name' => 'MainCategory', diff --git a/tests/Unit/DependencyInjection/TwigDocExtensionTest.php b/tests/Unit/DependencyInjection/TwigDocExtensionTest.php new file mode 100644 index 0000000..1bc4e7f --- /dev/null +++ b/tests/Unit/DependencyInjection/TwigDocExtensionTest.php @@ -0,0 +1,43 @@ + 'TWIG_DOC', + 'directories' => [ + 'some-directory' + ], + 'categories' => [ + ['name' => 'category'] + ], + ] + ]; + + $extension->load($configs, $container); + + $componentServiceDefinition = $container->getDefinition('twig_doc.service.component'); + $categoryServiceDefinition = $container->getDefinition('twig_doc.service.category'); + + static::assertEquals([], $componentServiceDefinition->getArgument('$componentsConfig')); + static::assertEquals([ + ['name' => 'category', 'sub_categories' => []] + ], $categoryServiceDefinition->getArgument('$categoriesConfig')); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..0e102ad --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,14 @@ +remove($kernel->getCacheDir()); +$kernel->boot(); +$kernel->shutdown(); From 6c81c897718b2dc37b4aa921bab5a7982de18eb1 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 7 Mar 2024 11:11:18 +0100 Subject: [PATCH 03/13] add default category - add directory resources to rebuild container on template changes --- src/Component/ComponentCategory.php | 2 ++ .../Compiler/TwigDocCollectDocsPass.php | 8 ++++++++ src/DependencyInjection/TwigDocExtension.php | 5 ++++- tests/Unit/DependencyInjection/TwigDocExtensionTest.php | 4 +++- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Component/ComponentCategory.php b/src/Component/ComponentCategory.php index f87559f..1bef8fc 100644 --- a/src/Component/ComponentCategory.php +++ b/src/Component/ComponentCategory.php @@ -10,6 +10,8 @@ */ class ComponentCategory { + public const DEFAULT_CATEGORY = 'Components'; + private ?ComponentCategory $parent = null; #[Assert\Regex('/^\w+$/')] diff --git a/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php b/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php index 443211e..ad4774d 100644 --- a/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php +++ b/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php @@ -2,8 +2,10 @@ namespace Qossmic\TwigDocBundle\DependencyInjection\Compiler; +use Qossmic\TwigDocBundle\Component\ComponentCategory; use Qossmic\TwigDocBundle\Configuration\ParserInterface; use Qossmic\TwigDocBundle\Exception\InvalidConfigException; +use Symfony\Component\Config\Resource\DirectoryResource; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Finder\Finder; @@ -41,6 +43,11 @@ public function process(ContainerBuilder $container): void return; } + foreach ($config['directories'] as $directory) { + // add resource to container to rebuild container on changes in templates + $container->addResource(new DirectoryResource($directory)); + } + $finder = new Finder(); foreach ($finder->in($config['directories'])->files()->filter(fn(SplFileInfo $file) => $file->getExtension() === 'twig') as $file) { $doc = $this->parseDoc($file, $config['doc_identifier']); @@ -60,6 +67,7 @@ public function process(ContainerBuilder $container): void 'name' => $componentName, 'path' => str_replace($projectDir . '/', '', $file->getRealPath()), 'renderPath' => str_replace($templateDir . '/', '', $file->getRealPath()), + 'category' => ComponentCategory::DEFAULT_CATEGORY, ]; $componentConfig[] = array_merge($itemConfig, $doc); } diff --git a/src/DependencyInjection/TwigDocExtension.php b/src/DependencyInjection/TwigDocExtension.php index abe614f..01d1f49 100644 --- a/src/DependencyInjection/TwigDocExtension.php +++ b/src/DependencyInjection/TwigDocExtension.php @@ -2,6 +2,7 @@ namespace Qossmic\TwigDocBundle\DependencyInjection; +use Qossmic\TwigDocBundle\Component\ComponentCategory; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\Extension; @@ -26,7 +27,9 @@ public function load(array $configs, ContainerBuilder $container) $definition = $container->getDefinition('twig_doc.service.component'); $definition->setArgument('$componentsConfig', $config['components']); + $categories = array_merge([['name' => ComponentCategory::DEFAULT_CATEGORY]], $config['categories']); + $definition = $container->getDefinition('twig_doc.service.category'); - $definition->setArgument('$categoriesConfig', $config['categories']); + $definition->setArgument('$categoriesConfig', $categories); } } diff --git a/tests/Unit/DependencyInjection/TwigDocExtensionTest.php b/tests/Unit/DependencyInjection/TwigDocExtensionTest.php index 1bc4e7f..5d0b299 100644 --- a/tests/Unit/DependencyInjection/TwigDocExtensionTest.php +++ b/tests/Unit/DependencyInjection/TwigDocExtensionTest.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; +use Qossmic\TwigDocBundle\Component\ComponentCategory; use Qossmic\TwigDocBundle\DependencyInjection\Configuration; use Qossmic\TwigDocBundle\DependencyInjection\TwigDocExtension; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -36,7 +37,8 @@ public function testLoad() $categoryServiceDefinition = $container->getDefinition('twig_doc.service.category'); static::assertEquals([], $componentServiceDefinition->getArgument('$componentsConfig')); - static::assertEquals([ + static::assertEqualsCanonicalizing([ + ['name' => ComponentCategory::DEFAULT_CATEGORY], ['name' => 'category', 'sub_categories' => []] ], $categoryServiceDefinition->getArgument('$categoriesConfig')); } From 95baba65339a298424f8f0de7f30da8efdc0f741 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 7 Mar 2024 13:50:59 +0100 Subject: [PATCH 04/13] compiler pass now adds paths to component config - add tests for path resolving --- .gitignore | 1 + .../Compiler/TwigDocCollectDocsPass.php | 74 ++++++++++++++----- .../Controller/TwigDocControllerTest.php | 2 +- .../invalid_for_test/SomeComponent.html.twig | 0 .../sub_dir/SomeComponent.html.twig | 0 .../Compiler/TwigDocCollectDocsPassTest.php | 44 +++++++++++ 6 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 tests/TestApp/templates/invalid_for_test/SomeComponent.html.twig create mode 100644 tests/TestApp/templates/invalid_for_test/sub_dir/SomeComponent.html.twig diff --git a/.gitignore b/.gitignore index 40bd46a..b2382b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /var/ /vendor/ +/report/ .phpunit.cache .phpunit.result.cache composer.lock diff --git a/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php b/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php index ad4774d..e571f03 100644 --- a/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php +++ b/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php @@ -23,33 +23,25 @@ public function process(ContainerBuilder $container): void return; } $config = $container->getParameter('twig_doc.config'); + $directories = $this->resolveDirectories($container, $config['directories']); $container->getParameterBag()->remove('twig_doc.config'); - $definition = $container->getDefinition('twig_doc.service.component'); - $componentConfig = $definition->getArgument('$componentsConfig'); - $projectDir = $container->getParameter('kernel.project_dir'); - $templateDir = $container->getParameter('twig.default_path'); - $defaultComponentsDirectory = $container->getParameter('twig.default_path') . '/components'; - if (!in_array($defaultComponentsDirectory, $config['directories'])) { - $config['directories'][] = $defaultComponentsDirectory; - } - foreach ($config['directories'] as $idx => $dir) { - if (!is_dir($dir)) { - unset($config['directories'][$idx]); - } - } - $config['directories'] = array_unique($config['directories']); - if (empty($config['directories'])) { + if (empty($directories)) { return; } - foreach ($config['directories'] as $directory) { + $definition = $container->getDefinition('twig_doc.service.component'); + $componentConfig = $this->enrichComponentsConfig($container, $directories, $definition->getArgument('$componentsConfig')); + $projectDir = $container->getParameter('kernel.project_dir'); + $templateDir = $container->getParameter('twig.default_path'); + + foreach ($directories as $directory) { // add resource to container to rebuild container on changes in templates $container->addResource(new DirectoryResource($directory)); } $finder = new Finder(); - foreach ($finder->in($config['directories'])->files()->filter(fn(SplFileInfo $file) => $file->getExtension() === 'twig') as $file) { + foreach ($finder->in($directories)->files()->filter(fn(SplFileInfo $file) => $file->getExtension() === 'twig') as $file) { $doc = $this->parseDoc($file, $config['doc_identifier']); if ($doc === null) { @@ -89,4 +81,52 @@ private function parseDoc(SplFileInfo $file, string $docIdentifier): null|array return $this->parser->parse($matches[1]); } + + private function enrichComponentsConfig(ContainerBuilder $container, array $directories, array $components): array + { + foreach ($components as &$component) { + if (!isset($component['path'])) { + $component['path'] = str_replace($container->getParameter('kernel.project_dir').'/', '', $this->getTemplatePath($component['name'], $directories)); + } + if (!isset($component['renderPath'])) { + $component['renderPath'] = str_replace($container->getParameter('twig.default_path').'/', '', $component['path']); + } + } + + return $components; + } + + private function resolveDirectories(ContainerBuilder $container, array $directories): array + { + $directories[] = $container->getParameter('twig.default_path') . '/components'; + + foreach ($directories as $idx => $dir) { + if (!is_dir($dir)) { + unset($directories[$idx]); + } + } + + return array_unique($directories); + } + + private function getTemplatePath(string $name, array $directories): ?string + { + $template = sprintf('%s.html.twig', $name); + + $finder = new Finder(); + + $files = $finder->in($directories)->files()->filter(fn(SplFileInfo $file) => $file->getFilename() === $template); + + if ($files->count() > 1) { + return null; + } + + if (!$files->hasResults()) { + return null; + } + + $files->getIterator()->rewind(); + + return $files->getIterator()->current(); + } } diff --git a/tests/Functional/Controller/TwigDocControllerTest.php b/tests/Functional/Controller/TwigDocControllerTest.php index 06742e1..61439ac 100644 --- a/tests/Functional/Controller/TwigDocControllerTest.php +++ b/tests/Functional/Controller/TwigDocControllerTest.php @@ -44,7 +44,7 @@ public function testFilterComponents(): void $node = $crawler->filter('div.twig-doc-component'); static::assertResponseIsSuccessful(); static::assertCount(1, $node); - static::assertEquals('Submit Button (ButtonSubmit.html.twig)', $node->filter('h3')->getNode(0)->nodeValue); + static::assertEquals('Submit Button (tests/TestApp/templates/components/ButtonSubmit.html.twig)', $node->filter('h3')->getNode(0)->nodeValue); } public function testInvalidComponentsRoute(): void diff --git a/tests/TestApp/templates/invalid_for_test/SomeComponent.html.twig b/tests/TestApp/templates/invalid_for_test/SomeComponent.html.twig new file mode 100644 index 0000000..e69de29 diff --git a/tests/TestApp/templates/invalid_for_test/sub_dir/SomeComponent.html.twig b/tests/TestApp/templates/invalid_for_test/sub_dir/SomeComponent.html.twig new file mode 100644 index 0000000..e69de29 diff --git a/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php b/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php index 332f51d..53e8322 100644 --- a/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php +++ b/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php @@ -40,9 +40,53 @@ public function testProcess(): void static::assertCount(2, $service->getArgument('$componentsConfig')); } + public function testProcessNotEnrichingPathsForMissingTemplate() + { + $container = $this->getContainer(componentsConfig: [ + [ + 'name' => 'invalidComponent', + ] + ]); + + $pass = new TwigDocCollectDocsPass(new YamlParser()); + + $pass->process($container); + + $definition = $container->getDefinition('twig_doc.service.component'); + + static::assertEmpty($definition->getArgument('$componentsConfig')[0]['path']); + static::assertEmpty($definition->getArgument('$componentsConfig')[0]['renderPath']); + } + + public function testProcessNotEnrichingPathsForAmbiguousTemplate() + { + $container = $this->getContainer(componentsConfig: [ + [ + 'name' => 'SomeComponent', + ], + [ + 'name' => 'SomeComponent', + ], + ],directories: [ + __DIR__.'/../../../TestApp/templates/invalid_for_test', + ]); + + $pass = new TwigDocCollectDocsPass(new YamlParser()); + + $pass->process($container); + + $definition = $container->getDefinition('twig_doc.service.component'); + + static::assertEmpty($definition->getArgument('$componentsConfig')[0]['path']); + static::assertEmpty($definition->getArgument('$componentsConfig')[0]['renderPath']); + static::assertEmpty($definition->getArgument('$componentsConfig')[1]['path']); + static::assertEmpty($definition->getArgument('$componentsConfig')[1]['renderPath']); + } + public function testProcessThrowsExceptionForInvalidConfiguration() { static::expectException(InvalidConfigException::class); + static::expectExceptionMessage(sprintf('component "%s" is configured twice, please configure either directly in the template or the general bundle configuration', 'Button')); $container = $this->getContainer([ [ 'name' => 'Button' From b0b71e0f7ff1a98ae47ad45b1fb9667b78ee5d56 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 7 Mar 2024 13:51:13 +0100 Subject: [PATCH 05/13] add documentation --- README.md | 124 ++++-------------------------- docs/BundleConfiguration.md | 68 ++++++++++++++++ docs/ComponentConfiguration.md | 66 ++++++++++++++++ docs/Usage.md | 5 ++ docs/resources/images/qossmic.png | Bin 0 -> 1415 bytes 5 files changed, 155 insertions(+), 108 deletions(-) create mode 100644 docs/BundleConfiguration.md create mode 100644 docs/ComponentConfiguration.md create mode 100644 docs/Usage.md create mode 100644 docs/resources/images/qossmic.png diff --git a/README.md b/README.md index 1b5ae90..0d3cb30 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,20 @@ ## Twic Doc Bundle -Allows you to create an overview for your Twig Components, be it either UX-Components, UX-Live-Components or simple snippet templates. +[![Image](docs/resources/images/qossmic.png)](https://qossmic.com) Brought to you by qossmic! -Components will be grouped in categories and optional sub-categories. +1. [Installation](#installation) +2. Configuration + 1. [Bundle Configuration](docs/BundleConfiguration.md) + 2. [Component Configuration](docs/ComponentConfiguration.md) +3. [Routing](#routing) +4. [Customization](#customizing-the-design) +5. [Usage](docs/Usage.md) + +--- -The categories must be configured in the bundle-configuration, see below. +Allows you to create an overview for your Twig Components, be it either [UX-Components](https://symfony.com/bundles/ux-twig-component/current/index.html), [UX-Live-Components](https://symfony.com/bundles/ux-live-component/current/index.html) or simple snippet templates. + +Components will be grouped in categories and optional sub-categories. ### Installation @@ -38,110 +48,8 @@ twig_doc: # or for localized: prefix: /{_locale}/twig/doc/ ``` -### Template - -To use your design in the documentation, you have to override the component template. - -Create a template in your project: templates/bundles/TwigDocBundle/component.html.twig - -```twig -{% extends '@!TwigDoc/component.html.twig' %} - -{% block stylesheets %} - -{% endblock %} -``` - -#### Customizing the documentation - -If you want to customize the documentation, you can override the template. - -Create a template in your project: templates/bundles/TwigDocBundle/documentation.html.twig - -```twig -{% extends '@!TwigDoc/documentation.html.twig' %} - -{% block stylesheets %} - -{% endblock %} -``` - -### Configuration +### Customizing the design -Create a config file: configs/packages/twig_doc.yaml +To customize the design of the documentation, you can override any template of the bundle in your project. -#### Configure categories - -```yaml -twig_doc: - categories: - - name: Button - sub_categories: - - Action - - Submit - - name: Heading - - name: Notification - sub_categories: - - Alert - - Flash -#... component config -``` - -#### Example for a Twig UX-Component - -```yaml -twig_doc: - #... categories config - components: - - name: ActionButton # will render %kernel.project_dir%/templates/components/ActionButton.html.twig - title: Action Button - description: An Action button - category: Buttons - sub_category: Action - tags: - - buttons - parameters: - color: String - text: String - link: String - variations: - secondary: - color: secondary - text: Secondary Button - link: '#' - primary: - color: primary - text: Primary Button - link: '#' -``` -The bundle will look for this component in the folder configured for the ux-twig-component bundle (default: %kernel.project_dir%/templates/components/COMPONENT_NAME.html.twig). - -#### Example for a non ux-twig-component - -The only difference is that non-ux components use another default-path and the naming is not specified. - -```yaml -twig_doc: - #... categories config - components: - - name: snippets/alert # will render %kernel.project_dir%/templates/snippets/alert.html.twig - title: Alert - description: non twig-ux-component component - category: Notification - sub_category: - - Alert - tags: - - highlight - - nameIt - parameters: - type: String - msg: String - variations: - primary: - type: primary - msg: Primary Alert - danger: - type: danger - msg: Danger Alert -``` -The bundle will look for this template in the twig template folder (default: %kernel.project_dir%/templates/COMPONENT_NAME.html.twig). +See: [How to override any Part of a Bundle](https://symfony.com/doc/current/bundles/override.html) diff --git a/docs/BundleConfiguration.md b/docs/BundleConfiguration.md new file mode 100644 index 0000000..972791f --- /dev/null +++ b/docs/BundleConfiguration.md @@ -0,0 +1,68 @@ +## Configuring the bundle + +When you do not want to customize, you do not need any config file. + +The bundle provides following defaults: + +```yaml +twig_doc: + doc_identifier: TWIG_DOC + directories: + - '%twig.default_path%/components' + categories: + - name: Components +``` + +### Directories + +By default, the bundle looks for your components in this directory: `%twig.default_path%/components` + +You can provide additional directories in the config-file: + +```yaml +twig_doc: + directories: + - '%twig.default_path%/snippets' + - '%kernel.project_dir%/resources/components' +``` + +### Documentation identifier + +By default, the bundle uses this identifier: `TWIG_DOC` + +To use another one: + +```yaml +twig_doc: + doc_identifier: 'MY_DOC_IDENTIFIER' +``` + +In your component template, you can then mark up your documentation in the template: + +```twig +{#MY_DOC_IDENTIFIER +title: My component +... +MY_DOC_IDENTIFIER#} +
    +``` + +### Categories + +The bundle groups components into categories and optionally into sub-categories. + +Example: + +```yaml +twig_doc: + categories: + - name: Buttons + sub_categories: + - Action + - Submit + - name: Headings + - name: Alerts +... +``` + +The default category is always merged into the configuration. diff --git a/docs/ComponentConfiguration.md b/docs/ComponentConfiguration.md new file mode 100644 index 0000000..35199a6 --- /dev/null +++ b/docs/ComponentConfiguration.md @@ -0,0 +1,66 @@ +### Component Configuration + +You have two possibilities to let the bundle know of your components: + +1. Directly in the template of the component itself (you should stick to this) +2. In the config file + +You can use both possibilities, but it is recommended to use only one to avoid scattering documentation over different places. + +When you do not provide a category for the component, it will be added to the default-category. + +#### In Template + +We "abused" the comment tag from twig to allow the component configuration directly in the template. +This won't hurt twig at all as comments are totally ignored by the twig-parser. + +When providing the config in the template, you do not need to provide the name, path or renderPath of it, this is automatically resolved from the template files. + +```twig +{#TWIG_DOC + title: Fancy Button + description: This is a really fancy button + category: Buttons + tags: + - button + parameters: + type: String + text: String + variations: + primary: + type: primary + text: Hello World + secondary: + type: secondary + text: Welcome to Hell! +#TWIG_DOC} + + +``` + +#### Config file + +This is only recommended for small sets of components. + +```yaml +... +components: + - name: Button + title: Fancy Button + description: This is a really fancy button + category: Buttons + path: '%twig.default_path%/components/Button.html.twig' + renderPath: 'components/Button.html.twig' + tags: + - button + parameters: + type: String + text: String + variations: + primary: + type: primary + text: Hello World + secondary: + type: secondary + text: Welcome to Hell! +``` diff --git a/docs/Usage.md b/docs/Usage.md new file mode 100644 index 0000000..c4668ad --- /dev/null +++ b/docs/Usage.md @@ -0,0 +1,5 @@ +## Usage + +TODO +- screenshots of UI +- searching diff --git a/docs/resources/images/qossmic.png b/docs/resources/images/qossmic.png new file mode 100644 index 0000000000000000000000000000000000000000..13cc823bbbd421f4639fe26e3fe1a96cb7a37710 GIT binary patch literal 1415 zcmV;21$g?2P)7Q+(pbwI!P!xRdr)pC*rAbV!#>9JXG(S}nzhh3qddcq0?#%A2u?IfpCOb1b z=bJNU&d#1q3=R&8(Ik;b2oww4T^6>wDK*m3PkjnBH#&)!E6)J3z5FvWQTzW z!0Z>Hm@Dka3hM+w#tD1!9426*Z31oyyRzI=N6CQgrdlST4pxvtc@PfqnodW)cLCh^ zNfciID-9`g56CgHKsW!+FVj5p&*ru7+=aOn?X_vyd24w^!B{w6p`_e`EM%-Y%h4p_?p-vS*A^Hj3U0@+l;&QFg zk^}5ktT)5NHaJ1VPgDs=m>{6Q<64O&0(wmnB_p8N<66oR0jdu|SrY_|k0AlsxDa4v z3Gmj|r%*Lxf&ecipR(u(d*fgMg%%0O#esmdMFMV1uJ>W7^oP9!p0-FpkBQmWwSb}+ zTRh!ZSTXrCU-KHyQdfXE0a9m`ALvA2Md82f=zL;t5FjZe__h$#X>j{`8+1}HfPKi?BWZ=!z%zGvmh5A5UJ!A*{`AGv_O27-(s zY0(D^lhBM}XWklLjG|t6zvBJ_Sc3Nd>D)hdfe^8u@0Qif-K4J+d&lq2{t6g!BqQMx`& z&~%TGcsc5x3QT}wR;Gxd#6$AV@AB`kWsexj2))e?uM5g_@OY}&;$*Q3wXG%+jD0eL zo0H%85qLdBC zsnM3s@~VNw0or!*cG%9_VGDcpV|3stY5SMSB^8(e4D>EM^4t78zQJ*f)z4vSK3Z7W ztEwq%F7gxbq0csl14V*9A7hDt0o{+76d#xA$ZVcslMBF0Zrh+S0-_X Date: Thu, 7 Mar 2024 14:21:50 +0100 Subject: [PATCH 06/13] resolve variables in directories - doc improvement --- docs/ComponentConfiguration.md | 19 ++++++++++++++++--- .../Compiler/TwigDocCollectDocsPass.php | 2 ++ .../Compiler/TwigDocCollectDocsPassTest.php | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/ComponentConfiguration.md b/docs/ComponentConfiguration.md index 35199a6..b4f4f18 100644 --- a/docs/ComponentConfiguration.md +++ b/docs/ComponentConfiguration.md @@ -14,7 +14,7 @@ When you do not provide a category for the component, it will be added to the de We "abused" the comment tag from twig to allow the component configuration directly in the template. This won't hurt twig at all as comments are totally ignored by the twig-parser. -When providing the config in the template, you do not need to provide the name, path or renderPath of it, this is automatically resolved from the template files. +When providing the config in the template, you do not need to provide the name, this is automatically resolved from the template file. ```twig {#TWIG_DOC @@ -42,6 +42,12 @@ When providing the config in the template, you do not need to provide the name, This is only recommended for small sets of components. +The bundle tries to resolve the path of the template in a compiler pass based on the name of the component. + +E.g.: name: Button -> bundle looks for a Button.html.twig + +For this to work, you need to ensure that your components are unique among all configured directories. + ```yaml ... components: @@ -49,8 +55,6 @@ components: title: Fancy Button description: This is a really fancy button category: Buttons - path: '%twig.default_path%/components/Button.html.twig' - renderPath: 'components/Button.html.twig' tags: - button parameters: @@ -64,3 +68,12 @@ components: type: secondary text: Welcome to Hell! ``` + +Alternatively, you can provide a path for your component in the configuration (parameters are resolved automatically): + +```yaml +... +components: + - name: Button + path: '%twig.default_path%/snippets/FancyButton.html.twig'' +``` diff --git a/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php b/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php index e571f03..13711a2 100644 --- a/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php +++ b/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php @@ -100,6 +100,8 @@ private function resolveDirectories(ContainerBuilder $container, array $director { $directories[] = $container->getParameter('twig.default_path') . '/components'; + $directories = array_map(fn (string $dir) => $container->getParameterBag()->resolveValue($dir), $directories); + foreach ($directories as $idx => $dir) { if (!is_dir($dir)) { unset($directories[$idx]); diff --git a/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php b/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php index 53e8322..cb610a2 100644 --- a/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php +++ b/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php @@ -68,7 +68,7 @@ public function testProcessNotEnrichingPathsForAmbiguousTemplate() 'name' => 'SomeComponent', ], ],directories: [ - __DIR__.'/../../../TestApp/templates/invalid_for_test', + '%twig.default_path%/invalid_for_test', ]); $pass = new TwigDocCollectDocsPass(new YamlParser()); From 1b68ca71f0401fccfe3ac2ee42a2a26e62905ae1 Mon Sep 17 00:00:00 2001 From: david Date: Fri, 8 Mar 2024 11:06:54 +0100 Subject: [PATCH 07/13] make paths mandatory for items add performance test --- src/Component/ComponentItem.php | 6 +- src/Component/ComponentItemFactory.php | 4 +- .../Service/ComponentServiceTest.php | 110 ++++++++++++++++++ 3 files changed, 116 insertions(+), 4 deletions(-) diff --git a/src/Component/ComponentItem.php b/src/Component/ComponentItem.php index d912971..d833aef 100644 --- a/src/Component/ComponentItem.php +++ b/src/Component/ComponentItem.php @@ -25,9 +25,11 @@ class ComponentItem #[Assert\Valid] private ComponentCategory $category; #[Assert\Length(max: 4096)] - private ?string $projectPath = null; + #[Assert\NotNull] + private string $projectPath; #[Assert\Length(max: 4096)] - private ?string $renderPath = null; + #[Assert\NotNull] + private string $renderPath; public function getName(): string { diff --git a/src/Component/ComponentItemFactory.php b/src/Component/ComponentItemFactory.php index 7ca0e1c..501e072 100644 --- a/src/Component/ComponentItemFactory.php +++ b/src/Component/ComponentItemFactory.php @@ -53,8 +53,8 @@ private function createItem(array $data): ComponentItem ->setTags($data['tags'] ?? []) ->setParameters($data['parameters'] ?? []) ->setVariations($data['variations'] ?? []) - ->setProjectPath($data['path'] ?? null) - ->setRenderPath($data['renderPath'] ?? null); + ->setProjectPath($data['path']) + ->setRenderPath($data['renderPath']); return $item; } diff --git a/tests/Functional/Service/ComponentServiceTest.php b/tests/Functional/Service/ComponentServiceTest.php index b215c07..2076b18 100644 --- a/tests/Functional/Service/ComponentServiceTest.php +++ b/tests/Functional/Service/ComponentServiceTest.php @@ -69,6 +69,19 @@ public function testGetInvalidComponents() } } + public function testParsePerformance(): void + { + $factory = static::getContainer()->get(ComponentItemFactory::class); + + $start = microtime(true); + + new ComponentService($factory, $this->getLargeConfig()); + + $elapsedTime = microtime(true) - $start; + + static::assertLessThan(1.5, $elapsedTime); + } + public static function getFilterTestCases(): iterable { yield 'name' => [ @@ -111,4 +124,101 @@ public static function getFilterTestCases(): iterable ] ]; } + + private function getLargeConfig(): array + { + return array_fill(0, 5000, [ + 'name' => 'component', + 'title' => 'title', + 'description' => 'long long text long long text long long text long long text long long text long long text long long text ', + 'category' => 'MainCategory', + 'path' => 'path/ultra/long/sub/dir/bla/blub/blub/blub/component.html.twig', + 'renderPath' => 'component.html.twig', + 'tags' => [ + 'tag1', + 'tag2', + 'tag3', + 'tag4', + 'tag5', + 'tag6', + 'tag7', + 'tag8', + 'tag9', + ], + 'parameters' => [ + 'param1' => 'String', + 'param2' => 'String', + 'param3' => 'String', + 'param4' => 'String', + 'param5' => [ + 'param1' => 'String', + 'param2' => 'String', + 'param3' => 'String', + 'param4' => 'String', + ], + 'param6' => [ + 'param1' => 'String', + 'param2' => 'String', + 'param3' => 'String', + 'param4' => 'String', + ] + ], + 'variations' => [ + 'variation1' => [ + 'param1' => 'String', + 'param2' => 'String', + 'param3' => 'String', + 'param4' => 'String', + 'param5' => [ + 'param1' => 'String', + 'param2' => 'String', + 'param3' => 'String', + 'param4' => 'String', + ], + 'param6' => [ + 'param1' => 'String', + 'param2' => 'String', + 'param3' => 'String', + 'param4' => 'String', + ] + ], + 'variation2' => [ + 'param1' => 'String', + 'param2' => 'String', + 'param3' => 'String', + 'param4' => 'String', + 'param5' => [ + 'param1' => 'String', + 'param2' => 'String', + 'param3' => 'String', + 'param4' => 'String', + ], + 'param6' => [ + 'param1' => 'String', + 'param2' => 'String', + 'param3' => 'String', + 'param4' => 'String', + ] + ], + 'variation3' => [ + 'param1' => 'String', + 'param2' => 'String', + 'param3' => 'String', + 'param4' => 'String', + 'param5' => [ + 'param1' => 'String', + 'param2' => 'String', + 'param3' => 'String', + 'param4' => 'String', + ], + 'param6' => [ + 'param1' => 'String', + 'param2' => 'String', + 'param3' => 'String', + 'param4' => 'String', + ] + ] + ] + ]); + } } From 1be893e91d4e654739a8da682eecd163caa242f7 Mon Sep 17 00:00:00 2001 From: david Date: Fri, 8 Mar 2024 11:39:11 +0100 Subject: [PATCH 08/13] fix tests test componentItemFactory with real services to capture violations --- src/Component/ComponentItem.php | 4 +- src/Component/ComponentItemFactory.php | 6 +- .../Service/ComponentItemFactoryTest.php | 50 +++++++++++++ .../Component/ComponentItemFactoryTest.php | 72 ------------------- 4 files changed, 55 insertions(+), 77 deletions(-) diff --git a/src/Component/ComponentItem.php b/src/Component/ComponentItem.php index d833aef..81987c1 100644 --- a/src/Component/ComponentItem.php +++ b/src/Component/ComponentItem.php @@ -25,10 +25,10 @@ class ComponentItem #[Assert\Valid] private ComponentCategory $category; #[Assert\Length(max: 4096)] - #[Assert\NotNull] + #[Assert\NotBlank] private string $projectPath; #[Assert\Length(max: 4096)] - #[Assert\NotNull] + #[Assert\NotBlank] private string $renderPath; public function getName(): string diff --git a/src/Component/ComponentItemFactory.php b/src/Component/ComponentItemFactory.php index 501e072..a5a730f 100644 --- a/src/Component/ComponentItemFactory.php +++ b/src/Component/ComponentItemFactory.php @@ -27,7 +27,7 @@ public function create(array $data): ComponentItem isset($data['sub_category']) ? 'sub_category' : 'category', $data['sub_category'] ?? $data['category'], implode(', ', array_keys($this->categoryService->getCategories())), - implode(', ', array_keys($this->categoryService->getSubCategories())) + implode(', ', array_map(fn (ComponentCategory $category) => $category->getName(), $this->categoryService->getSubCategories())) ) ); throw new InvalidComponentConfigurationException($violations); @@ -53,8 +53,8 @@ private function createItem(array $data): ComponentItem ->setTags($data['tags'] ?? []) ->setParameters($data['parameters'] ?? []) ->setVariations($data['variations'] ?? []) - ->setProjectPath($data['path']) - ->setRenderPath($data['renderPath']); + ->setProjectPath($data['path'] ?? '') + ->setRenderPath($data['renderPath'] ?? ''); return $item; } diff --git a/tests/Functional/Service/ComponentItemFactoryTest.php b/tests/Functional/Service/ComponentItemFactoryTest.php index 3f73520..c7ab5a0 100644 --- a/tests/Functional/Service/ComponentItemFactoryTest.php +++ b/tests/Functional/Service/ComponentItemFactoryTest.php @@ -5,16 +5,32 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\UsesClass; +use Qossmic\TwigDocBundle\Component\ComponentCategory; +use Qossmic\TwigDocBundle\Component\ComponentItem; use Qossmic\TwigDocBundle\Component\ComponentItemFactory; use Qossmic\TwigDocBundle\Exception\InvalidComponentConfigurationException; use Qossmic\TwigDocBundle\Service\CategoryService; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\Validator\Validator\ValidatorInterface; use TypeError; #[CoversClass(ComponentItemFactory::class)] #[UsesClass(CategoryService::class)] class ComponentItemFactoryTest extends KernelTestCase { + #[DataProvider('getValidComponents')] + public function testValidComponent(array $componentData): void + { + $validator = static::getContainer()->get(ValidatorInterface::class); + $categoryService = static::getContainer()->get(CategoryService::class); + $componentItemFactory = new ComponentItemFactory($validator, $categoryService); + + $item = $componentItemFactory->create($componentData); + + static::assertInstanceOf(ComponentItem::class, $item); + static::assertInstanceOf(ComponentCategory::class, $item->getCategory()); + } + #[DataProvider('getInvalidComponentConfigurationTestCases')] public function testInvalidComponentConfiguration(array $componentData, string $expectedExceptionClass = InvalidComponentConfigurationException::class) { @@ -58,4 +74,38 @@ public static function getInvalidComponentConfigurationTestCases(): iterable TypeError::class, ]; } + + public static function getValidComponents(): iterable + { + yield 'Component without sub-category' => [ + [ + 'name' => 'Component1', + 'title' => 'Component', + 'description' => 'Test component', + 'category' => 'MainCategory', + 'path' => 'path/to/template', + 'renderPath' => 'render/path/to/template', + 'parameters' => [], + 'variations' => [ + 'default' => [] + ] + ], + ]; + + yield 'Component with sub-category' => [ + [ + 'name' => 'Component1', + 'title' => 'Component', + 'description' => 'Test component', + 'category' => 'MainCategory', + 'sub_category' => 'SubCategory1', + 'path' => 'path/to/template', + 'renderPath' => 'render/path/to/template', + 'parameters' => [], + 'variations' => [ + 'default' => [] + ] + ], + ]; + } } diff --git a/tests/Unit/Component/ComponentItemFactoryTest.php b/tests/Unit/Component/ComponentItemFactoryTest.php index a4717cb..0ca9f56 100644 --- a/tests/Unit/Component/ComponentItemFactoryTest.php +++ b/tests/Unit/Component/ComponentItemFactoryTest.php @@ -3,43 +3,17 @@ namespace Qossmic\TwigDocBundle\Tests\Unit\Component; use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\UsesClass; -use Qossmic\TwigDocBundle\Component\ComponentCategory; -use Qossmic\TwigDocBundle\Component\ComponentItem; use Qossmic\TwigDocBundle\Component\ComponentItemFactory; use PHPUnit\Framework\TestCase; use Qossmic\TwigDocBundle\Exception\InvalidComponentConfigurationException; use Qossmic\TwigDocBundle\Service\CategoryService; -use Symfony\Component\Validator\ConstraintViolationList; use Symfony\Component\Validator\Validator\ValidatorInterface; #[CoversClass(ComponentItemFactory::class)] #[UsesClass(InvalidComponentConfigurationException::class)] class ComponentItemFactoryTest extends TestCase { - #[DataProvider('getValidComponents')] - public function testValidComponent(array $componentData): void - { - $componentCategoryMock = $this->getComponentCategoryMock($componentData['category'], $componentData['sub_category'] ?? null); - $categoryServiceMock = static::createMock(CategoryService::class); - $categoryServiceMock - ->method('getCategory') - ->with($componentData['category']) - ->willReturn($componentCategoryMock) - ; - $validatorMock = static::createMock(ValidatorInterface::class); - $validatorMock->method('validate') - ->willReturn(new ConstraintViolationList()); - - $componentItemFactory = new ComponentItemFactory($validatorMock, $categoryServiceMock); - - $item = $componentItemFactory->create($componentData); - - static::assertInstanceOf(ComponentItem::class, $item); - static::assertInstanceOf(ComponentCategory::class, $item->getCategory()); - } - public function testInvalidCategory() { static::expectException(InvalidComponentConfigurationException::class); @@ -85,50 +59,4 @@ public function testGetParamsFromVariables(): void ], ], $result); } - - public static function getValidComponents(): iterable - { - yield 'Component without sub-category' => [ - [ - 'name' => 'Component1', - 'title' => 'Component', - 'description' => 'Test component', - 'category' => 'TestCategory', - 'parameters' => [], - 'variations' => [ - 'default' => [] - ] - ], - ]; - - yield 'Component with sub-category' => [ - [ - 'name' => 'Component1', - 'title' => 'Component', - 'description' => 'Test component', - 'category' => 'TestCategory', - 'sub_category' => 'SubCategory', - 'parameters' => [], - 'variations' => [ - 'default' => [] - ] - ], - ]; - } - - private function getComponentCategoryMock(string $category, ?string $subCategory = null): ComponentCategory - { - $componentCategoryMock = static::createMock(ComponentCategory::class); - $componentCategoryMock->method('getName') - ->willReturn($subCategory ?? $category); - $parentMock = null; - if ($subCategory) { - $parentMock = static::createMock(ComponentCategory::class); - $parentMock->method('getName')->willReturn($category); - } - $componentCategoryMock->method('getParent') - ->willReturn($parentMock); - - return $componentCategoryMock; - } } From d73be0de14083afdb65a62e9287a09f8219bc24e Mon Sep 17 00:00:00 2001 From: david Date: Fri, 8 Mar 2024 12:54:37 +0100 Subject: [PATCH 09/13] fix tests after merging main --- tests/Functional/Service/ComponentItemFactoryTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/Functional/Service/ComponentItemFactoryTest.php b/tests/Functional/Service/ComponentItemFactoryTest.php index 788460f..879c877 100644 --- a/tests/Functional/Service/ComponentItemFactoryTest.php +++ b/tests/Functional/Service/ComponentItemFactoryTest.php @@ -51,6 +51,8 @@ public function testComponentWithoutParameters(): void 'title' => 'Test title', 'description' => 'description', 'category' => 'MainCategory', + 'path' => 'path/to/component', + 'renderPath' => 'path/to/component', ]; /** @var ComponentItemFactory $factory */ @@ -68,6 +70,8 @@ public function testFactoryCreatesDefaultVariationWhenMissingInConfig(): void 'title' => 'Test title', 'description' => 'description', 'category' => 'MainCategory', + 'path' => 'path/to/component', + 'renderPath' => 'path/to/component', ]; /** @var ComponentItemFactory $factory */ @@ -85,6 +89,8 @@ public function testFactoryCreatesDefaultVariationWithParameterTypes(): void 'title' => 'Test title', 'description' => 'description', 'category' => 'MainCategory', + 'path' => 'path/to/component', + 'renderPath' => 'path/to/component', 'parameters' => [ 'string' => 'String', 'float' => 'Float', From 87acf99177ae6f046195f1cb0ff490521217fbc7 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 11 Mar 2024 13:00:01 +0100 Subject: [PATCH 10/13] remove doubled char --- docs/ComponentConfiguration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ComponentConfiguration.md b/docs/ComponentConfiguration.md index b4f4f18..6b6b2cd 100644 --- a/docs/ComponentConfiguration.md +++ b/docs/ComponentConfiguration.md @@ -75,5 +75,5 @@ Alternatively, you can provide a path for your component in the configuration (p ... components: - name: Button - path: '%twig.default_path%/snippets/FancyButton.html.twig'' + path: '%twig.default_path%/snippets/FancyButton.html.twig' ``` From 56fc66e87cf6bb9fa6ab6116928026c7b3469a4c Mon Sep 17 00:00:00 2001 From: david Date: Mon, 18 Mar 2024 13:40:24 +0100 Subject: [PATCH 11/13] #9 cs-fixer - merge from main --- src/Component/ComponentItem.php | 4 +-- src/Component/ComponentItemFactory.php | 1 + src/Configuration/YamlParser.php | 6 ++-- src/Controller/TwigDocController.php | 1 + .../Compiler/TwigDocCollectDocsPass.php | 17 +++++----- src/DependencyInjection/Configuration.php | 2 +- src/Twig/TwigDocExtension.php | 2 ++ .../Controller/TwigDocControllerTest.php | 8 ++--- .../Service/ComponentItemFactoryTest.php | 9 +++--- .../Service/ComponentServiceTest.php | 32 +++++++++---------- tests/TestApp/Kernel.php | 1 - .../Component/ComponentItemFactoryTest.php | 13 ++++---- tests/Unit/Configuration/YamlParserTest.php | 17 +++++----- .../Compiler/TwigDocCollectDocsPassTest.php | 8 ++--- .../TwigDocExtensionTest.php | 8 ++--- 15 files changed, 65 insertions(+), 64 deletions(-) diff --git a/src/Component/ComponentItem.php b/src/Component/ComponentItem.php index 32638c4..04fc317 100644 --- a/src/Component/ComponentItem.php +++ b/src/Component/ComponentItem.php @@ -154,7 +154,7 @@ public function getProjectPath(): ?string return $this->projectPath; } - public function setProjectPath(?string $projectPath): ComponentItem + public function setProjectPath(?string $projectPath): self { $this->projectPath = $projectPath; @@ -166,7 +166,7 @@ public function getRenderPath(): ?string return $this->renderPath; } - public function setRenderPath(?string $renderPath): ComponentItem + public function setRenderPath(?string $renderPath): self { $this->renderPath = $renderPath; diff --git a/src/Component/ComponentItemFactory.php b/src/Component/ComponentItemFactory.php index e665161..146338c 100644 --- a/src/Component/ComponentItemFactory.php +++ b/src/Component/ComponentItemFactory.php @@ -78,6 +78,7 @@ public function getParamsFromVariables(array $variables): array $c = 'Scalar'; } } + return $r; } diff --git a/src/Configuration/YamlParser.php b/src/Configuration/YamlParser.php index c3d60f8..3c9f710 100644 --- a/src/Configuration/YamlParser.php +++ b/src/Configuration/YamlParser.php @@ -32,14 +32,14 @@ private function fixIndentation(string $content): string } if ($firstLineDetected === false) { $firstLineDetected = true; - # check for whitespaces at the beginning + // check for whitespaces at the beginning if (!preg_match('#^(\s+)#', $line, $matches)) { - # no leading whitespaces, indentation seems to be fine + // no leading whitespaces, indentation seems to be fine return $content; } $indentationWhitespace = $matches[1]; } - $line = substr($line, strlen($indentationWhitespace)); + $line = substr($line, \strlen($indentationWhitespace)); $lines[] = $line; $fileObject->next(); } diff --git a/src/Controller/TwigDocController.php b/src/Controller/TwigDocController.php index 600ceca..a47c05d 100644 --- a/src/Controller/TwigDocController.php +++ b/src/Controller/TwigDocController.php @@ -54,6 +54,7 @@ public function componentView(Request $request): Response } // disable profiler to get rid of toolbar in dev $this->profiler?->disable(); + return new Response( $this->twig->render('@TwigDoc/component.html.twig', [ 'component' => $component, diff --git a/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php b/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php index 13711a2..7476460 100644 --- a/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php +++ b/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php @@ -41,7 +41,7 @@ public function process(ContainerBuilder $container): void } $finder = new Finder(); - foreach ($finder->in($directories)->files()->filter(fn(SplFileInfo $file) => $file->getExtension() === 'twig') as $file) { + foreach ($finder->in($directories)->files()->filter(fn (SplFileInfo $file) => $file->getExtension() === 'twig') as $file) { $doc = $this->parseDoc($file, $config['doc_identifier']); if ($doc === null) { @@ -51,14 +51,13 @@ public function process(ContainerBuilder $container): void $filename = $file->getFilename(); $componentName = substr($filename, 0, strpos($filename, '.')); - if (array_filter($componentConfig, fn(array $data) => $data['name'] === $componentName)) { - throw new InvalidConfigException( - sprintf('component "%s" is configured twice, please configure either directly in the template or the general bundle configuration', $componentName)); + if (array_filter($componentConfig, fn (array $data) => $data['name'] === $componentName)) { + throw new InvalidConfigException(sprintf('component "%s" is configured twice, please configure either directly in the template or the general bundle configuration', $componentName)); } $itemConfig = [ 'name' => $componentName, - 'path' => str_replace($projectDir . '/', '', $file->getRealPath()), - 'renderPath' => str_replace($templateDir . '/', '', $file->getRealPath()), + 'path' => str_replace($projectDir.'/', '', $file->getRealPath()), + 'renderPath' => str_replace($templateDir.'/', '', $file->getRealPath()), 'category' => ComponentCategory::DEFAULT_CATEGORY, ]; $componentConfig[] = array_merge($itemConfig, $doc); @@ -67,7 +66,7 @@ public function process(ContainerBuilder $container): void $definition->replaceArgument('$componentsConfig', $componentConfig); } - private function parseDoc(SplFileInfo $file, string $docIdentifier): null|array + private function parseDoc(SplFileInfo $file, string $docIdentifier): ?array { $content = $file->getContents(); @@ -98,7 +97,7 @@ private function enrichComponentsConfig(ContainerBuilder $container, array $dire private function resolveDirectories(ContainerBuilder $container, array $directories): array { - $directories[] = $container->getParameter('twig.default_path') . '/components'; + $directories[] = $container->getParameter('twig.default_path').'/components'; $directories = array_map(fn (string $dir) => $container->getParameterBag()->resolveValue($dir), $directories); @@ -117,7 +116,7 @@ private function getTemplatePath(string $name, array $directories): ?string $finder = new Finder(); - $files = $finder->in($directories)->files()->filter(fn(SplFileInfo $file) => $file->getFilename() === $template); + $files = $finder->in($directories)->files()->filter(fn (SplFileInfo $file) => $file->getFilename() === $template); if ($files->count() > 1) { return null; diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 741b1b4..f1c64b4 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -15,7 +15,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->children() ->scalarNode('doc_identifier')->defaultValue('TWIG_DOC') ->validate() - ->ifTrue(fn($v) => !preg_match('#^\w+$#', $v)) + ->ifTrue(fn ($v) => !preg_match('#^\w+$#', $v)) ->thenInvalid('The twig_doc documentation identifier must match \w (regex)') ->end() ->end() diff --git a/src/Twig/TwigDocExtension.php b/src/Twig/TwigDocExtension.php index 8683341..19e0dd2 100644 --- a/src/Twig/TwigDocExtension.php +++ b/src/Twig/TwigDocExtension.php @@ -58,6 +58,7 @@ public function renderComponent(ComponentItem $item, array $params): string /** * @return ComponentInvalid[] + * * @codeCoverageIgnore */ public function getInvalidComponents(): array @@ -67,6 +68,7 @@ public function getInvalidComponents(): array /** * @return ComponentCategory[] + * * @codeCoverageIgnore */ public function getSubCategories(?string $mainCategoryName = null): array diff --git a/tests/Functional/Controller/TwigDocControllerTest.php b/tests/Functional/Controller/TwigDocControllerTest.php index 61439ac..e3787a1 100644 --- a/tests/Functional/Controller/TwigDocControllerTest.php +++ b/tests/Functional/Controller/TwigDocControllerTest.php @@ -12,7 +12,6 @@ use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; #[CoversClass(TwigDocController::class)] #[UsesClass(ComponentItemFactory::class)] @@ -29,6 +28,7 @@ protected function setUp(): void $this->client = static::createClient(); } + public function testIndexReturnsStatus200(): void { $crawler = $this->client->request(Request::METHOD_GET, '/'); @@ -66,8 +66,8 @@ public function testComponentViewRoute(): void 'name' => 'Button', 'data' => [ 'type' => 'primary', - 'text' => 'btn-text' - ] + 'text' => 'btn-text', + ], ] ); @@ -82,7 +82,7 @@ public function testComponentViewRouteReturns404(): void Request::METHOD_GET, '/component-view', [ - 'name' => 'notExistingComponent' + 'name' => 'notExistingComponent', ] ); diff --git a/tests/Functional/Service/ComponentItemFactoryTest.php b/tests/Functional/Service/ComponentItemFactoryTest.php index bd3b237..b621504 100644 --- a/tests/Functional/Service/ComponentItemFactoryTest.php +++ b/tests/Functional/Service/ComponentItemFactoryTest.php @@ -12,7 +12,6 @@ use Qossmic\TwigDocBundle\Service\CategoryService; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Validator\Validator\ValidatorInterface; -use TypeError; #[CoversClass(ComponentItemFactory::class)] #[UsesClass(CategoryService::class)] @@ -170,8 +169,8 @@ public static function getValidComponents(): iterable 'renderPath' => 'render/path/to/template', 'parameters' => [], 'variations' => [ - 'default' => [] - ] + 'default' => [], + ], ], ]; @@ -186,8 +185,8 @@ public static function getValidComponents(): iterable 'renderPath' => 'render/path/to/template', 'parameters' => [], 'variations' => [ - 'default' => [] - ] + 'default' => [], + ], ], ]; } diff --git a/tests/Functional/Service/ComponentServiceTest.php b/tests/Functional/Service/ComponentServiceTest.php index 2076b18..316d99a 100644 --- a/tests/Functional/Service/ComponentServiceTest.php +++ b/tests/Functional/Service/ComponentServiceTest.php @@ -88,40 +88,40 @@ public static function getFilterTestCases(): iterable 'query' => 'button', 'type' => 'name', 'expectedCounts' => [ - 'MainCategory' => 3 - ] + 'MainCategory' => 3, + ], ]; yield 'category' => [ 'query' => 'MainCategory', 'type' => 'category', 'expectedCounts' => [ - 'MainCategory' => 4 - ] + 'MainCategory' => 4, + ], ]; yield 'sub_category' => [ 'query' => 'SubCategory2', 'type' => 'sub_category', 'expectedCounts' => [ - 'MainCategory' => 1 - ] + 'MainCategory' => 1, + ], ]; yield 'tags' => [ 'query' => 'snippet', 'type' => 'tags', 'expectedCounts' => [ - 'MainCategory' => 1 - ] + 'MainCategory' => 1, + ], ]; yield 'any' => [ 'query' => 'action', 'type' => '', 'expectedCounts' => [ - 'MainCategory' => 1 - ] + 'MainCategory' => 1, + ], ]; } @@ -161,7 +161,7 @@ private function getLargeConfig(): array 'param2' => 'String', 'param3' => 'String', 'param4' => 'String', - ] + ], ], 'variations' => [ 'variation1' => [ @@ -180,7 +180,7 @@ private function getLargeConfig(): array 'param2' => 'String', 'param3' => 'String', 'param4' => 'String', - ] + ], ], 'variation2' => [ 'param1' => 'String', @@ -198,7 +198,7 @@ private function getLargeConfig(): array 'param2' => 'String', 'param3' => 'String', 'param4' => 'String', - ] + ], ], 'variation3' => [ 'param1' => 'String', @@ -216,9 +216,9 @@ private function getLargeConfig(): array 'param2' => 'String', 'param3' => 'String', 'param4' => 'String', - ] - ] - ] + ], + ], + ], ]); } } diff --git a/tests/TestApp/Kernel.php b/tests/TestApp/Kernel.php index 370ef8d..381fc17 100644 --- a/tests/TestApp/Kernel.php +++ b/tests/TestApp/Kernel.php @@ -4,7 +4,6 @@ namespace Qossmic\TwigDocBundle\Tests\TestApp; -use Psr\Log\NullLogger; use Qossmic\TwigDocBundle\TwigDocBundle; use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; diff --git a/tests/Unit/Component/ComponentItemFactoryTest.php b/tests/Unit/Component/ComponentItemFactoryTest.php index 70201c1..4e633cf 100644 --- a/tests/Unit/Component/ComponentItemFactoryTest.php +++ b/tests/Unit/Component/ComponentItemFactoryTest.php @@ -3,6 +3,7 @@ namespace Qossmic\TwigDocBundle\Tests\Unit\Component; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; use Qossmic\TwigDocBundle\Component\ComponentCategory; @@ -60,7 +61,7 @@ public function testGetParamsFromVariables(): void $variables = [ 'var.separated.by.dots', 'second', - 'third.param' + 'third.param', ]; $componentItemFactory = new ComponentItemFactory( @@ -71,16 +72,16 @@ public function testGetParamsFromVariables(): void $result = $componentItemFactory->getParamsFromVariables($variables); static::assertEquals([ - 'var'=> [ + 'var' => [ 'separated' => [ 'by' => [ - 'dots' => 'Scalar' - ] - ] + 'dots' => 'Scalar', + ], + ], ], 'second' => 'Scalar', 'third' => [ - 'param' => 'Scalar' + 'param' => 'Scalar', ], ], $result); } diff --git a/tests/Unit/Configuration/YamlParserTest.php b/tests/Unit/Configuration/YamlParserTest.php index 57f347f..5a76102 100644 --- a/tests/Unit/Configuration/YamlParserTest.php +++ b/tests/Unit/Configuration/YamlParserTest.php @@ -35,21 +35,21 @@ public static function getIndentationTestCases(): iterable { yield 'simple key-value test' => [ 'yaml' => " key: yaml ain't markup language", - 'expected' => ["key" => "yaml ain't markup language"] + 'expected' => ['key' => "yaml ain't markup language"], ]; yield 'indentationFix' => [ 'yaml' => << 'value', 'otherKey' => [ - 'sub' => 'subValue' - ] - ] + 'sub' => 'subValue', + ], + ], ]; yield 'correctly formatted yaml' => [ @@ -63,10 +63,9 @@ public static function getIndentationTestCases(): iterable [ 'key' => 'value', 'otherKey' => [ - 'sub' => 'subValue' - ] - ] - + 'sub' => 'subValue', + ], + ], ]; } } diff --git a/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php b/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php index cb610a2..8bacbfc 100644 --- a/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php +++ b/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php @@ -45,7 +45,7 @@ public function testProcessNotEnrichingPathsForMissingTemplate() $container = $this->getContainer(componentsConfig: [ [ 'name' => 'invalidComponent', - ] + ], ]); $pass = new TwigDocCollectDocsPass(new YamlParser()); @@ -67,7 +67,7 @@ public function testProcessNotEnrichingPathsForAmbiguousTemplate() [ 'name' => 'SomeComponent', ], - ],directories: [ + ], directories: [ '%twig.default_path%/invalid_for_test', ]); @@ -89,8 +89,8 @@ public function testProcessThrowsExceptionForInvalidConfiguration() static::expectExceptionMessage(sprintf('component "%s" is configured twice, please configure either directly in the template or the general bundle configuration', 'Button')); $container = $this->getContainer([ [ - 'name' => 'Button' - ] + 'name' => 'Button', + ], ]); $pass = new TwigDocCollectDocsPass(new YamlParser()); diff --git a/tests/Unit/DependencyInjection/TwigDocExtensionTest.php b/tests/Unit/DependencyInjection/TwigDocExtensionTest.php index 5d0b299..4077d24 100644 --- a/tests/Unit/DependencyInjection/TwigDocExtensionTest.php +++ b/tests/Unit/DependencyInjection/TwigDocExtensionTest.php @@ -23,12 +23,12 @@ public function testLoad() [ 'doc_identifier' => 'TWIG_DOC', 'directories' => [ - 'some-directory' + 'some-directory', ], 'categories' => [ - ['name' => 'category'] + ['name' => 'category'], ], - ] + ], ]; $extension->load($configs, $container); @@ -39,7 +39,7 @@ public function testLoad() static::assertEquals([], $componentServiceDefinition->getArgument('$componentsConfig')); static::assertEqualsCanonicalizing([ ['name' => ComponentCategory::DEFAULT_CATEGORY], - ['name' => 'category', 'sub_categories' => []] + ['name' => 'category', 'sub_categories' => []], ], $categoryServiceDefinition->getArgument('$categoriesConfig')); } } From 97f3d6a80690b9b50a02ed70172662428777cece Mon Sep 17 00:00:00 2001 From: david Date: Mon, 18 Mar 2024 13:41:12 +0100 Subject: [PATCH 12/13] #9 fix version constraints --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 8bfbb6f..f178e45 100644 --- a/composer.json +++ b/composer.json @@ -17,10 +17,10 @@ "require": { "php": ">=8.2", "symfony/framework-bundle": "^6.4|^7.0", - "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/routing": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", "symfony/twig-bundle": "^6.4|^7.0", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/yaml": "^6.4|^7.0" }, "require-dev": { "phpunit/phpunit": "^10.5", From 315ef69bc8aad9f3601c64528733dca1ef2785ea Mon Sep 17 00:00:00 2001 From: david Date: Tue, 19 Mar 2024 08:27:55 +0100 Subject: [PATCH 13/13] #9 remove redundant packages --- composer.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/composer.json b/composer.json index f178e45..ca8b1bb 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,6 @@ "require": { "php": ">=8.2", "symfony/framework-bundle": "^6.4|^7.0", - "symfony/routing": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", "symfony/twig-bundle": "^6.4|^7.0", "symfony/yaml": "^6.4|^7.0" @@ -27,7 +26,6 @@ "symfony/browser-kit": "^6.4|^7.0", "symfony/css-selector": "^6.4|^7.0", "symfony/dom-crawler": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", "friendsofphp/php-cs-fixer": "^3.51" } }