diff --git a/.gitignore b/.gitignore index a0a9d64..7dd9fa5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ /var/ /vendor/ +/report/ .phpunit.cache +.phpunit.result.cache composer.lock .php-cs-fixer.cache 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/composer.json b/composer.json index 4c08521..ca8b1bb 100644 --- a/composer.json +++ b/composer.json @@ -18,12 +18,14 @@ "php": ">=8.2", "symfony/framework-bundle": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", - "symfony/twig-bundle": "^6.4|^7.0" + "symfony/twig-bundle": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, "require-dev": { "phpunit/phpunit": "^10.5", "symfony/browser-kit": "^6.4|^7.0", "symfony/css-selector": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", "friendsofphp/php-cs-fixer": "^3.51" } } 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..6b6b2cd --- /dev/null +++ b/docs/ComponentConfiguration.md @@ -0,0 +1,79 @@ +### 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, this is automatically resolved from the template file. + +```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. + +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: + - name: Button + 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! +``` + +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/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 0000000..13cc823 Binary files /dev/null and b/docs/resources/images/qossmic.png differ 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/ComponentCategory.php b/src/Component/ComponentCategory.php index 5e5ad60..fa3cf7e 100644 --- a/src/Component/ComponentCategory.php +++ b/src/Component/ComponentCategory.php @@ -11,6 +11,8 @@ */ class ComponentCategory { + public const DEFAULT_CATEGORY = 'Components'; + private ?ComponentCategory $parent = null; #[Assert\Regex('/^\w+$/')] diff --git a/src/Component/ComponentItem.php b/src/Component/ComponentItem.php index 7cc812e..04fc317 100644 --- a/src/Component/ComponentItem.php +++ b/src/Component/ComponentItem.php @@ -25,6 +25,12 @@ class ComponentItem private array $variations; #[Assert\Valid] private ComponentCategory $category; + #[Assert\Length(max: 4096)] + #[Assert\NotBlank] + private string $projectPath; + #[Assert\Length(max: 4096)] + #[Assert\NotBlank] + private string $renderPath; public function getName(): string { @@ -142,4 +148,28 @@ public function getMainCategory(): ComponentCategory { return $this->category->getParent() ?? $this->category; } + + public function getProjectPath(): ?string + { + return $this->projectPath; + } + + public function setProjectPath(?string $projectPath): self + { + $this->projectPath = $projectPath; + + return $this; + } + + public function getRenderPath(): ?string + { + return $this->renderPath; + } + + public function setRenderPath(?string $renderPath): self + { + $this->renderPath = $renderPath; + + return $this; + } } diff --git a/src/Component/ComponentItemFactory.php b/src/Component/ComponentItemFactory.php index 16c4986..146338c 100644 --- a/src/Component/ComponentItemFactory.php +++ b/src/Component/ComponentItemFactory.php @@ -28,7 +28,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); @@ -55,11 +55,33 @@ private function createItem(array $data): ComponentItem ->setParameters($data['parameters'] ?? []) ->setVariations($data['variations'] ?? [ 'default' => $this->createVariationParameters($data['parameters'] ?? []), - ]); + ]) + ->setProjectPath($data['path'] ?? '') + ->setRenderPath($data['renderPath'] ?? ''); 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; + } + public function createVariationParameters(array $parameters): array { $params = []; 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 $content; + } + $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 273e68d..a47c05d 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', [ diff --git a/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php b/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php new file mode 100644 index 0000000..7476460 --- /dev/null +++ b/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php @@ -0,0 +1,133 @@ +hasExtension('twig_doc')) { + return; + } + $config = $container->getParameter('twig_doc.config'); + $directories = $this->resolveDirectories($container, $config['directories']); + $container->getParameterBag()->remove('twig_doc.config'); + + if (empty($directories)) { + return; + } + + $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($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)); + } + $itemConfig = [ + 'name' => $componentName, + 'path' => str_replace($projectDir.'/', '', $file->getRealPath()), + 'renderPath' => str_replace($templateDir.'/', '', $file->getRealPath()), + 'category' => ComponentCategory::DEFAULT_CATEGORY, + ]; + $componentConfig[] = array_merge($itemConfig, $doc); + } + + $definition->replaceArgument('$componentsConfig', $componentConfig); + } + + private function parseDoc(SplFileInfo $file, string $docIdentifier): ?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]); + } + + 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'; + + $directories = array_map(fn (string $dir) => $container->getParameterBag()->resolveValue($dir), $directories); + + 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/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 5ea5e44..f1c64b4 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -13,6 +13,15 @@ public function getConfigTreeBuilder(): TreeBuilder $treeBuilder->getRootNode() ->children() + ->scalarNode('doc_identifier')->defaultValue('TWIG_DOC') + ->validate() + ->ifTrue(fn ($v) => !preg_match('#^\w+$#', $v)) + ->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() @@ -23,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 b7ac68a..9592ce3 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; @@ -14,13 +15,17 @@ public function load(array $configs, ContainerBuilder $container): void $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'); $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/src/Service/ComponentService.php b/src/Service/ComponentService.php index 90222cf..40b596f 100644 --- a/src/Service/ComponentService.php +++ b/src/Service/ComponentService.php @@ -124,7 +124,7 @@ private function filterComponents(string $filterQuery, string $filterType): arra } /** - * @return ComponentItem[] + * @return ComponentInvalid[] */ public function getInvalidComponents(): array { @@ -133,6 +133,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 8adba61..19e0dd2 100644 --- a/src/Twig/TwigDocExtension.php +++ b/src/Twig/TwigDocExtension.php @@ -58,6 +58,8 @@ public function renderComponent(ComponentItem $item, array $params): string /** * @return ComponentInvalid[] + * + * @codeCoverageIgnore */ public function getInvalidComponents(): array { @@ -66,6 +68,8 @@ public function getInvalidComponents(): array /** * @return ComponentCategory[] + * + * @codeCoverageIgnore */ public function getSubCategories(?string $mainCategoryName = null): array { @@ -79,6 +83,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 a6a9500..9ad2f9f 100644 --- a/src/TwigDocBundle.php +++ b/src/TwigDocBundle.php @@ -4,10 +4,20 @@ namespace Qossmic\TwigDocBundle; +use Qossmic\TwigDocBundle\Configuration\YamlParser; +use Qossmic\TwigDocBundle\DependencyInjection\Compiler\TwigDocCollectDocsPass; +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 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 new file mode 100644 index 0000000..358c945 --- /dev/null +++ b/templates/blocks/component_blocks.html.twig @@ -0,0 +1,15 @@ +{% block language %}en{% endblock %} +{% 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 94e9950..7b8649c 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..b97d324 100644 --- a/templates/documentation.html.twig +++ b/templates/documentation.html.twig @@ -1,104 +1,10 @@ +{% use '@TwigDoc/blocks/page_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') }} + diff --git a/tests/Functional/Controller/TwigDocControllerTest.php b/tests/Functional/Controller/TwigDocControllerTest.php index a414896..e3787a1 100644 --- a/tests/Functional/Controller/TwigDocControllerTest.php +++ b/tests/Functional/Controller/TwigDocControllerTest.php @@ -9,6 +9,7 @@ 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; @@ -19,10 +20,18 @@ #[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,22 +39,53 @@ 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(); 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 { - $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 71524f4..b621504 100644 --- a/tests/Functional/Service/ComponentItemFactoryTest.php +++ b/tests/Functional/Service/ComponentItemFactoryTest.php @@ -5,16 +5,31 @@ 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; #[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) { @@ -35,6 +50,8 @@ public function testComponentWithoutParameters(): void 'title' => 'Test title', 'description' => 'description', 'category' => 'MainCategory', + 'path' => 'path/to/component', + 'renderPath' => 'path/to/component', ]; /** @var ComponentItemFactory $factory */ @@ -52,6 +69,8 @@ public function testFactoryCreatesDefaultVariationWhenMissingInConfig(): void 'title' => 'Test title', 'description' => 'description', 'category' => 'MainCategory', + 'path' => 'path/to/component', + 'renderPath' => 'path/to/component', ]; /** @var ComponentItemFactory $factory */ @@ -69,6 +88,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', @@ -114,14 +135,6 @@ public static function getInvalidComponentConfigurationTestCases(): iterable ], ]; - yield [ - [ - 'name' => 'InvalidComponentMissingTitleAndDescription', - 'category' => 'MainCategory', - 'title' => 'Component title', - ], - ]; - yield [ [ 'name' => 'InvalidComponentMissingDescription', @@ -143,4 +156,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/Functional/Service/ComponentServiceTest.php b/tests/Functional/Service/ComponentServiceTest.php new file mode 100644 index 0000000..316d99a --- /dev/null +++ b/tests/Functional/Service/ComponentServiceTest.php @@ -0,0 +1,224 @@ +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 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' => [ + '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, + ], + ]; + } + + 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', + ], + ], + ], + ]); + } +} 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/config/packages/twig_doc.php b/tests/TestApp/config/packages/twig_doc.php index c957322..bb5f475 100644 --- a/tests/TestApp/config/packages/twig_doc.php +++ b/tests/TestApp/config/packages/twig_doc.php @@ -1,6 +1,9 @@ [ + '%twig.default_path%/snippets', + ], 'categories' => [ [ 'name' => 'MainCategory', 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/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/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 be88006..4e633cf 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..5a76102 --- /dev/null +++ b/tests/Unit/Configuration/YamlParserTest.php @@ -0,0 +1,71 @@ +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..8bacbfc --- /dev/null +++ b/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php @@ -0,0 +1,119 @@ +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 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: [ + '%twig.default_path%/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', + ], + ]); + + $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 582d0bc..4a2b778 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..4077d24 --- /dev/null +++ b/tests/Unit/DependencyInjection/TwigDocExtensionTest.php @@ -0,0 +1,45 @@ + '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::assertEqualsCanonicalizing([ + ['name' => ComponentCategory::DEFAULT_CATEGORY], + ['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();