8000 [RFC] Simplify form theme testing for external packages · Issue #49775 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content
[RFC] Simplify form theme testing for external packages #49775
Closed
@ker0x

Description

@ker0x

Description

Current state

I recently create a bundle which provide a form theme for Flowbite and when came time to set up tests, I digged into Symfony internal to understand how form theme testing is done.

And ho boy! What a mess 😱!

  1. A lot of code is duplicate internaly across all theme testing classes.
  2. It's not possible for external packages to benefits from thoses classes because they are internal and located in the Tests folder
  3. I had to duplicate a lot of code portions in my turn

For example, take a look to the setUp method of all thoses classes:

This setUp part can be definitely improve! The same goes for all render* methods.

Example

I propose to introduce a new abstract class Symfony\Component\Form\Test\FormLayoutTestCase which will extend from Symfony\Component\Form\Test\FormIntegrationTestCase:

abstract class FormLayoutTestCase extends FormIntegrationTestCase
{
    use RuntimeLoaderProvider;

    protected FormRendererInterface $renderer;

    protected function setUp(): void
    {
        parent::setUp();

        $loader = new FilesystemLoader($this->getTemplatePaths());

        $environment = new Environment($loader, ['strict_variables' => true]);
        $environment->setExtensions($this->getTwigExtensions());

        foreach ($this->getTwigGlobals() as [$name, $value]) {
            $environment->addGlobal($name, $value);
        }

        $rendererEngine = new TwigRendererEngine($this->getThemes(), $environment);
        $this->renderer = new FormRenderer($rendererEngine, $this->createMock(CsrfTokenManagerInterface::class));
        $this->registerTwigRuntimeLoader($environment, $this->renderer);
    }

    protected function assertMatchesXpath(string $html, string $expression, int $count = 1): void
    {
        $dom = new \DOMDocument('UTF-8');
        try {
            $dom->loadXML('<root>'.$html.'</root>');
        } catch (\Exception $e) {
            $this->fail(sprintf(
                "Failed loading HTML:\n\n%s\n\nError: %s",
                $html,
                $e->getMessage()
            ));
        }
        $xpath = new \DOMXPath($dom);
        $nodeList = $xpath->evaluate('/root'.$expression);

        if ($nodeList->length != $count) {
            $dom->formatOutput = true;
            $this->fail(sprintf(
                "Failed asserting that \n\n%s\n\nmatches exactly %s. Matches %s in \n\n%s",
                $expression,
                1 == $count ? 'once' : $count.' times',
                1 == $nodeList->length ? 'once' : $nodeList->length.' times',
                // strip away <root> and </root>
                substr($dom->saveHTML(), 6, -8)
            ));
        } else {
            $this->addToAssertionCount(1);
        }
    }

    abstract protected function getTemplatePaths(): array;

    abstract protected function getTwigExtensions(): array;

    protected function getTwigGlobals(): array
    {
        return [];
    }

    abstract protected function getThemes(): array;

    protected function renderForm(FormView $view, array $vars = []): string
    {
        return $this->renderer->renderBlock($view, 'form', $vars);
    }

    protected function renderLabel(FormView $view, $label = null, array $vars = []): string
    {
        if (null !== $label) {
            $vars += ['label' => $label];
        }

        return $this->renderer->searchAndRenderBlock($view, 'label', $vars);
    }

    protected function renderHelp(FormView $view): string
    {
        return $this->renderer->searchAndRenderBlock($view, 'help');
    }

    protected function renderErrors(FormView $view): string
    {
        return $this->renderer->searchAndRenderBlock($view, 'errors');
    }

    protected function renderWidget(FormView $view, array $vars = []): string
    {
        return $this->renderer->searchAndRenderBlock($view, 'widget', $vars);
    }

    protected function renderRow(FormView $view, array $vars = []): string
    {
        return $this->renderer->searchAndRenderBlock($view, 'row', $vars);
    }

    protected function renderRest(FormView $view, array $vars = []): string
    {
        return $this->renderer->searchAndRenderBlock($view, 'rest', $vars);
    }

    protected function renderStart(FormView $view, array $vars = []): string
    {
        return $this->renderer->renderBlock($view, 'form_start', $vars);
    }

    protected function renderEnd(FormView $view, array $vars = []): string
    {
        return $this->renderer->renderBlock($view, 'form_end', $vars);
    }

    protected function setTheme(FormView $view, array $themes, $useDefaultThemes = true): void
    {
        $this->renderer->setTheme($view, $themes, $useDefaultThemes);
    }
}

With this new class, external packages will benefit from a lighter configuration and internal classes will be more concise.

final class FlowbiteLayoutTest extends FormLayoutTestCase
{
    protected function getTemplatePaths(): array
    {
        return [
            __DIR__.'/../vendor/symfony/twig-bridge/Resources/views/Form',
            __DIR__.'/../templates/form',
        ];
    }

    protected function getTwigExtensions(): array
    {
        return [
            new TranslationExtension(new StubTranslator()),
            new FormExtension(),
        ];
    }

    protected function getThemes(): array
    {
        return [
            'default.html.twig',
        ];
    }

   // tests
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    RFCRFC = Request For Comments (proposals about features that you want to be discussed)Stalled

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0