diff --git a/tests/Form/AbstractLayoutTest.php b/tests/Form/AbstractLayoutTest.php new file mode 100644 index 0000000000..9e0a8711f9 --- /dev/null +++ b/tests/Form/AbstractLayoutTest.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\AdminBundle\Tests\Form; + +use Sonata\AdminBundle\Form\Extension\Field\Type\FormTypeFieldExtension; +use Sonata\Form\Fixtures\StubTranslator; +use Symfony\Bridge\Twig\Extension\FormExtension; +use Symfony\Bridge\Twig\Extension\TranslationExtension; +use Symfony\Bridge\Twig\Form\TwigRendererEngine; +use Symfony\Component\Form\FormRenderer; +use Symfony\Component\Form\FormRendererInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\Test\FormIntegrationTestCase; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Twig\Environment; +use Twig\Loader\FilesystemLoader; +use Twig\RuntimeLoader\FactoryRuntimeLoader; + +abstract class AbstractLayoutTest extends FormIntegrationTestCase +{ + /** + * @var FormRenderer + */ + protected $renderer; + + protected function setUp(): void + { + parent::setUp(); + + $reflection = new \ReflectionClass(TwigRendererEngine::class); + $bridgeDirectory = \dirname($reflection->getFileName()).'/../Resources/views/Form'; + + $loader = new FilesystemLoader([ + __DIR__.'/../../src/Resources/views/Form', + $bridgeDirectory, + ]); + + $environment = new Environment($loader, ['strict_variables' => true]); + $environment->addExtension(new TranslationExtension(new StubTranslator())); + $environment->addExtension(new FormExtension()); + + $rendererEngine = new TwigRendererEngine([ + 'form_admin_fields.html.twig', + ], $environment); + + $csrfTokenManager = $this->createStub(CsrfTokenManagerInterface::class); + + $environment->addRuntimeLoader(new FactoryRuntimeLoader([ + FormRenderer::class => static function () use ($rendererEngine, $csrfTokenManager): FormRendererInterface { + return new FormRenderer($rendererEngine, $csrfTokenManager); + }, + ])); + + $this->renderer = $environment->getRuntime(FormRenderer::class); + } + + /** + * @see https://github.com/symfony/symfony/blob/e68da40f5649bb0266c74c2e1e4bbf83f9c6bb13/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php#L64 + */ + final protected function assertMatchesXpath(string $html, string $expression, int $count = 1): void + { + $dom = new \DOMDocument('UTF-8'); + try { + // Wrap in node so we can load HTML with multiple tags at + // the top level + $dom->loadXML(''.$html.''); + } 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 and + substr($dom->saveHTML(), 6, -8) + )); + } else { + $this->addToAssertionCount(1); + } + } + + protected function getTypeExtensions(): array + { + return [ + new FormTypeFieldExtension([], [ + 'form_type' => 'horizontal', + ]), + ]; + } + + protected function renderRow(FormView $view, array $vars = []): string + { + return (string) $this->renderer->searchAndRenderBlock($view, 'row', $vars); + } + + protected function renderErrors(FormView $view): string + { + return (string) $this->renderer->searchAndRenderBlock($view, 'errors'); + } +} diff --git a/tests/Form/AdminLayoutTestCase.php b/tests/Form/AdminLayoutTestCase.php new file mode 100644 index 0000000000..fae0bc58bc --- /dev/null +++ b/tests/Form/AdminLayoutTestCase.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\AdminBundle\Tests\Form; + +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormError; + +final class AdminLayoutTestCase extends AbstractLayoutTest +{ + public function testRowSetId(): void + { + $form = $this->factory->createNamed('name', TextType::class); + $view = $form->createView(); + $html = $this->renderRow($view); + + $this->assertMatchesXpath($html, '//div[@class="form-group"][@id="sonata-ba-field-container-name"]'); + } + + public function testRowWithErrors(): void + { + $form = $this->factory->createNamed('name', TextType::class); + $form->addError(new FormError('[trans]Error 1[/trans]')); + $form->addError(new FormError('[trans]Error 2[/trans]')); + $view = $form->createView(); + $html = $this->renderRow($view); + + $this->assertMatchesXpath($html, '/div[@class="form-group has-error"][@id="sonata-ba-field-container-name"]'); + } + + public function testErrors(): void + { + $form = $this->factory->createNamed('name', TextType::class); + $form->addError(new FormError('[trans]Error 1[/trans]')); + $form->addError(new FormError('[trans]Error 2[/trans]')); + $view = $form->createView(); + $html = $this->renderErrors($view); + + $expression = <<<'EOD' +/div + [@class="alert alert-danger"] + [ + ./ul + [@class="list-unstyled"] + [ + ./li + [.=" [trans]Error 1[/trans]"] + [ + ./i[@class="fa fa-exclamation-circle"] + ] + /following-sibling::li + [.=" [trans]Error 2[/trans]"] + [ + ./i[@class="fa fa-exclamation-circle"] + ] + ] + [count(./li)=2] + ] +EOD; + + $this->assertMatchesXpath( + $html, + $expression + ); + } +}