3 namespace Drupal\Tests\Core\Form;
5 use Drupal\Core\Form\FormState;
6 use Drupal\Tests\UnitTestCase;
7 use Symfony\Component\HttpFoundation\Request;
8 use Symfony\Component\HttpFoundation\RequestStack;
11 * @coversDefaultClass \Drupal\Core\Form\FormValidator
14 class FormValidatorTest extends UnitTestCase {
19 * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject
24 * The CSRF token generator to validate the form token.
26 * @var \Drupal\Core\Access\CsrfTokenGenerator|\PHPUnit_Framework_MockObject_MockObject
31 * The form error handler.
33 * @var \Drupal\Core\Form\FormErrorHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
35 protected $formErrorHandler;
40 protected function setUp() {
42 $this->logger = $this->getMock('Psr\Log\LoggerInterface');
43 $this->csrfToken = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
44 ->disableOriginalConstructor()
46 $this->formErrorHandler = $this->getMock('Drupal\Core\Form\FormErrorHandlerInterface');
50 * Tests the 'validation_complete' $form_state flag.
52 * @covers ::validateForm
53 * @covers ::finalizeValidation
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])
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());
69 * Tests the 'must_validate' $form_state flag.
71 * @covers ::validateForm
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'])
78 $form_validator->expects($this->never())
79 ->method('doValidateForm');
82 $form_state = (new FormState())
83 ->setValidationComplete();
84 $form_validator->validateForm('test_form_id', $form, $form_state);
85 $this->assertArrayNotHasKey('#errors', $form);
89 * Tests the 'must_validate' $form_state flag.
91 * @covers ::validateForm
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'])
98 $form_validator->expects($this->once())
99 ->method('doValidateForm');
100 $this->formErrorHandler->expects($this->once())
101 ->method('handleFormErrors');
104 $form_state = (new FormState())
105 ->setValidationComplete()
106 ->setValidationEnforced();
107 $form_validator->validateForm('test_form_id', $form, $form_state);
111 * @covers ::validateForm
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())
119 ->will($this->returnValue(FALSE));
121 $form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
122 ->setConstructorArgs([$request_stack, $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
123 ->setMethods(['doValidateForm'])
125 $form_validator->expects($this->never())
126 ->method('doValidateForm');
128 $form['#token'] = 'test_form_id';
129 $form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
130 ->setMethods(['setErrorByName'])
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());
141 * @covers ::validateForm
143 public function testValidateValidFormToken() {
144 $request_stack = new RequestStack();
145 $this->csrfToken->expects($this->once())
147 ->will($this->returnValue(TRUE));
149 $form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
150 ->setConstructorArgs([$request_stack, $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
151 ->setMethods(['doValidateForm'])
153 $form_validator->expects($this->once())
154 ->method('doValidateForm');
156 $form['#token'] = 'test_form_id';
157 $form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
158 ->setMethods(['setErrorByName'])
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());
168 * @covers ::handleErrorsWithLimitedValidation
170 * @dataProvider providerTestHandleErrorsWithLimitedValidation
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])
178 $triggering_element['#limit_validation_errors'] = $sections;
180 $form_state = (new FormState())
182 ->setTriggeringElement($triggering_element);
184 $form_validator->validateForm('test_form_id', $form, $form_state);
185 $this->assertSame($expected, $form_state->getValues());
188 public function providerTestHandleErrorsWithLimitedValidation() {
190 // Test with a non-existent section.
192 [['test1'], ['test3']],
202 // Test with buttons in a non-validated section.
206 '#is_button' => TRUE,
209 '#parents' => ['submit'],
223 // Test with a matching button #value and $form_state value.
227 '#is_button' => TRUE,
230 '#parents' => ['submit'],
243 // Test with a mismatched button #value and $form_state value.
247 '#is_button' => TRUE,
250 '#parents' => ['submit'],
266 * @covers ::executeValidateHandlers
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])
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'));
282 $form_state = new FormState();
283 $form_validator->executeValidateHandlers($form, $form_state);
285 $form['#validate'][] = [$mock, 'hash_validate'];
286 $form_validator->executeValidateHandlers($form, $form_state);
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);
295 * @covers ::doValidateForm
297 * @dataProvider providerTestRequiredErrorMessage
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'])
304 $form_validator->expects($this->once())
305 ->method('executeValidateHandlers');
308 $form['test'] = $element + [
309 '#type' => 'textfield',
311 '#needs_validation' => TRUE,
313 '#parents' => ['test'],
315 $form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
316 ->setMethods(['setError'])
318 $form_state->expects($this->once())
320 ->with($this->isType('array'), $expected_message);
321 $form_validator->validateForm('test_form_id', $form, $form_state);
324 public function providerTestRequiredErrorMessage() {
327 // Use the default message with a title.
328 ['#title' => 'Test'],
329 'Test field is required.',
331 // Use a custom message.
333 ['#required_error' => 'FAIL'],
336 // No title or custom message.
345 * @covers ::doValidateForm
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'])
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);
361 '#type' => 'textfield',
363 '#parents' => ['test'],
364 '#element_validate' => [[$mock, 'element_validate']],
366 $form_state = new FormState();
367 $form_validator->validateForm('test_form_id', $form, $form_state);
371 * @covers ::performRequiredValidation
373 * @dataProvider providerTestPerformRequiredValidation
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'])
381 if ($call_watchdog) {
382 $this->logger->expects($this->once())
384 ->with($this->isType('string'), $this->isType('array'));
388 $form['test'] = $element + [
390 '#needs_validation' => TRUE,
391 '#required' => FALSE,
392 '#parents' => ['test'],
394 $form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
395 ->setMethods(['setError'])
397 $form_state->expects($this->once())
399 ->with($this->isType('array'), $expected_message);
400 $form_validator->validateForm('test_form_id', $form, $form_state);
403 public function providerTestPerformRequiredValidation() {
414 '#empty_value' => 'baz',
415 '#multiple' => FALSE,
417 'Test field is required.',
428 '#multiple' => FALSE,
430 'An illegal choice has been detected. Please contact the site administrator.',
435 '#type' => 'checkboxes',
443 'An illegal choice has been detected. Please contact the site administrator.',
456 'An illegal choice has been detected. Please contact the site administrator.',
461 '#type' => 'textfield',
463 '#value' => $this->randomMachineName(8),
465 'Test cannot be longer than <em class="placeholder">7</em> characters but is currently <em class="placeholder">8</em> characters long.',