1<?php
2
3declare(strict_types=1);
4
5namespace JMS\Serializer\Tests\Handler;
6
7use JMS\Serializer\GraphNavigatorInterface;
8use JMS\Serializer\Handler\FormErrorHandler;
9use JMS\Serializer\JsonSerializationVisitor;
10use JMS\Serializer\SerializationContext;
11use JMS\Serializer\Visitor\Factory\JsonSerializationVisitorFactory;
12use PHPUnit\Framework\TestCase;
13use Symfony\Component\EventDispatcher\EventDispatcher;
14use Symfony\Component\EventDispatcher\EventDispatcherInterface;
15use Symfony\Component\Form\Extension\Core\Type\FormType;
16use Symfony\Component\Form\Extension\Core\Type\TextType;
17use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
18use Symfony\Component\Form\FormBuilder;
19use Symfony\Component\Form\FormError;
20use Symfony\Component\Form\FormFactoryInterface;
21use Symfony\Component\Form\Forms;
22use Symfony\Component\Translation\Translator;
23use Symfony\Component\Validator\Constraints\Length;
24use Symfony\Component\Validator\Validation;
25
26class FormErrorHandlerTest extends TestCase
27{
28    /**
29     * @var FormErrorHandler
30     */
31    protected $handler;
32
33    /**
34     * @var JsonSerializationVisitor
35     */
36    protected $visitor;
37
38    /**
39     * @var EventDispatcherInterface
40     */
41    protected $dispatcher;
42
43    /**
44     * @var FormFactoryInterface
45     */
46    protected $factory;
47
48    public function setUp()
49    {
50        $this->handler = new FormErrorHandler(new Translator('en'));
51        $navigator = $this->getMockBuilder(GraphNavigatorInterface::class)->getMock();
52        $context = SerializationContext::create();
53        $this->visitor = (new JsonSerializationVisitorFactory())->getVisitor($navigator, $context);
54        $this->dispatcher = new EventDispatcher();
55        $this->factory = $this->getMockBuilder('Symfony\Component\Form\FormFactoryInterface')->getMock();
56    }
57
58    protected function tearDown()
59    {
60        $this->handler = null;
61        $this->visitor = null;
62        $this->dispatcher = null;
63        $this->factory = null;
64    }
65
66    public function testSerializeEmptyFormError()
67    {
68        $form = $this->createForm();
69        $json = json_encode($this->handler->serializeFormToJson($this->visitor, $form, []));
70
71        self::assertSame('{}', $json);
72    }
73
74    public function testErrorHandlerWithoutTranslator()
75    {
76        $this->handler = new FormErrorHandler();
77        $form = $this->createForm();
78        $form->addError(new FormError('error!'));
79        $json = json_encode($this->handler->serializeFormToJson($this->visitor, $form, []));
80
81        self::assertSame(json_encode([
82            'errors' => ['error!'],
83        ]), $json);
84    }
85
86    public function testSerializeHasFormError()
87    {
88        $form = $this->createForm();
89        $form->addError(new FormError('error!'));
90        $json = json_encode($this->handler->serializeFormToJson($this->visitor, $form, []));
91
92        self::assertSame(json_encode([
93            'errors' => ['error!'],
94        ]), $json);
95    }
96
97    public function testSerializeFormWithData()
98    {
99        $formFactoryBuilder = Forms::createFormFactoryBuilder();
100        $formFactoryBuilder->addExtension(new ValidatorExtension(Validation::createValidator()));
101
102        $formFactory = $formFactoryBuilder->getFormFactory();
103        $builer = $formFactory->createNamedBuilder('foo', FormType::class);
104
105        $builer->add('url', TextType::class);
106        $builer->add('txt', TextType::class, [
107            'constraints' => [
108                new Length(['min' => 10]),
109            ],
110        ]);
111
112        $form = $builer->getForm();
113
114        $form->submit([
115            'url' => 'hi',
116            'txt' => 'hello',
117        ]);
118
119        $data = json_encode($this->handler->serializeFormToJson($this->visitor, $form, []));
120        self::assertSame('{"children":{"url":{},"txt":{"errors":["This value is too short. It should have 10 characters or more."]}}}', $data);
121    }
122
123    public function testSerializeChildElements()
124    {
125        $formFactory = Forms::createFormFactory();
126        $form = $formFactory->createBuilder()
127            ->add('child')
128            ->add('date')
129            ->getForm();
130
131        $form->addError(new FormError('error!'));
132        $form->get('date')->addError(new FormError('child-error'));
133
134        $json = json_encode($this->handler->serializeFormToJson($this->visitor, $form, []));
135
136        self::assertSame(json_encode([
137            'errors' => ['error!'],
138            'children' => [
139                'child' => new \stdClass(),
140                'date' => ['errors' => ['child-error']],
141            ],
142        ]), $json);
143    }
144
145    public function testDefaultTranslationDomain()
146    {
147        /** @var Translator|\PHPUnit_Framework_MockObject_MockObject $translator */
148        $translator = $this->getMockBuilder('Symfony\Component\Translation\TranslatorInterface')->getMock();
149
150        $handler = new FormErrorHandler($translator);
151
152        $translator->expects($this->once())
153            ->method('trans')
154            ->with(
155                $this->equalTo('error!'),
156                $this->equalTo([]),
157                $this->equalTo('validators')
158            );
159
160        $formError = $this->getMockBuilder('Symfony\Component\Form\FormError')->disableOriginalConstructor()->getMock();
161        $formError->expects($this->once())->method('getMessageTemplate')->willReturn('error!');
162        $formError->expects($this->once())->method('getMessagePluralization')->willReturn(null);
163        $formError->expects($this->once())->method('getMessageParameters')->willReturn([]);
164
165        $this->invokeMethod($handler, 'getErrorMessage', [$formError]);
166    }
167
168    public function testDefaultTranslationDomainWithPluralTranslation()
169    {
170        /** @var Translator|\PHPUnit_Framework_MockObject_MockObject $translator */
171        $translator = $this->getMockBuilder('Symfony\Component\Translation\TranslatorInterface')->getMock();
172
173        $handler = new FormErrorHandler($translator);
174
175        $translator->expects($this->once())
176            ->method('transChoice')
177            ->with(
178                $this->equalTo('error!'),
179                $this->equalTo(0),
180                $this->equalTo([]),
181                $this->equalTo('validators')
182            );
183
184        $formError = $this->getMockBuilder('Symfony\Component\Form\FormError')->disableOriginalConstructor()->getMock();
185        $formError->expects($this->once())->method('getMessageTemplate')->willReturn('error!');
186        $formError->expects($this->exactly(2))->method('getMessagePluralization')->willReturn(0);
187        $formError->expects($this->once())->method('getMessageParameters')->willReturn([]);
188
189        $this->invokeMethod($handler, 'getErrorMessage', [$formError]);
190    }
191
192    public function testCustomTranslationDomain()
193    {
194        /** @var Translator|\PHPUnit_Framework_MockObject_MockObject $translator */
195        $translator = $this->getMockBuilder('Symfony\Component\Translation\TranslatorInterface')->getMock();
196
197        $handler = new FormErrorHandler($translator, 'custom_domain');
198
199        $translator->expects($this->once())
200            ->method('trans')
201            ->with(
202                $this->equalTo('error!'),
203                $this->equalTo([]),
204                $this->equalTo('custom_domain')
205            );
206
207        $formError = $this->getMockBuilder('Symfony\Component\Form\FormError')->disableOriginalConstructor()->getMock();
208        $formError->expects($this->once())->method('getMessageTemplate')->willReturn('error!');
209        $formError->expects($this->once())->method('getMessagePluralization')->willReturn(null);
210        $formError->expects($this->once())->method('getMessageParameters')->willReturn([]);
211
212        $this->invokeMethod($handler, 'getErrorMessage', [$formError]);
213    }
214
215    public function testCustomTranslationDomainWithPluralTranslation()
216    {
217        /** @var Translator|\PHPUnit_Framework_MockObject_MockObject $translator */
218        $translator = $this->getMockBuilder('Symfony\Component\Translation\TranslatorInterface')->getMock();
219
220        $handler = new FormErrorHandler($translator, 'custom_domain');
221
222        $translator->expects($this->once())
223            ->method('transChoice')
224            ->with(
225                $this->equalTo('error!'),
226                $this->equalTo(0),
227                $this->equalTo([]),
228                $this->equalTo('custom_domain')
229            );
230
231        $formError = $this->getMockBuilder('Symfony\Component\Form\FormError')->disableOriginalConstructor()->getMock();
232        $formError->expects($this->once())->method('getMessageTemplate')->willReturn('error!');
233        $formError->expects($this->exactly(2))->method('getMessagePluralization')->willReturn(0);
234        $formError->expects($this->once())->method('getMessageParameters')->willReturn([]);
235
236        $this->invokeMethod($handler, 'getErrorMessage', [$formError]);
237    }
238
239    /**
240     * @param string $name
241     * @param EventDispatcherInterface $dispatcher
242     * @param string $dataClass
243     *
244     * @return FormBuilder
245     */
246    protected function getBuilder($name = 'name', ?EventDispatcherInterface $dispatcher = null, $dataClass = null)
247    {
248        return new FormBuilder($name, $dataClass, $dispatcher ?: $this->dispatcher, $this->factory);
249    }
250
251    /**
252     * @param string $name
253     *
254     * @return \PHPUnit_Framework_MockObject_MockObject
255     */
256    protected function getMockForm($name = 'name')
257    {
258        $form = $this->getMockBuilder('Symfony\Component\Form\Test\FormInterface')->getMock();
259        $config = $this->getMockBuilder('Symfony\Component\Form\FormConfigInterface')->getMock();
260
261        $form->expects($this->any())
262            ->method('getName')
263            ->will($this->returnValue($name));
264        $form->expects($this->any())
265            ->method('getConfig')
266            ->will($this->returnValue($config));
267
268        return $form;
269    }
270
271    protected function createForm()
272    {
273        return $this->getBuilder()->getForm();
274    }
275
276    protected function invokeMethod($object, $method, array $args = [])
277    {
278        $reflectionMethod = new \ReflectionMethod($object, $method);
279        $reflectionMethod->setAccessible(true);
280
281        return $reflectionMethod->invokeArgs($object, $args);
282    }
283}
284