This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
symfony/tests/Symfony/Tests/Component/Form/FormTest.php
Fabien Potencier 5e0823c99c merged branch bschussek/issue1919 (PR #3156)
Commits
-------

8dc78bd [Form] Fixed YODA issues
600cec7 [Form] Added missing entries to CHANGELOG and UPGRADE
b154f7c [Form] Fixed docblock and unneeded use statement
399af27 [Form] Implemented checks to assert that values and indices generated in choice lists match their requirements
5f6f75c [Form] Fixed outstanding issues mentioned in the PR
7c70976 [Form] Fixed text in UPGRADE file
c26b47a [Form] Made query parameter name generated by ORMQueryBuilderLoader unique
18f92cd [Form] Fixed double choice fixing
f533ef0 [Form] Added ChoiceView class for passing choice-related data to the view
d72900e [Form] Incorporated changes suggested in PR comments
28d2f6d Removed duplicated lines from UPGRADE file
e1fc5a5 [Form] Restricted form names to specific characters to (1) fix generation of HTML IDs and to (2) avoid problems with property paths.
87b16e7 [Form] Greatly improved ChoiceListInterface and all of its implementations

Discussion
----------

[Form] Improved ChoiceList implementation and made form naming more restrictive

Bug fix: yes
Feature addition: yes
Backwards compatibility break: **yes**
Symfony2 tests pass: yes
Fixes the following tickets: #2869, #3021, #1919, #3153
Todo: adapt documentation

![Travis Build Status](https://secure.travis-ci.org/bschussek/symfony.png?branch=issue1919)

The changes in this PR are primarily motivated by the fact that invalid form/field names lead to various problems.

1. When a name contains any characters that are not permitted in HTML "id" attributes, these are invalid
2. When a name contains periods ("."), form validation is broken, because they confuse the property path resolution
3. Since choices in expanded choice fields are directly translated to field names, choices applying to either 1. or 2. lead to problems. But choices should be unrestricted.
4. Unless a choice field is not expanded and does not allow multiple selection, it is not possible to use empty strings as choices, which might be desirable in some occasions.

The solution to these problems is to

* Restrict form names to disallow unpermitted characters (solves 1. and 2.)
* Generate integer indices to be stored in the HTML "id" and "name" attributes and map them to the choices (solves 3.). Can be reverted to the old behaviour by setting the option "index_generation" to ChoiceList::COPY_CHOICE
* Generate integer values to be stored in the HTML "value" attribute and map them to the choices (solves 4.). Can be reverted to the old behaviour by setting the option "value_generation" to ChoiceList::COPY_CHOICE

Apart from these fixes, it is now possible to write more flexible choice lists. One of these is `ObjectChoiceList`, which allows to use objects as choices and is bundled in the core. `EntityChoiceList` has been made an extension of this class.

    $form = $this->createFormBuilder()
        ->add('object', 'choice', array(
            'choice_list' => new ObjectChoiceList(
                array($obj1, $obj2, $obj3, $obj4),
                // property path determining the choice label (optional)
                'name',
                // preferred choices (optional)
                array($obj2, $obj3),
                // property path for object grouping (optional)
                'category',
                // property path for value generation (optional)
                'id',
                // property path for index generation (optional)
                'id'
            )
        ))
        ->getForm()
    ;

---------------------------------------------------------------------------

by kriswallsmith at 2012-01-19T18:09:09Z

Rather than passing `choices` and a `choice_labels` arrays to the view would it make sense to introduce a `ChoiceView` class and pass one array of objects?

---------------------------------------------------------------------------

by stof at 2012-01-22T15:32:36Z

@bschussek can you update your PR according to the feedback (and rebase it as it conflicts according to github) ?

---------------------------------------------------------------------------

by bschussek at 2012-01-24T00:15:42Z

@kriswallsmith fixed

Fixed all outstanding issues. Would be glad if someone could review again, otherwise this PR is ready to merge.

---------------------------------------------------------------------------

by fabpot at 2012-01-25T15:17:59Z

Is it ready to be merged?

---------------------------------------------------------------------------

by Tobion at 2012-01-25T15:35:50Z

Yes I think so. He said it's ready to be merged when reviewed.

---------------------------------------------------------------------------

by bschussek at 2012-01-26T02:30:36Z

Yes.

---------------------------------------------------------------------------

by bschussek at 2012-01-28T12:39:00Z

Fixed outstanding issues. Ready for merge.
2012-01-28 15:19:10 +01:00

1222 lines
36 KiB
PHP

<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Tests\Component\Form;
require_once __DIR__.'/Fixtures/FixedDataTransformer.php';
require_once __DIR__.'/Fixtures/FixedFilterListener.php';
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Tests\Component\Form\Fixtures\FixedDataTransformer;
use Symfony\Tests\Component\Form\Fixtures\FixedFilterListener;
class FormTest extends \PHPUnit_Framework_TestCase
{
private $dispatcher;
private $factory;
private $builder;
private $form;
protected function setUp()
{
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface');
$this->form = $this->getBuilder()->getForm();
}
protected function tearDown()
{
$this->dispatcher = null;
$this->factory = null;
$this->form = null;
}
/**
* @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
*/
public function testConstructExpectsValidValidators()
{
$validators = array(new \stdClass());
new Form('name', $this->dispatcher, array(), array(), array(), null, $validators);
}
public function getHtml4Ids()
{
return array(
array('a0', true),
array('a9', true),
array('z0', true),
array('A0', true),
array('A9', true),
array('Z0', true),
array('#', false),
array('a#', false),
array('a$', false),
array('a%', false),
array('a ', false),
array("a\t", false),
array("a\n", false),
array('a-', true),
array('a_', true),
array('a:', true),
// Periods are allowed by the HTML4 spec, but disallowed by us
// because they break the generated property paths
array('a.', false),
// Contrary to the HTML4 spec, we allow names starting with a
// number, otherwise naming fields by collection indices is not
// possible.
// For root forms, leading digits will be stripped from the
// "id" attribute to produce valid HTML4.
array('0', true),
array('9', true),
// Contrary to the HTML4 spec, we allow names starting with an
// underscore, since this is already a widely used practice in
// Symfony2.
// For root forms, leading underscores will be stripped from the
// "id" attribute to produce valid HTML4.
array('_', true),
);
}
/**
* @dataProvider getHtml4Ids
*/
public function testConstructAcceptsOnlyNamesValidAsIdsInHtml4($name, $accepted)
{
try {
new Form($name, $this->dispatcher);
if (!$accepted) {
$this->fail(sprintf('The value "%s" should not be accepted', $name));
}
} catch (\InvalidArgumentException $e) {
// if the value was not accepted, but should be, rethrow exception
if ($accepted) {
throw $e;
}
}
}
public function testDataIsInitializedEmpty()
{
$norm = new FixedDataTransformer(array(
'' => 'foo',
));
$client = new FixedDataTransformer(array(
'foo' => 'bar',
));
$form = new Form('name', $this->dispatcher, array(), array($client), array($norm));
$this->assertNull($form->getData());
$this->assertSame('foo', $form->getNormData());
$this->assertSame('bar', $form->getClientData());
}
public function testErrorsBubbleUpIfEnabled()
{
$error = new FormError('Error!');
$parent = $this->form;
$form = $this->getBuilder()->setErrorBubbling(true)->getForm();
$form->setParent($parent);
$form->addError($error);
$this->assertEquals(array(), $form->getErrors());
$this->assertEquals(array($error), $parent->getErrors());
}
public function testErrorsDontBubbleUpIfDisabled()
{
$error = new FormError('Error!');
$parent = $this->form;
$form = $this->getBuilder()->setErrorBubbling(false)->getForm();
$form->setParent($parent);
$form->addError($error);
$this->assertEquals(array($error), $form->getErrors());
$this->assertEquals(array(), $parent->getErrors());
}
public function testValidIfAllChildrenAreValid()
{
$this->form->add($this->getValidForm('firstName'));
$this->form->add($this->getValidForm('lastName'));
$this->form->bind(array(
'firstName' => 'Bernhard',
'lastName' => 'Schussek',
));
$this->assertTrue($this->form->isValid());
}
public function testInvalidIfChildrenIsInvalid()
{
$this->form->add($this->getValidForm('firstName'));
$this->form->add($this->getInvalidForm('lastName'));
$this->form->bind(array(
'firstName' => 'Bernhard',
'lastName' => 'Schussek',
));
$this->assertFalse($this->form->isValid());
}
public function testBind()
{
$child = $this->getMockForm('firstName');
$this->form->add($child);
$child->expects($this->once())
->method('bind')
->with($this->equalTo('Bernhard'));
$this->form->bind(array('firstName' => 'Bernhard'));
$this->assertEquals(array('firstName' => 'Bernhard'), $this->form->getData());
}
public function testBindForwardsNullIfValueIsMissing()
{
$child = $this->getMockForm('firstName');
$this->form->add($child);
$child->expects($this->once())
->method('bind')
->with($this->equalTo(null));
$this->form->bind(array());
}
public function testBindIsIgnoredIfReadOnly()
{
$form = $this->getBuilder()
->setReadOnly(true)
->setData('initial')
->getForm();
$form->bind('new');
$this->assertEquals('initial', $form->getData());
$this->assertTrue($form->isBound());
}
public function testNeverRequiredIfParentNotRequired()
{
$parent = $this->getBuilder()->setRequired(false)->getForm();
$child = $this->getBuilder()->setRequired(true)->getForm();
$child->setParent($parent);
$this->assertFalse($child->isRequired());
}
public function testRequired()
{
$parent = $this->getBuilder()->setRequired(true)->getForm();
$child = $this->getBuilder()->setRequired(true)->getForm();
$child->setParent($parent);
$this->assertTrue($child->isRequired());
}
public function testNotRequired()
{
$parent = $this->getBuilder()->setRequired(true)->getForm();
$child = $this->getBuilder()->setRequired(false)->getForm();
$child->setParent($parent);
$this->assertFalse($child->isRequired());
}
public function testAlwaysReadOnlyIfParentReadOnly()
{
$parent = $this->getBuilder()->setReadOnly(true)->getForm();
$child = $this->getBuilder()->setReadOnly(false)->getForm();
$child->setParent($parent);
$this->assertTrue($child->isReadOnly());
}
public function testReadOnly()
{
$parent = $this->getBuilder()->setReadOnly(false)->getForm();
$child = $this->getBuilder()->setReadOnly(true)->getForm();
$child->setParent($parent);
$this->assertTrue($child->isReadOnly());
}
public function testNotReadOnly()
{
$parent = $this->getBuilder()->setReadOnly(false)->getForm();
$child = $this->getBuilder()->setReadOnly(false)->getForm();
$child->setParent($parent);
$this->assertFalse($child->isReadOnly());
}
public function testCloneChildren()
{
$child = $this->getBuilder('child')->getForm();
$this->form->add($child);
$clone = clone $this->form;
$this->assertNotSame($this->form, $clone);
$this->assertNotSame($child, $clone['child']);
}
public function testGetRootReturnsRootOfParent()
{
$parent = $this->getMockForm();
$parent->expects($this->once())
->method('getRoot')
->will($this->returnValue('ROOT'));
$this->form->setParent($parent);
$this->assertEquals('ROOT', $this->form->getRoot());
}
public function testGetRootReturnsSelfIfNoParent()
{
$this->assertSame($this->form, $this->form->getRoot());
}
public function testEmptyIfEmptyArray()
{
$this->form->setData(array());
$this->assertTrue($this->form->isEmpty());
}
public function testEmptyIfNull()
{
$this->form->setData(null);
$this->assertTrue($this->form->isEmpty());
}
public function testEmptyIfEmptyString()
{
$this->form->setData('');
$this->assertTrue($this->form->isEmpty());
}
public function testNotEmptyIfText()
{
$this->form->setData('foobar');
$this->assertFalse($this->form->isEmpty());
}
public function testNotEmptyIfChildNotEmpty()
{
$child = $this->getMockForm();
$child->expects($this->once())
->method('isEmpty')
->will($this->returnValue(false));
$this->form->setData(null);
$this->form->add($child);
$this->assertFalse($this->form->isEmpty());
}
public function testValidIfBound()
{
$this->form->bind('foobar');
$this->assertTrue($this->form->isValid());
}
public function testValidIfBoundAndReadOnly()
{
$form = $this->getBuilder()->setReadOnly(true)->getForm();
$form->bind('foobar');
$this->assertTrue($form->isValid());
}
public function testValidIfBoundAndReadOnlyWithChildren()
{
$this->factory->expects($this->once())
->method('createNamedBuilder')
->with('text', 'name', null, array())
->will($this->returnValue($this->getBuilder('name')));
$form = $this->getBuilder('person')
->setReadOnly(true)
->add('name', 'text')
->getForm();
$form->bind(array('name' => 'Jacques Doe'));
$this->assertTrue($form->isValid());
}
/**
* @expectedException \LogicException
*/
public function testNotValidIfNotBound()
{
$this->form->isValid();
}
public function testNotValidIfErrors()
{
$this->form->bind('foobar');
$this->form->addError(new FormError('Error!'));
$this->assertFalse($this->form->isValid());
}
public function testNotValidIfChildNotValid()
{
$child = $this->getMockForm();
$child->expects($this->once())
->method('isValid')
->will($this->returnValue(false));
$this->form->bind('foobar');
$this->form->add($child);
$this->assertFalse($this->form->isValid());
}
public function testHasErrors()
{
$this->form->addError(new FormError('Error!'));
$this->assertTrue($this->form->hasErrors());
}
public function testHasNoErrors()
{
$this->assertFalse($this->form->hasErrors());
}
public function testHasChildren()
{
$this->form->add($this->getBuilder()->getForm());
$this->assertTrue($this->form->hasChildren());
}
public function testHasNoChildren()
{
$this->assertFalse($this->form->hasChildren());
}
public function testAdd()
{
$child = $this->getBuilder('foo')->getForm();
$this->form->add($child);
$this->assertSame($this->form, $child->getParent());
$this->assertSame(array('foo' => $child), $this->form->getChildren());
}
public function testRemove()
{
$child = $this->getBuilder('foo')->getForm();
$this->form->add($child);
$this->form->remove('foo');
$this->assertNull($child->getParent());
$this->assertFalse($this->form->hasChildren());
}
public function testRemoveIgnoresUnknownName()
{
$this->form->remove('notexisting');
}
public function testArrayAccess()
{
$child = $this->getBuilder('foo')->getForm();
$this->form[] = $child;
$this->assertTrue(isset($this->form['foo']));
$this->assertSame($child, $this->form['foo']);
unset($this->form['foo']);
$this->assertFalse(isset($this->form['foo']));
}
public function testCountable()
{
$this->form->add($this->getBuilder('foo')->getForm());
$this->form->add($this->getBuilder('bar')->getForm());
$this->assertCount(2, $this->form);
}
public function testIterator()
{
$this->form->add($this->getBuilder('foo')->getForm());
$this->form->add($this->getBuilder('bar')->getForm());
$this->assertSame($this->form->getChildren(), iterator_to_array($this->form));
}
public function testBound()
{
$this->form->bind('foobar');
$this->assertTrue($this->form->isBound());
}
public function testNotBound()
{
$this->assertFalse($this->form->isBound());
}
public function testSetDataExecutesTransformationChain()
{
// use real event dispatcher now
$form = $this->getBuilder('name', new EventDispatcher())
->addEventSubscriber(new FixedFilterListener(array(
'onSetData' => array(
'app' => 'filtered',
),
)))
->appendNormTransformer(new FixedDataTransformer(array(
'' => '',
'filtered' => 'norm',
)))
->appendClientTransformer(new FixedDataTransformer(array(
'' => '',
'norm' => 'client',
)))
->getForm();
$form->setData('app');
$this->assertEquals('filtered', $form->getData());
$this->assertEquals('norm', $form->getNormData());
$this->assertEquals('client', $form->getClientData());
}
public function testSetDataExecutesClientTransformersInOrder()
{
$form = $this->getBuilder()
->appendClientTransformer(new FixedDataTransformer(array(
'' => '',
'first' => 'second',
)))
->appendClientTransformer(new FixedDataTransformer(array(
'' => '',
'second' => 'third',
)))
->getForm();
$form->setData('first');
$this->assertEquals('third', $form->getClientData());
}
public function testSetDataExecutesNormTransformersInOrder()
{
$form = $this->getBuilder()
->appendNormTransformer(new FixedDataTransformer(array(
'' => '',
'first' => 'second',
)))
->appendNormTransformer(new FixedDataTransformer(array(
'' => '',
'second' => 'third',
)))
->getForm();
$form->setData('first');
$this->assertEquals('third', $form->getNormData());
}
/*
* When there is no data transformer, the data must have the same format
* in all three representations
*/
public function testSetDataConvertsScalarToStringIfNoTransformer()
{
$form = $this->getBuilder()->getForm();
$form->setData(1);
$this->assertSame('1', $form->getData());
$this->assertSame('1', $form->getNormData());
$this->assertSame('1', $form->getClientData());
}
/*
* Data in client format should, if possible, always be a string to
* facilitate differentiation between '0' and ''
*/
public function testSetDataConvertsScalarToStringIfOnlyNormTransformer()
{
$form = $this->getBuilder()
->appendNormTransformer(new FixedDataTransformer(array(
'' => '',
1 => 23,
)))
->getForm();
$form->setData(1);
$this->assertSame(1, $form->getData());
$this->assertSame(23, $form->getNormData());
$this->assertSame('23', $form->getClientData());
}
/*
* NULL remains NULL in app and norm format to remove the need to treat
* empty values and NULL explicitely in the application
*/
public function testSetDataConvertsNullToStringIfNoTransformer()
{
$form = $this->getBuilder()->getForm();
$form->setData(null);
$this->assertNull($form->getData());
$this->assertNull($form->getNormData());
$this->assertSame('', $form->getClientData());
}
public function testBindConvertsEmptyToNullIfNoTransformer()
{
$form = $this->getBuilder()->getForm();
$form->bind('');
$this->assertNull($form->getData());
$this->assertNull($form->getNormData());
$this->assertSame('', $form->getClientData());
}
public function testBindExecutesTransformationChain()
{
// use real event dispatcher now
$form = $this->getBuilder('name', new EventDispatcher())
->addEventSubscriber(new FixedFilterListener(array(
'onBindClientData' => array(
'client' => 'filteredclient',
),
'onBindNormData' => array(
'norm' => 'filterednorm',
),
)))
->appendClientTransformer(new FixedDataTransformer(array(
'' => '',
// direction is reversed!
'norm' => 'filteredclient',
'filterednorm' => 'cleanedclient'
)))
->appendNormTransformer(new FixedDataTransformer(array(
'' => '',
// direction is reversed!
'app' => 'filterednorm',
)))
->getForm();
$form->setData('app');
$this->assertEquals('app', $form->getData());
$this->assertEquals('filterednorm', $form->getNormData());
$this->assertEquals('cleanedclient', $form->getClientData());
}
public function testBindExecutesClientTransformersInReverseOrder()
{
$form = $this->getBuilder()
->appendClientTransformer(new FixedDataTransformer(array(
'' => '',
'third' => 'second',
)))
->appendClientTransformer(new FixedDataTransformer(array(
'' => '',
'second' => 'first',
)))
->getForm();
$form->bind('first');
$this->assertEquals('third', $form->getNormData());
}
public function testBindExecutesNormTransformersInReverseOrder()
{
$form = $this->getBuilder()
->appendNormTransformer(new FixedDataTransformer(array(
'' => '',
'third' => 'second',
)))
->appendNormTransformer(new FixedDataTransformer(array(
'' => '',
'second' => 'first',
)))
->getForm();
$form->bind('first');
$this->assertEquals('third', $form->getData());
}
public function testSynchronizedByDefault()
{
$this->assertTrue($this->form->isSynchronized());
}
public function testSynchronizedAfterBinding()
{
$this->form->bind('foobar');
$this->assertTrue($this->form->isSynchronized());
}
public function testNotSynchronizedIfTransformationFailed()
{
$transformer = $this->getDataTransformer();
$transformer->expects($this->once())
->method('reverseTransform')
->will($this->throwException(new TransformationFailedException()));
$form = $this->getBuilder()
->appendClientTransformer($transformer)
->getForm();
$form->bind('foobar');
$this->assertFalse($form->isSynchronized());
}
public function testEmptyDataCreatedBeforeTransforming()
{
$form = $this->getBuilder()
->setEmptyData('foo')
->appendClientTransformer(new FixedDataTransformer(array(
'' => '',
// direction is reversed!
'bar' => 'foo',
)))
->getForm();
$form->bind('');
$this->assertEquals('bar', $form->getData());
}
public function testEmptyDataFromClosure()
{
$test = $this;
$form = $this->getBuilder()
->setEmptyData(function ($form) use ($test) {
// the form instance is passed to the closure to allow use
// of form data when creating the empty value
$test->assertInstanceOf('Symfony\Component\Form\FormInterface', $form);
return 'foo';
})
->appendClientTransformer(new FixedDataTransformer(array(
'' => '',
// direction is reversed!
'bar' => 'foo',
)))
->getForm();
$form->bind('');
$this->assertEquals('bar', $form->getData());
}
public function testAddMapsClientDataToForm()
{
$mapper = $this->getDataMapper();
$form = $this->getBuilder()
->setDataMapper($mapper)
->appendClientTransformer(new FixedDataTransformer(array(
'' => '',
'foo' => 'bar',
)))
->setData('foo')
->getForm();
$child = $this->getBuilder()->getForm();
$mapper->expects($this->once())
->method('mapDataToForm')
->with('bar', $child);
$form->add($child);
}
public function testSetDataMapsClientDataToChildren()
{
$mapper = $this->getDataMapper();
$form = $this->getBuilder()
->setDataMapper($mapper)
->appendClientTransformer(new FixedDataTransformer(array(
'' => '',
'foo' => 'bar',
)))
->getForm();
$form->add($child1 = $this->getBuilder('firstName')->getForm());
$form->add($child2 = $this->getBuilder('lastName')->getForm());
$mapper->expects($this->once())
->method('mapDataToForms')
->with('bar', array('firstName' => $child1, 'lastName' => $child2));
$form->setData('foo');
}
public function testBindMapsBoundChildrenOntoExistingClientData()
{
$test = $this;
$mapper = $this->getDataMapper();
$form = $this->getBuilder()
->setDataMapper($mapper)
->appendClientTransformer(new FixedDataTransformer(array(
'' => '',
'foo' => 'bar',
)))
->setData('foo')
->getForm();
$form->add($child1 = $this->getBuilder('firstName')->getForm());
$form->add($child2 = $this->getBuilder('lastName')->getForm());
$mapper->expects($this->once())
->method('mapFormsToData')
->with(array('firstName' => $child1, 'lastName' => $child2), 'bar')
->will($this->returnCallback(function ($children, $bar) use ($test) {
$test->assertEquals('Bernhard', $children['firstName']->getData());
$test->assertEquals('Schussek', $children['lastName']->getData());
}));
$form->bind(array(
'firstName' => 'Bernhard',
'lastName' => 'Schussek',
));
}
public function testBindMapsBoundChildrenOntoEmptyData()
{
$test = $this;
$mapper = $this->getDataMapper();
$object = new \stdClass();
$form = $this->getBuilder()
->setDataMapper($mapper)
->setEmptyData($object)
->setData(null)
->getForm();
$form->add($child = $this->getBuilder('name')->getForm());
$mapper->expects($this->once())
->method('mapFormsToData')
->with(array('name' => $child), $object);
$form->bind(array(
'name' => 'Bernhard',
));
}
public function testBindValidatesAfterTransformation()
{
$test = $this;
$validator = $this->getFormValidator();
$form = $this->getBuilder()
->addValidator($validator)
->getForm();
$validator->expects($this->once())
->method('validate')
->with($form)
->will($this->returnCallback(function ($form) use ($test) {
$test->assertEquals('foobar', $form->getData());
}));
$form->bind('foobar');
}
public function requestMethodProvider()
{
return array(
array('POST'),
array('PUT'),
array('DELETE'),
array('PATCH'),
);
}
/**
* @dataProvider requestMethodProvider
*/
public function testBindPostOrPutRequest($method)
{
$path = tempnam(sys_get_temp_dir(), 'sf2');
touch($path);
$values = array(
'author' => array(
'name' => 'Bernhard',
'image' => array('filename' => 'foobar.png'),
),
);
$files = array(
'author' => array(
'error' => array('image' => UPLOAD_ERR_OK),
'name' => array('image' => 'upload.png'),
'size' => array('image' => 123),
'tmp_name' => array('image' => $path),
'type' => array('image' => 'image/png'),
),
);
$request = new Request(array(), $values, array(), array(), $files, array(
'REQUEST_METHOD' => $method,
));
$form = $this->getBuilder('author')->getForm();
$form->add($this->getBuilder('name')->getForm());
$form->add($this->getBuilder('image')->getForm());
$form->bindRequest($request);
$file = new UploadedFile($path, 'upload.png', 'image/png', 123, UPLOAD_ERR_OK);
$this->assertEquals('Bernhard', $form['name']->getData());
$this->assertEquals($file, $form['image']->getData());
unlink($path);
}
/**
* @dataProvider requestMethodProvider
*/
public function testBindPostOrPutRequestWithEmptyRootFormName($method)
{
$path = tempnam(sys_get_temp_dir(), 'sf2');
touch($path);
$values = array(
'name' => 'Bernhard',
'image' => array('filename' => 'foobar.png'),
'extra' => 'data',
);
$files = array(
'image' => array(
'error' => UPLOAD_ERR_OK,
'name' => 'upload.png',
'size' => 123,
'tmp_name' => $path,
'type' => 'image/png',
),
);
$request = new Request(array(), $values, array(), array(), $files, array(
'REQUEST_METHOD' => $method,
));
$form = $this->getBuilder('')->getForm();
$form->add($this->getBuilder('name')->getForm());
$form->add($this->getBuilder('image')->getForm());
$form->bindRequest($request);
$file = new UploadedFile($path, 'upload.png', 'image/png', 123, UPLOAD_ERR_OK);
$this->assertEquals('Bernhard', $form['name']->getData());
$this->assertEquals($file, $form['image']->getData());
$this->assertEquals(array('extra' => 'data'), $form->getExtraData());
unlink($path);
}
public function testBindGetRequest()
{
$values = array(
'author' => array(
'firstName' => 'Bernhard',
'lastName' => 'Schussek',
),
);
$request = new Request($values, array(), array(), array(), array(), array(
'REQUEST_METHOD' => 'GET',
));
$form = $this->getBuilder('author')->getForm();
$form->add($this->getBuilder('firstName')->getForm());
$form->add($this->getBuilder('lastName')->getForm());
$form->bindRequest($request);
$this->assertEquals('Bernhard', $form['firstName']->getData());
$this->assertEquals('Schussek', $form['lastName']->getData());
}
public function testBindGetRequestWithEmptyRootFormName()
{
$values = array(
'firstName' => 'Bernhard',
'lastName' => 'Schussek',
'extra' => 'data'
);
$request = new Request($values, array(), array(), array(), array(), array(
'REQUEST_METHOD' => 'GET',
));
$form = $this->getBuilder('')->getForm();
$form->add($this->getBuilder('firstName')->getForm());
$form->add($this->getBuilder('lastName')->getForm());
$form->bindRequest($request);
$this->assertEquals('Bernhard', $form['firstName']->getData());
$this->assertEquals('Schussek', $form['lastName']->getData());
$this->assertEquals(array('extra' => 'data'), $form->getExtraData());
}
public function testBindResetsErrors()
{
$form = $this->getBuilder()->getForm();
$form->addError(new FormError('Error!'));
$form->bind('foobar');
$this->assertSame(array(), $form->getErrors());
}
public function testCreateView()
{
$test = $this;
$type1 = $this->getMock('Symfony\Component\Form\FormTypeInterface');
$type1Extension = $this->getMock('Symfony\Component\Form\FormTypeExtensionInterface');
$type1->expects($this->any())
->method('getExtensions')
->will($this->returnValue(array($type1Extension)));
$type2 = $this->getMock('Symfony\Component\Form\FormTypeInterface');
$type2Extension = $this->getMock('Symfony\Component\Form\FormTypeExtensionInterface');
$type2->expects($this->any())
->method('getExtensions')
->will($this->returnValue(array($type2Extension)));
$calls = array();
$type1->expects($this->once())
->method('buildView')
->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
$calls[] = 'type1::buildView';
$test->assertTrue($view->hasParent());
$test->assertFalse($view->hasChildren());
}));
$type1Extension->expects($this->once())
->method('buildView')
->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
$calls[] = 'type1ext::buildView';
$test->assertTrue($view->hasParent());
$test->assertFalse($view->hasChildren());
}));
$type2->expects($this->once())
->method('buildView')
->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
$calls[] = 'type2::buildView';
$test->assertTrue($view->hasParent());
$test->assertFalse($view->hasChildren());
}));
$type2Extension->expects($this->once())
->method('buildView')
->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
$calls[] = 'type2ext::buildView';
$test->assertTrue($view->hasParent());
$test->assertFalse($view->hasChildren());
}));
$type1->expects($this->once())
->method('buildViewBottomUp')
->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
$calls[] = 'type1::buildViewBottomUp';
$test->assertTrue($view->hasChildren());
}));
$type1Extension->expects($this->once())
->method('buildViewBottomUp')
->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
$calls[] = 'type1ext::buildViewBottomUp';
$test->assertTrue($view->hasChildren());
}));
$type2->expects($this->once())
->method('buildViewBottomUp')
->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
$calls[] = 'type2::buildViewBottomUp';
$test->assertTrue($view->hasChildren());
}));
$type2Extension->expects($this->once())
->method('buildViewBottomUp')
->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
$calls[] = 'type2ext::buildViewBottomUp';
$test->assertTrue($view->hasChildren());
}));
$form = $this->getBuilder()->setTypes(array($type1, $type2))->getForm();
$form->setParent($this->getBuilder()->getForm());
$form->add($this->getBuilder()->getForm());
$form->createView();
$this->assertEquals(array(
0 => 'type1::buildView',
1 => 'type1ext::buildView',
2 => 'type2::buildView',
3 => 'type2ext::buildView',
4 => 'type1::buildViewBottomUp',
5 => 'type1ext::buildViewBottomUp',
6 => 'type2::buildViewBottomUp',
7 => 'type2ext::buildViewBottomUp',
), $calls);
}
public function testCreateViewAcceptsParent()
{
$parent = new FormView();
$form = $this->getBuilder()->getForm();
$view = $form->createView($parent);
$this->assertSame($parent, $view->getParent());
}
public function testGetErrorsAsString()
{
$form = $this->getBuilder()->getForm();
$form->addError(new FormError('Error!'));
$this->assertEquals("ERROR: Error!\n", $form->getErrorsAsString());
}
public function testGetErrorsAsStringDeep()
{
$form = $this->getBuilder()->getForm();
$form->addError(new FormError('Error!'));
$parent = $this->getBuilder()->getForm();
$parent->add($form);
$parent->add($this->getBuilder('foo')->getForm());
$this->assertEquals("name:\n ERROR: Error!\nfoo:\n No errors\n", $parent->getErrorsAsString());
}
public function testFormCanHaveEmptyName()
{
$form = $this->getBuilder('')->getForm();
$this->assertEquals('', $form->getName());
}
/**
* @expectedException Symfony\Component\Form\Exception\FormException
* @expectedExceptionMessage Form with empty name can not have parent form.
*/
public function testFormCannotHaveEmptyNameNotInRootLevel()
{
$parent = $this->getBuilder()
->add($this->getBuilder(''))
->getForm();
}
protected function getBuilder($name = 'name', EventDispatcherInterface $dispatcher = null)
{
return new FormBuilder($name, $this->factory, $dispatcher ?: $this->dispatcher);
}
protected function getMockForm($name = 'name')
{
$form = $this->getMock('Symfony\Tests\Component\Form\FormInterface');
$form->expects($this->any())
->method('getName')
->will($this->returnValue($name));
return $form;
}
protected function getValidForm($name)
{
$form = $this->getMockForm($name);
$form->expects($this->any())
->method('isValid')
->will($this->returnValue(true));
return $form;
}
protected function getInvalidForm($name)
{
$form = $this->getMockForm($name);
$form->expects($this->any())
->method('isValid')
->will($this->returnValue(false));
return $form;
}
protected function getDataMapper()
{
return $this->getMock('Symfony\Component\Form\DataMapperInterface');
}
protected function getDataTransformer()
{
return $this->getMock('Symfony\Component\Form\DataTransformerInterface');
}
protected function getFormValidator()
{
return $this->getMock('Symfony\Component\Form\FormValidatorInterface');
}
}