Skip to content
This repository has been archived by the owner on Sep 30, 2021. It is now read-only.

Implemented test suite for widget rendering #275

Merged
merged 1 commit into from
Aug 16, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Resources/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ Reference Guide
reference/api
reference/conditional_validation
reference/command
reference/testing

30 changes: 30 additions & 0 deletions Resources/doc/reference/testing.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.. index::
double: Test Widgets; Definition

Testing
=======

Test Widgets
~~~~~~~~~~~~

You can write unit tests for twig form rendering with the following code.

.. code-block:: php
use Sonata\CoreBundle\Test\AbstractWidgetTestCase;
class CustomTest extends AbstractWidgetTestCase
{
public function testAcmeWidget()
{
$options = array(
'foo' => 'bar',
);
$form = $this->factory->create('Acme\Form\CustomType', null, $options);
$html = $this->renderWidget($form->createView());
$expected = '<input foo="bar" />';
$this->assertContains($expected, $this->cleanHtmlWhitespace($html));
}
}
138 changes: 138 additions & 0 deletions Test/AbstractWidgetTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php

/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Sonata\CoreBundle\Test;

use Symfony\Bridge\Twig\Extension\FormExtension;
use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Bridge\Twig\Form\TwigRenderer;
use Symfony\Bridge\Twig\Form\TwigRendererEngine;
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubFilesystemLoader;
use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTranslator;
use Symfony\Component\Form\FormExtensionInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Test\TypeTestCase;

/**
* Base class for tests checking rendering of form widgets.
*
* @author Christian Gripp <mail@core23.de>
*/
abstract class AbstractWidgetTestCase extends TypeTestCase
{
/**
* @var FormExtensionInterface
*/
private $extension;

/**
* {@inheritdoc}
*/
protected function setUp()
{
parent::setUp();

$rendererEngine = new TwigRendererEngine(array(
'form_div_layout.html.twig',
));

// NEXT_MAJOR: Remove BC hack when dropping symfony 2.4 support
$csrfProviderClasses = array_filter(array(
// symfony <=2.4
Copy link
Member

@OskarStark OskarStark Jun 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add a NEXT_MAJOR here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think we need this here, because it's no direct deprecation. Also we have plenty of symfony BC hacks in the code without any comment.

'Symfony\Component\Security\Csrf\CsrfTokenManagerInterface',
// symfony >=2.4
'Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface',
), 'interface_exists');

$renderer = new TwigRenderer($rendererEngine, $this->getMock(current($csrfProviderClasses)));

$this->extension = new FormExtension($renderer);

// this is an workaround for different composer requirements and different TwigBridge installation directories
$twigPaths = array_filter(array(
// symfony/twig-bridge (running from this bundle)
__DIR__.'/../vendor/symfony/twig-bridge/Resources/views/Form',
// symfony/twig-bridge (running from other bundles)
__DIR__.'/../../../symfony/twig-bridge/Resources/views/Form',
// NEXT_MAJOR: Remove BC hacks when dropping symfony 2.3 support
// symfony/twig-bridge 2.3 (running from this bundle)
__DIR__.'/../vendor/symfony/twig-bridge/Symfony/Bridge/Twig/Resources/views/Form',
// symfony/twig-bridge 2.3 (running from other bundles)
__DIR__.'/../../../symfony/twig-bridge/Symfony/Bridge/Twig/Resources/views/Form',
// symfony/symfony (running from this bundle)
__DIR__.'/../vendor/symfony/symfony/src/Symfony/Bridge/Twig/Resources/views/Form',
// symfony/symfony (running from other bundles)
__DIR__.'/../../../symfony/symfony/src/Symfony/Bridge/Twig/Resources/views/Form',
), 'is_dir');

$twigPaths[] = __DIR__.'/../Resources/views/Form';

$loader = new StubFilesystemLoader($twigPaths);

$environment = new \Twig_Environment($loader, array(
'strict_variables' => true,
));
$environment->addExtension(new TranslationExtension(new StubTranslator()));
foreach ($this->getTwigExtensions() as $extension) {
$environment->addExtension($extension);
}
$environment->addExtension($this->extension);

$this->extension->initRuntime($environment);
}

/**
* @return \Twig_ExtensionInterface[]
*/
protected function getTwigExtensions()
{
return array();
}

/**
* Renders widget from FormView, in SonataAdmin context, with optional view variables $vars. Returns plain HTML.
*
* @param FormView $view
* @param array $vars
*
* @return string
*/
final protected function renderWidget(FormView $view, array $vars = array())
{
return (string) $this->extension->renderer->searchAndRenderBlock($view, 'widget', $vars);
}

/**
* Helper method to strip newline and space characters from html string to make comparing easier.
*
* @param string $html
*
* @return string
*/
final protected function cleanHtmlWhitespace($html)
{
return preg_replace_callback('/\s*>([^<]+)</', function ($value) {
return '>'.trim($value[1]).'<';
}, $html);
}

/**
* @param string $html
*
* @return string
*/
final protected function cleanHtmlAttributeWhitespace($html)
{
return preg_replace_callback('~<([A-Z0-9]+) \K(.*?)>~i', function ($m) {
return preg_replace('~\s*~', '', $m[0]);
}, $html);
}
}
125 changes: 125 additions & 0 deletions Tests/Form/Type/FormChoiceWidgetTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Sonata\CoreBundle\Tests\Form\Type;

