Closed
Description
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 😱!
- A lot of code is duplicate internaly across all theme testing classes.
- It's not possible for external packages to benefits from thoses classes because they are internal and located in the
Tests
folder - 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:
- FormExtensionDivLayoutTest
- FormExtensionBootstrap3LayoutTest
- FormExtensionBootstrap4LayoutTest
- FormExtensionBootstrap5LayoutTest
- FormLayoutTest (external package)
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
}