More updates to stop using dev or alpha or beta versions.
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Form / FormValidatorTest.php
1 <?php
2
3 namespace Drupal\Tests\Core\Form;
4
5 use Drupal\Core\Form\FormState;
6 use Drupal\Tests\UnitTestCase;
7 use Symfony\Component\HttpFoundation\Request;
8 use Symfony\Component\HttpFoundation\RequestStack;
9
10 /**
11  * @coversDefaultClass \Drupal\Core\Form\FormValidator
12  * @group Form
13  */
14 class FormValidatorTest extends UnitTestCase {
15
16   /**
17    * A logger instance.
18    *
19    * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject
20    */
21   protected $logger;
22
23   /**
24    * The CSRF token generator to validate the form token.
25    *
26    * @var \Drupal\Core\Access\CsrfTokenGenerator|\PHPUnit_Framework_MockObject_MockObject
27    */
28   protected $csrfToken;
29
30   /**
31    * The form error handler.
32    *
33    * @var \Drupal\Core\Form\FormErrorHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
34    */
35   protected $formErrorHandler;
36
37   /**
38    * {@inheritdoc}
39    */
40   protected function setUp() {
41     parent::setUp();
42     $this->logger = $this->getMock('Psr\Log\LoggerInterface');
43     $this->csrfToken = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
44       ->disableOriginalConstructor()
45       ->getMock();
46     $this->formErrorHandler = $this->getMock('Drupal\Core\Form\FormErrorHandlerInterface');
47   }
48
49   /**
50    * Tests the 'validation_complete' $form_state flag.
51    *
52    * @covers ::validateForm
53    * @covers ::finalizeValidation
54    */
55   public function testValidationComplete() {
56     $form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
57       ->setConstructorArgs([new RequestStack(), $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
58       ->setMethods(NULL)
59       ->getMock();
60
61     $form = [];
62     $form_state = new FormState();
63     $this->assertFalse($form_state->isValidationComplete());
64     $form_validator->validateForm('test_form_id', $form, $form_state);
65     $this->assertTrue($form_state->isValidationComplete());
66   }
67
68   /**
69    * Tests the 'must_validate' $form_state flag.
70    *
71    * @covers ::validateForm
72    */
73   public function testPreventDuplicateValidation() {
74     $form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
75       ->setConstructorArgs([new RequestStack(), $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
76       ->setMethods(['doValidateForm'])
77       ->getMock();
78     $form_validator->expects($this->never())
79       ->method('doValidateForm');
80
81     $form = [];
82     $form_state = (new FormState())
83       ->setValidationComplete();
84     $form_validator->validateForm('test_form_id', $form, $form_state);
85     $this->assertArrayNotHasKey('#errors', $form);
86   }
87
88   /**
89    * Tests the 'must_validate' $form_state flag.
90    *
91    * @covers ::validateForm
92    */
93   public function testMustValidate() {
94     $form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
95       ->setConstructorArgs([new RequestStack(), $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
96       ->setMethods(['doValidateForm'])
97       ->getMock();
98     $form_validator->expects($this->once())
99       ->method('doValidateForm');
100     $this->formErrorHandler->expects($this->once())
101       ->method('handleFormErrors');
102
103     $form = [];
104     $form_state = (new FormState())
105       ->setValidationComplete()
106       ->setValidationEnforced();
107     $form_validator->validateForm('test_form_id', $form, $form_state);
108   }
109
110   /**
111    * @covers ::validateForm
112    */
113   public function testValidateInvalidFormToken() {
114     $request_stack = new RequestStack();
115     $request = new Request([], [], [], [], [], ['REQUEST_URI' => '/test/example?foo=bar']);
116     $request_stack->push($request);
117     $this->csrfToken->expects($this->once())
118       ->method('validate')
119       ->will($this->returnValue(FALSE));
120
121     $form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
122       ->setConstructorArgs([$request_stack, $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
123       ->setMethods(['doValidateForm'])
124       ->getMock();
125     $form_validator->expects($this->never())
126       ->method('doValidateForm');
127
128     $form['#token'] = 'test_form_id';
129     $form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
130       ->setMethods(['setErrorByName'])
131       ->getMock();
132     $form_state->expects($this->once())
133       ->method('setErrorByName')
134       ->with('form_token', 'The form has become outdated. Copy any unsaved work in the form below and then <a href="/test/example?foo=bar">reload this page</a>.');
135     $form_state->setValue('form_token', 'some_random_token');
136     $form_validator->validateForm('test_form_id', $form, $form_state);
137     $this->assertTrue($form_state->isValidationComplete());
138   }
139
140   /**
141    * @covers ::validateForm
142    */
143   public function testValidateValidFormToken() {
144     $request_stack = new RequestStack();
145     $this->csrfToken->expects($this->once())
146       ->method('validate')
147       ->will($this->returnValue(TRUE));
148
149     $form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
150       ->setConstructorArgs([$request_stack, $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
151       ->setMethods(['doValidateForm'])
152       ->getMock();
153     $form_validator->expects($this->once())
154       ->method('doValidateForm');
155
156     $form['#token'] = 'test_form_id';
157     $form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
158       ->setMethods(['setErrorByName'])
159       ->getMock();
160     $form_state->expects($this->never())
161       ->method('setErrorByName');
162     $form_state->setValue('form_token', 'some_random_token');
163     $form_validator->validateForm('test_form_id', $form, $form_state);
164     $this->assertTrue($form_state->isValidationComplete());
165   }
166
167   /**
168    * @covers ::handleErrorsWithLimitedValidation
169    *
170    * @dataProvider providerTestHandleErrorsWithLimitedValidation
171    */
172   public function testHandleErrorsWithLimitedValidation($sections, $triggering_element, $values, $expected) {
173     $form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
174       ->setConstructorArgs([new RequestStack(), $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
175       ->setMethods(NULL)
176       ->getMock();
177
178     $triggering_element['#limit_validation_errors'] = $sections;
179     $form = [];
180     $form_state = (new FormState())
181       ->setValues($values)
182       ->setTriggeringElement($triggering_element);
183
184     $form_validator->validateForm('test_form_id', $form, $form_state);
185     $this->assertSame($expected, $form_state->getValues());
186   }
187
188   public function providerTestHandleErrorsWithLimitedValidation() {
189     return [
190       // Test with a non-existent section.
191       [
192         [['test1'], ['test3']],
193         [],
194         [
195           'test1' => 'foo',
196           'test2' => 'bar',
197         ],
198         [
199           'test1' => 'foo',
200         ],
201       ],
202       // Test with buttons in a non-validated section.
203       [
204         [['test1']],
205         [
206           '#is_button' => TRUE,
207           '#value' => 'baz',
208           '#name' => 'op',
209           '#parents' => ['submit'],
210         ],
211         [
212           'test1' => 'foo',
213           'test2' => 'bar',
214           'op' => 'baz',
215           'submit' => 'baz',
216         ],
217         [
218           'test1' => 'foo',
219           'submit' => 'baz',
220           'op' => 'baz',
221         ],
222       ],
223       // Test with a matching button #value and $form_state value.
224       [
225         [['submit']],
226         [
227           '#is_button' => TRUE,
228           '#value' => 'baz',
229           '#name' => 'op',
230           '#parents' => ['submit'],
231         ],
232         [
233           'test1' => 'foo',
234           'test2' => 'bar',
235           'op' => 'baz',
236           'submit' => 'baz',
237         ],
238         [
239           'submit' => 'baz',
240           'op' => 'baz',
241         ],
242       ],
243       // Test with a mismatched button #value and $form_state value.
244       [
245         [['submit']],
246         [
247           '#is_button' => TRUE,
248           '#value' => 'bar',
249           '#name' => 'op',
250           '#parents' => ['submit'],
251         ],
252         [
253           'test1' => 'foo',
254           'test2' => 'bar',
255           'op' => 'baz',
256           'submit' => 'baz',
257         ],
258         [
259           'submit' => 'baz',
260         ],
261       ],
262     ];
263   }
264
265   /**
266    * @covers ::executeValidateHandlers
267    */
268   public function testExecuteValidateHandlers() {
269     $form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
270       ->setConstructorArgs([new RequestStack(), $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
271       ->setMethods(NULL)
272       ->getMock();
273     $mock = $this->getMock('stdClass', ['validate_handler', 'hash_validate']);
274     $mock->expects($this->once())
275       ->method('validate_handler')
276       ->with($this->isType('array'), $this->isInstanceOf('Drupal\Core\Form\FormStateInterface'));
277     $mock->expects($this->once())
278       ->method('hash_validate')
279       ->with($this->isType('array'), $this->isInstanceOf('Drupal\Core\Form\FormStateInterface'));
280
281     $form = [];
282     $form_state = new FormState();
283     $form_validator->executeValidateHandlers($form, $form_state);
284
285     $form['#validate'][] = [$mock, 'hash_validate'];
286     $form_validator->executeValidateHandlers($form, $form_state);
287
288     // $form_state validate handlers will supersede $form handlers.
289     $validate_handlers[] = [$mock, 'validate_handler'];
290     $form_state->setValidateHandlers($validate_handlers);
291     $form_validator->executeValidateHandlers($form, $form_state);
292   }
293
294   /**
295    * @covers ::doValidateForm
296    *
297    * @dataProvider providerTestRequiredErrorMessage
298    */
299   public function testRequiredErrorMessage($element, $expected_message) {
300     $form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
301       ->setConstructorArgs([new RequestStack(), $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
302       ->setMethods(['executeValidateHandlers'])
303       ->getMock();
304     $form_validator->expects($this->once())
305       ->method('executeValidateHandlers');
306
307     $form = [];
308     $form['test'] = $element + [
309       '#type' => 'textfield',
310       '#value' => '',
311       '#needs_validation' => TRUE,
312       '#required' => TRUE,
313       '#parents' => ['test'],
314     ];
315     $form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
316       ->setMethods(['setError'])
317       ->getMock();
318     $form_state->expects($this->once())
319       ->method('setError')
320       ->with($this->isType('array'), $expected_message);
321     $form_validator->validateForm('test_form_id', $form, $form_state);
322   }
323
324   public function providerTestRequiredErrorMessage() {
325     return [
326       [
327         // Use the default message with a title.
328         ['#title' => 'Test'],
329         'Test field is required.',
330       ],
331       // Use a custom message.
332       [
333         ['#required_error' => 'FAIL'],
334         'FAIL',
335       ],
336       // No title or custom message.
337       [
338         [],
339         '',
340       ],
341     ];
342   }
343
344   /**
345    * @covers ::doValidateForm
346    */
347   public function testElementValidate() {
348     $form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
349       ->setConstructorArgs([new RequestStack(), $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
350       ->setMethods(['executeValidateHandlers'])
351       ->getMock();
352     $form_validator->expects($this->once())
353       ->method('executeValidateHandlers');
354     $mock = $this->getMock('stdClass', ['element_validate']);
355     $mock->expects($this->once())
356       ->method('element_validate')
357       ->with($this->isType('array'), $this->isInstanceOf('Drupal\Core\Form\FormStateInterface'), NULL);
358
359     $form = [];
360     $form['test'] = [
361       '#type' => 'textfield',
362       '#title' => 'Test',
363       '#parents' => ['test'],
364       '#element_validate' => [[$mock, 'element_validate']],
365     ];
366     $form_state = new FormState();
367     $form_validator->validateForm('test_form_id', $form, $form_state);
368   }
369
370   /**
371    * @covers ::performRequiredValidation
372    *
373    * @dataProvider providerTestPerformRequiredValidation
374    */
375   public function testPerformRequiredValidation($element, $expected_message, $call_watchdog) {
376     $form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
377       ->setConstructorArgs([new RequestStack(), $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
378       ->setMethods(['setError'])
379       ->getMock();
380
381     if ($call_watchdog) {
382       $this->logger->expects($this->once())
383         ->method('error')
384         ->with($this->isType('string'), $this->isType('array'));
385     }
386
387     $form = [];
388     $form['test'] = $element + [
389       '#title' => 'Test',
390       '#needs_validation' => TRUE,
391       '#required' => FALSE,
392       '#parents' => ['test'],
393     ];
394     $form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
395       ->setMethods(['setError'])
396       ->getMock();
397     $form_state->expects($this->once())
398       ->method('setError')
399       ->with($this->isType('array'), $expected_message);
400     $form_validator->validateForm('test_form_id', $form, $form_state);
401   }
402
403   public function providerTestPerformRequiredValidation() {
404     return [
405       [
406         [
407           '#type' => 'select',
408           '#options' => [
409             'foo' => 'Foo',
410             'bar' => 'Bar',
411           ],
412           '#required' => TRUE,
413           '#value' => 'baz',
414           '#empty_value' => 'baz',
415           '#multiple' => FALSE,
416         ],
417         'Test field is required.',
418         FALSE,
419       ],
420       [
421         [
422           '#type' => 'select',
423           '#options' => [
424             'foo' => 'Foo',
425             'bar' => 'Bar',
426           ],
427           '#value' => 'baz',
428           '#multiple' => FALSE,
429         ],
430         'An illegal choice has been detected. Please contact the site administrator.',
431         TRUE,
432       ],
433       [
434         [
435           '#type' => 'checkboxes',
436           '#options' => [
437             'foo' => 'Foo',
438             'bar' => 'Bar',
439           ],
440           '#value' => ['baz'],
441           '#multiple' => TRUE,
442         ],
443         'An illegal choice has been detected. Please contact the site administrator.',
444         TRUE,
445       ],
446       [
447         [
448           '#type' => 'select',
449           '#options' => [
450             'foo' => 'Foo',
451             'bar' => 'Bar',
452           ],
453           '#value' => ['baz'],
454           '#multiple' => TRUE,
455         ],
456         'An illegal choice has been detected. Please contact the site administrator.',
457         TRUE,
458       ],
459       [
460         [
461           '#type' => 'textfield',
462           '#maxlength' => 7,
463           '#value' => $this->randomMachineName(8),
464         ],
465         'Test cannot be longer than <em class="placeholder">7</em> characters but is currently <em class="placeholder">8</em> characters long.',
466         FALSE,
467       ],
468     ];
469   }
470
471 }