use Sonata\CoreBundle\Test\AbstractWidgetTestCase;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@author missing

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

/**
* @author Christian Gripp <mail@core23.de>
*/
class FormChoiceWidgetTest extends AbstractWidgetTestCase
{
public function testLabelRendering()
{
$choices = array('some', 'choices');
if (!method_exists('Symfony\Component\Form\FormTypeInterface', 'setDefaultOptions')) {
$choices = array_flip($choices);
}

$choice = $this->factory->create(
$this->getChoiceClass(),
null,
$this->getDefaultOption() + array(
'multiple' => true,
'expanded' => true,
) + compact('choices')
);

$html = $this->renderWidget($choice->createView());

$this->assertContains(
'<div id="choice"><input type="checkbox" id="choice_0" name="choice[]" value="0" /><label for="choice_0">[trans]some[/trans]</label><input type="checkbox" id="choice_1" name="choice[]" value="1" /><label for="choice_1">[trans]choices[/trans]</label></div>',
Copy link
Contributor

@greg0ire greg0ire Aug 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use line breaks here please :

<?php
$this->cleanHtmlWhitespace(<<<HTML
<div id="choice">
    <input type="checkbox" id="choice_0" name="choice[]" value="0" />
    <label for="choice_0">[trans]some[/trans]</label>
    <input type="checkbox" id="choice_1" name="choice[]" value="1" />
    <label for="choice_1">[trans]choices[/trans]</label>
</div>
HTML)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed that

$this->cleanHtmlWhitespace($html)
);
}

public function testDefaultValueRendering()
{
$choice = $this->factory->create(
$this->getChoiceClass(),
null,
$this->getDefaultOption()
);

$html = $this->renderWidget($choice->createView());

$this->assertContains(
'<option value="" selected="selected">[trans]Choose an option[/trans]</option>',
$this->cleanHtmlWhitespace($html)
);
}

public function testRequiredIsDisabledForEmptyPlaceholder()
{
$choice = $this->factory->create(
$this->getChoiceClass(),
null,
$this->getRequiredOption()
);

$html = $this->renderWidget($choice->createView());

$this->assertNotContains(
'required="required"',
$this->cleanHtmlWhitespace($html)
);
}

public function testRequiredIsEnabledIfPlaceholderIsSet()
{
$choice = $this->factory->create(
$this->getChoiceClass(),
null,
array_merge($this->getRequiredOption(), $this->getDefaultOption())
);

$html = $this->renderWidget($choice->createView());

$this->assertContains(
'required="required"',
$this->cleanHtmlWhitespace($html)
);
}

private function getRequiredOption()
{
return array(
'required' => true,
);
}

private function getChoiceClass()
{
return method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix') ?
'Symfony\Component\Form\Extension\Core\Type\ChoiceType' :
'choice';
}

/**
* NEXT_MAJOR: Remove this hack when dropping support for symfony 2.6.
*
* For SF < 2.6, we use 'empty_data' to provide default empty value.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add a NEXT_MAJOR tag here?

* For SF >= 2.6, we must use 'placeholder' to achieve the same.
*/
private function getDefaultOption()
{
if (method_exists('Symfony\Component\Form\Tests\AbstractLayoutTest', 'testSingleChoiceNonRequiredWithPlaceholder')) {
return array(
'placeholder' => 'Choose an option',
);
}

return array(
'empty_value' => 'Choose an option',
);
}
}
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@
"php": "^5.3 || ^7.0",
"cocur/slugify": "^1.4 || ^2.0",
"symfony/config": "^2.3 || ^3.0",
"symfony/form": "^2.3 || ^3.0",
"symfony/form": "^2.3.5 || ^3.0",
"symfony/http-foundation": "^2.3 || ^3.0",
"symfony/property-access": "^2.3 || ^3.0",
"symfony/translation": "^2.3 || ^3.0",
"symfony/twig-bridge": "^2.3.5 || ^3.0",
"symfony/validator": "^2.3 || ^3.0",
"twig/twig": "^1.23"
},
Expand Down