5 * Contains \Drupal\Tests\Core\Form\FormBuilderTest.
8 namespace Drupal\Tests\Core\Form;
10 use Drupal\Component\Utility\Html;
11 use Drupal\Component\Utility\NestedArray;
12 use Drupal\Core\Access\AccessResult;
13 use Drupal\Core\Cache\Context\CacheContextsManager;
14 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
15 use Drupal\Core\Form\EnforcedResponseException;
16 use Drupal\Core\Form\Exception\BrokenPostRequestException;
17 use Drupal\Core\Form\FormBuilder;
18 use Drupal\Core\Form\FormBuilderInterface;
19 use Drupal\Core\Form\FormInterface;
20 use Drupal\Core\Form\FormState;
21 use Drupal\Core\Form\FormStateInterface;
22 use Drupal\Core\Session\AccountInterface;
23 use Drupal\Core\Session\AccountProxyInterface;
24 use Symfony\Component\DependencyInjection\ContainerBuilder;
25 use Symfony\Component\DependencyInjection\ContainerInterface;
26 use Symfony\Component\HttpFoundation\Request;
27 use Symfony\Component\HttpFoundation\RequestStack;
30 * @coversDefaultClass \Drupal\Core\Form\FormBuilder
33 class FormBuilderTest extends FormTestBase {
36 * The dependency injection container.
38 * @var \Symfony\Component\DependencyInjection\ContainerBuilder
45 protected function setUp() {
48 $this->container = new ContainerBuilder();
49 $cache_contexts_manager = $this->prophesize(CacheContextsManager::class)->reveal();
50 $this->container->set('cache_contexts_manager', $cache_contexts_manager);
51 \Drupal::setContainer($this->container);
55 * Tests the getFormId() method with a string based form ID.
57 public function testGetFormIdWithString() {
59 $form_state = new FormState();
60 $this->setExpectedException(\InvalidArgumentException::class, 'The form argument foo is not a valid form.');
61 $this->formBuilder->getFormId($form_arg, $form_state);
65 * Tests the getFormId() method with a class name form ID.
67 public function testGetFormIdWithClassName() {
68 $form_arg = 'Drupal\Tests\Core\Form\TestForm';
70 $form_state = new FormState();
71 $form_id = $this->formBuilder->getFormId($form_arg, $form_state);
73 $this->assertSame('test_form', $form_id);
74 $this->assertSame($form_arg, get_class($form_state->getFormObject()));
78 * Tests the getFormId() method with an injected class name form ID.
80 public function testGetFormIdWithInjectedClassName() {
81 $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
82 \Drupal::setContainer($container);
84 $form_arg = 'Drupal\Tests\Core\Form\TestFormInjected';
86 $form_state = new FormState();
87 $form_id = $this->formBuilder->getFormId($form_arg, $form_state);
89 $this->assertSame('test_form', $form_id);
90 $this->assertSame($form_arg, get_class($form_state->getFormObject()));
94 * Tests the getFormId() method with a form object.
96 public function testGetFormIdWithObject() {
97 $expected_form_id = 'my_module_form_id';
99 $form_arg = $this->getMockForm($expected_form_id);
101 $form_state = new FormState();
102 $form_id = $this->formBuilder->getFormId($form_arg, $form_state);
104 $this->assertSame($expected_form_id, $form_id);
105 $this->assertSame($form_arg, $form_state->getFormObject());
109 * Tests the getFormId() method with a base form object.
111 public function testGetFormIdWithBaseForm() {
112 $expected_form_id = 'my_module_form_id';
113 $base_form_id = 'my_module';
115 $form_arg = $this->getMock('Drupal\Core\Form\BaseFormIdInterface');
116 $form_arg->expects($this->once())
117 ->method('getFormId')
118 ->will($this->returnValue($expected_form_id));
119 $form_arg->expects($this->once())
120 ->method('getBaseFormId')
121 ->will($this->returnValue($base_form_id));
123 $form_state = new FormState();
124 $form_id = $this->formBuilder->getFormId($form_arg, $form_state);
126 $this->assertSame($expected_form_id, $form_id);
127 $this->assertSame($form_arg, $form_state->getFormObject());
128 $this->assertSame($base_form_id, $form_state->getBuildInfo()['base_form_id']);
132 * Tests the handling of FormStateInterface::$response.
134 * @dataProvider formStateResponseProvider
136 public function testHandleFormStateResponse($class, $form_state_key) {
137 $form_id = 'test_form_id';
138 $expected_form = $form_id();
140 $response = $this->getMockBuilder($class)
141 ->disableOriginalConstructor()
144 $form_arg = $this->getMockForm($form_id, $expected_form);
145 $form_arg->expects($this->any())
146 ->method('submitForm')
147 ->will($this->returnCallback(function ($form, FormStateInterface $form_state) use ($response, $form_state_key) {
148 $form_state->setFormState([$form_state_key => $response]);
151 $form_state = new FormState();
153 $input['form_id'] = $form_id;
154 $form_state->setUserInput($input);
155 $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
156 $this->fail('EnforcedResponseException was not thrown.');
158 catch (EnforcedResponseException $e) {
159 $this->assertSame($response, $e->getResponse());
161 $this->assertSame($response, $form_state->getResponse());
165 * Provides test data for testHandleFormStateResponse().
167 public function formStateResponseProvider() {
169 ['Symfony\Component\HttpFoundation\Response', 'response'],
170 ['Symfony\Component\HttpFoundation\RedirectResponse', 'redirect'],
175 * Tests the handling of a redirect when FormStateInterface::$response exists.
177 public function testHandleRedirectWithResponse() {
178 $form_id = 'test_form_id';
179 $expected_form = $form_id();
181 // Set up a response that will be used.
182 $response = $this->getMockBuilder('Symfony\Component\HttpFoundation\Response')
183 ->disableOriginalConstructor()
186 // Set up a redirect that will not be called.
187 $redirect = $this->getMockBuilder('Symfony\Component\HttpFoundation\RedirectResponse')
188 ->disableOriginalConstructor()
191 $form_arg = $this->getMockForm($form_id, $expected_form);
192 $form_arg->expects($this->any())
193 ->method('submitForm')
194 ->will($this->returnCallback(function ($form, FormStateInterface $form_state) use ($response, $redirect) {
195 // Set both the response and the redirect.
196 $form_state->setResponse($response);
197 $form_state->set('redirect', $redirect);
200 $form_state = new FormState();
202 $input['form_id'] = $form_id;
203 $form_state->setUserInput($input);
204 $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
205 $this->fail('EnforcedResponseException was not thrown.');
207 catch (EnforcedResponseException $e) {
208 $this->assertSame($response, $e->getResponse());
210 $this->assertSame($response, $form_state->getResponse());
214 * Tests the getForm() method with a string based form ID.
216 public function testGetFormWithString() {
217 $form_id = 'test_form_id';
218 $this->setExpectedException(\InvalidArgumentException::class, 'The form argument test_form_id is not a valid form.');
219 $this->formBuilder->getForm($form_id);
223 * Tests the getForm() method with a form object.
225 public function testGetFormWithObject() {
226 $form_id = 'test_form_id';
227 $expected_form = $form_id();
229 $form_arg = $this->getMockForm($form_id, $expected_form);
231 $form = $this->formBuilder->getForm($form_arg);
232 $this->assertFormElement($expected_form, $form, 'test');
233 $this->assertArrayHasKey('#id', $form);
237 * Tests the getForm() method with a class name based form ID.
239 public function testGetFormWithClassString() {
240 $form_id = '\Drupal\Tests\Core\Form\TestForm';
241 $object = new TestForm();
243 $form_state = new FormState();
244 $expected_form = $object->buildForm($form, $form_state);
246 $form = $this->formBuilder->getForm($form_id);
247 $this->assertFormElement($expected_form, $form, 'test');
248 $this->assertSame('test-form', $form['#id']);
252 * Tests the buildForm() method with a string based form ID.
254 public function testBuildFormWithString() {
255 $form_id = 'test_form_id';
256 $this->setExpectedException(\InvalidArgumentException::class, 'The form argument test_form_id is not a valid form.');
257 $this->formBuilder->getForm($form_id);
261 * Tests the buildForm() method with a class name based form ID.
263 public function testBuildFormWithClassString() {
264 $form_id = '\Drupal\Tests\Core\Form\TestForm';
265 $object = new TestForm();
267 $form_state = new FormState();
268 $expected_form = $object->buildForm($form, $form_state);
270 $form = $this->formBuilder->buildForm($form_id, $form_state);
271 $this->assertFormElement($expected_form, $form, 'test');
272 $this->assertSame('test-form', $form['#id']);
276 * Tests the buildForm() method with a form object.
278 public function testBuildFormWithObject() {
279 $form_id = 'test_form_id';
280 $expected_form = $form_id();
282 $form_arg = $this->getMockForm($form_id, $expected_form);
284 $form_state = new FormState();
285 $form = $this->formBuilder->buildForm($form_arg, $form_state);
286 $this->assertFormElement($expected_form, $form, 'test');
287 $this->assertSame($form_id, $form_state->getBuildInfo()['form_id']);
288 $this->assertArrayHasKey('#id', $form);
292 * Tests whether the triggering element is properly identified.
294 * @param string $element_value
295 * The input element "#value" value.
296 * @param string $input_value
297 * The corresponding submitted input value.
299 * @covers ::buildForm
301 * @dataProvider providerTestBuildFormWithTriggeringElement
303 public function testBuildFormWithTriggeringElement($element_value, $input_value) {
304 $form_id = 'test_form_id';
305 $expected_form = $form_id();
307 $expected_form['actions']['other_submit'] = [
309 '#value' => $element_value,
312 $form_arg = $this->getMockForm($form_id, $expected_form, 2);
313 $form_state = new FormState();
314 $form_state->setProcessInput();
315 $form_state->setUserInput(['form_id' => $form_id, 'op' => $input_value]);
316 $this->request->setMethod('POST');
317 $this->formBuilder->buildForm($form_arg, $form_state);
319 $this->assertEquals($expected_form['actions']['other_submit']['#value'], $form_state->getTriggeringElement()['#value']);
323 * Data provider for ::testBuildFormWithTriggeringElement().
325 public function providerTestBuildFormWithTriggeringElement() {
326 $plain_text = 'Other submit value';
327 $markup = 'Other submit <input> value';
329 'plain-text' => [$plain_text, $plain_text],
330 'markup' => [$markup, $markup],
331 // Note: The input is always decoded, see
332 // \Drupal\Core\Form\FormBuilder::buttonWasClicked, so we do not need to
334 'escaped-markup' => [Html::escape($markup), $markup],
339 * Tests the rebuildForm() method for a POST submission.
341 public function testRebuildForm() {
342 $form_id = 'test_form_id';
343 $expected_form = $form_id();
345 // The form will be built four times.
346 $form_arg = $this->getMock('Drupal\Core\Form\FormInterface');
347 $form_arg->expects($this->exactly(2))
348 ->method('getFormId')
349 ->will($this->returnValue($form_id));
350 $form_arg->expects($this->exactly(4))
351 ->method('buildForm')
352 ->will($this->returnValue($expected_form));
354 // Do an initial build of the form and track the build ID.
355 $form_state = new FormState();
356 $form = $this->formBuilder->buildForm($form_arg, $form_state);
357 $original_build_id = $form['#build_id'];
359 $this->request->setMethod('POST');
360 $form_state->setRequestMethod('POST');
362 // Rebuild the form, and assert that the build ID has not changed.
363 $form_state->setRebuild();
364 $input['form_id'] = $form_id;
365 $form_state->setUserInput($input);
366 $form_state->addRebuildInfo('copy', ['#build_id' => TRUE]);
367 $this->formBuilder->processForm($form_id, $form, $form_state);
368 $this->assertSame($original_build_id, $form['#build_id']);
369 $this->assertTrue($form_state->isCached());
371 // Rebuild the form again, and assert that there is a new build ID.
372 $form_state->setRebuildInfo([]);
373 $form = $this->formBuilder->buildForm($form_arg, $form_state);
374 $this->assertNotSame($original_build_id, $form['#build_id']);
375 $this->assertTrue($form_state->isCached());
379 * Tests the rebuildForm() method for a GET submission.
381 public function testRebuildFormOnGetRequest() {
382 $form_id = 'test_form_id';
383 $expected_form = $form_id();
385 // The form will be built four times.
386 $form_arg = $this->getMock('Drupal\Core\Form\FormInterface');
387 $form_arg->expects($this->exactly(2))
388 ->method('getFormId')
389 ->will($this->returnValue($form_id));
390 $form_arg->expects($this->exactly(4))
391 ->method('buildForm')
392 ->will($this->returnValue($expected_form));
394 // Do an initial build of the form and track the build ID.
395 $form_state = new FormState();
396 $form_state->setMethod('GET');
397 $form = $this->formBuilder->buildForm($form_arg, $form_state);
398 $original_build_id = $form['#build_id'];
400 // Rebuild the form, and assert that the build ID has not changed.
401 $form_state->setRebuild();
402 $input['form_id'] = $form_id;
403 $form_state->setUserInput($input);
404 $form_state->addRebuildInfo('copy', ['#build_id' => TRUE]);
405 $this->formBuilder->processForm($form_id, $form, $form_state);
406 $this->assertSame($original_build_id, $form['#build_id']);
407 $this->assertFalse($form_state->isCached());
409 // Rebuild the form again, and assert that there is a new build ID.
410 $form_state->setRebuildInfo([]);
411 $form = $this->formBuilder->buildForm($form_arg, $form_state);
412 $this->assertNotSame($original_build_id, $form['#build_id']);
413 $this->assertFalse($form_state->isCached());
417 * Tests the getCache() method.
419 public function testGetCache() {
420 $form_id = 'test_form_id';
421 $expected_form = $form_id();
422 $expected_form['#token'] = FALSE;
424 // FormBuilder::buildForm() will be called twice, but the form object will
425 // only be called once due to caching.
426 $form_arg = $this->getMock('Drupal\Core\Form\FormInterface');
427 $form_arg->expects($this->exactly(2))
428 ->method('getFormId')
429 ->will($this->returnValue($form_id));
430 $form_arg->expects($this->once())
431 ->method('buildForm')
432 ->will($this->returnValue($expected_form));
434 // Do an initial build of the form and track the build ID.
435 $form_state = (new FormState())
436 ->addBuildInfo('files', [['module' => 'node', 'type' => 'pages.inc']])
437 ->setRequestMethod('POST')
439 $form = $this->formBuilder->buildForm($form_arg, $form_state);
441 $cached_form = $form;
442 $cached_form['#cache_token'] = 'csrf_token';
443 // The form cache, form_state cache, and CSRF token validation will only be
444 // called on the cached form.
445 $this->formCache->expects($this->once())
449 // The final form build will not trigger any actual form building, but will
450 // use the form cache.
451 $form_state->setExecuted();
452 $input['form_id'] = $form_id;
453 $input['form_build_id'] = $form['#build_id'];
454 $form_state->setUserInput($input);
455 $this->formBuilder->buildForm($form_arg, $form_state);
456 $this->assertEmpty($form_state->getErrors());
460 * Tests that HTML IDs are unique when rebuilding a form with errors.
462 public function testUniqueHtmlId() {
463 $form_id = 'test_form_id';
464 $expected_form = $form_id();
465 $expected_form['test']['#required'] = TRUE;
467 // Mock a form object that will be built two times.
468 $form_arg = $this->getMock('Drupal\Core\Form\FormInterface');
469 $form_arg->expects($this->exactly(2))
470 ->method('getFormId')
471 ->will($this->returnValue($form_id));
472 $form_arg->expects($this->exactly(2))
473 ->method('buildForm')
474 ->will($this->returnValue($expected_form));
476 $form_state = new FormState();
477 $form = $this->simulateFormSubmission($form_id, $form_arg, $form_state);
478 $this->assertSame('test-form-id', $form['#id']);
480 $form_state = new FormState();
481 $form = $this->simulateFormSubmission($form_id, $form_arg, $form_state);
482 $this->assertSame('test-form-id--2', $form['#id']);
486 * Tests that a cached form is deleted after submit.
488 public function testFormCacheDeletionCached() {
489 $form_id = 'test_form_id';
490 $form_build_id = $this->randomMachineName();
492 $expected_form = $form_id();
493 $expected_form['#build_id'] = $form_build_id;
494 $form_arg = $this->getMockForm($form_id, $expected_form);
495 $form_arg->expects($this->once())
496 ->method('submitForm')
497 ->willReturnCallback(function (array &$form, FormStateInterface $form_state) {
498 // Mimic EntityForm by cleaning the $form_state upon submit.
499 $form_state->cleanValues();
502 $this->formCache->expects($this->once())
503 ->method('deleteCache')
504 ->with($form_build_id);
506 $form_state = new FormState();
507 $form_state->setRequestMethod('POST');
508 $form_state->setCached();
509 $this->simulateFormSubmission($form_id, $form_arg, $form_state);
513 * Tests that an uncached form does not trigger cache set or delete.
515 public function testFormCacheDeletionUncached() {
516 $form_id = 'test_form_id';
517 $form_build_id = $this->randomMachineName();
519 $expected_form = $form_id();
520 $expected_form['#build_id'] = $form_build_id;
521 $form_arg = $this->getMockForm($form_id, $expected_form);
523 $this->formCache->expects($this->never())
524 ->method('deleteCache');
526 $form_state = new FormState();
527 $this->simulateFormSubmission($form_id, $form_arg, $form_state);
531 * @covers ::buildForm
533 public function testExceededFileSize() {
534 $request = new Request([FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]);
535 $request_stack = new RequestStack();
536 $request_stack->push($request);
537 $this->formBuilder = $this->getMockBuilder('\Drupal\Core\Form\FormBuilder')
538 ->setConstructorArgs([$this->formValidator, $this->formSubmitter, $this->formCache, $this->moduleHandler, $this->eventDispatcher, $request_stack, $this->classResolver, $this->elementInfo, $this->themeManager, $this->csrfToken])
539 ->setMethods(['getFileUploadMaxSize'])
541 $this->formBuilder->expects($this->once())
542 ->method('getFileUploadMaxSize')
543 ->willReturn(33554432);
545 $form_arg = $this->getMockForm('test_form_id');
546 $form_state = new FormState();
548 $this->setExpectedException(BrokenPostRequestException::class);
549 $this->formBuilder->buildForm($form_arg, $form_state);
553 * @covers ::buildForm
555 public function testGetPostAjaxRequest() {
556 $request = new Request([FormBuilderInterface::AJAX_FORM_REQUEST => TRUE], ['form_id' => 'different_form_id']);
557 $request->setMethod('POST');
558 $this->requestStack->push($request);
560 $form_state = (new FormState())
561 ->setUserInput([FormBuilderInterface::AJAX_FORM_REQUEST => TRUE])
567 $form_id = '\Drupal\Tests\Core\Form\TestForm';
568 $expected_form = (new TestForm())->buildForm([], $form_state);
570 $form = $this->formBuilder->buildForm($form_id, $form_state);
571 $this->assertFormElement($expected_form, $form, 'test');
572 $this->assertSame('test-form', $form['#id']);
576 * @covers ::buildForm
578 * @dataProvider providerTestChildAccessInheritance
580 public function testChildAccessInheritance($element, $access_checks) {
581 $form_arg = new TestFormWithPredefinedForm();
582 $form_arg->setForm($element);
584 $form_state = new FormState();
586 $form = $this->formBuilder->buildForm($form_arg, $form_state);
588 $actual_access_structure = [];
589 $expected_access_structure = [];
591 // Ensure that the expected access checks are set.
592 foreach ($access_checks as $access_check) {
593 $parents = $access_check[0];
594 $parents[] = '#access';
596 $actual_access = NestedArray::getValue($form, $parents);
597 $actual_access_structure[] = [$parents, $actual_access];
598 $expected_access_structure[] = [$parents, $access_check[1]];
601 $this->assertEquals($expected_access_structure, $actual_access_structure);
605 * Data provider for testChildAccessInheritance.
609 public function providerTestChildAccessInheritance() {
614 '#type' => 'checkbox',
617 '#type' => 'checkbox',
620 '#type' => 'fieldset',
622 '#type' => 'checkbox',
625 '#type' => 'checkbox',
628 '#type' => 'checkbox',
633 // Sets access FALSE on the root level, this should be inherited completely.
635 $clone['#access'] = FALSE;
637 $expected_access = [];
638 $expected_access[] = [[], FALSE];
639 $expected_access[] = [['child0'], FALSE];
640 $expected_access[] = [['child1'], FALSE];
641 $expected_access[] = [['child2'], FALSE];
642 $expected_access[] = [['child2', 'child2.0'], FALSE];
643 $expected_access[] = [['child2', 'child2.1'], FALSE];
644 $expected_access[] = [['child2', 'child2.2'], FALSE];
646 $data['access-false-root'] = [$clone, $expected_access];
649 $access_result = AccessResult::forbidden();
650 $clone['#access'] = $access_result;
652 $expected_access = [];
653 $expected_access[] = [[], $access_result];
654 $expected_access[] = [['child0'], $access_result];
655 $expected_access[] = [['child1'], $access_result];
656 $expected_access[] = [['child2'], $access_result];
657 $expected_access[] = [['child2', 'child2.0'], $access_result];
658 $expected_access[] = [['child2', 'child2.1'], $access_result];
659 $expected_access[] = [['child2', 'child2.2'], $access_result];
661 $data['access-forbidden-root'] = [$clone, $expected_access];
663 // Allow access on the most outer level but set FALSE otherwise.
665 $clone['#access'] = TRUE;
666 $clone['child0']['#access'] = FALSE;
668 $expected_access = [];
669 $expected_access[] = [[], TRUE];
670 $expected_access[] = [['child0'], FALSE];
671 $expected_access[] = [['child1'], NULL];
672 $expected_access[] = [['child2'], NULL];
673 $expected_access[] = [['child2', 'child2.0'], NULL];
674 $expected_access[] = [['child2', 'child2.1'], NULL];
675 $expected_access[] = [['child2', 'child2.2'], NULL];
677 $data['access-true-root'] = [$clone, $expected_access];
679 // Allow access on the most outer level but forbid otherwise.
681 $access_result_allowed = AccessResult::allowed();
682 $clone['#access'] = $access_result_allowed;
683 $access_result_forbidden = AccessResult::forbidden();
684 $clone['child0']['#access'] = $access_result_forbidden;
686 $expected_access = [];
687 $expected_access[] = [[], $access_result_allowed];
688 $expected_access[] = [['child0'], $access_result_forbidden];
689 $expected_access[] = [['child1'], NULL];
690 $expected_access[] = [['child2'], NULL];
691 $expected_access[] = [['child2', 'child2.0'], NULL];
692 $expected_access[] = [['child2', 'child2.1'], NULL];
693 $expected_access[] = [['child2', 'child2.2'], NULL];
695 $data['access-allowed-root'] = [$clone, $expected_access];
697 // Allow access on the most outer level, deny access on a parent, and allow
698 // on a child. The denying should be inherited.
700 $clone['#access'] = TRUE;
701 $clone['child2']['#access'] = FALSE;
702 $clone['child2.0']['#access'] = TRUE;
703 $clone['child2.1']['#access'] = TRUE;
704 $clone['child2.2']['#access'] = TRUE;
706 $expected_access = [];
707 $expected_access[] = [[], TRUE];
708 $expected_access[] = [['child0'], NULL];
709 $expected_access[] = [['child1'], NULL];
710 $expected_access[] = [['child2'], FALSE];
711 $expected_access[] = [['child2', 'child2.0'], FALSE];
712 $expected_access[] = [['child2', 'child2.1'], FALSE];
713 $expected_access[] = [['child2', 'child2.2'], FALSE];
715 $data['access-mixed-parents'] = [$clone, $expected_access];
718 $clone['#access'] = $access_result_allowed;
719 $clone['child2']['#access'] = $access_result_forbidden;
720 $clone['child2.0']['#access'] = $access_result_allowed;
721 $clone['child2.1']['#access'] = $access_result_allowed;
722 $clone['child2.2']['#access'] = $access_result_allowed;
724 $expected_access = [];
725 $expected_access[] = [[], $access_result_allowed];
726 $expected_access[] = [['child0'], NULL];
727 $expected_access[] = [['child1'], NULL];
728 $expected_access[] = [['child2'], $access_result_forbidden];
729 $expected_access[] = [['child2', 'child2.0'], $access_result_forbidden];
730 $expected_access[] = [['child2', 'child2.1'], $access_result_forbidden];
731 $expected_access[] = [['child2', 'child2.2'], $access_result_forbidden];
733 $data['access-mixed-parents-object'] = [$clone, $expected_access];
739 * @covers ::valueCallableIsSafe
741 * @dataProvider providerTestValueCallableIsSafe
743 public function testValueCallableIsSafe($callback, $expected) {
744 $method = new \ReflectionMethod(FormBuilder::class, 'valueCallableIsSafe');
745 $method->setAccessible(TRUE);
746 $is_safe = $method->invoke($this->formBuilder, $callback);
747 $this->assertSame($expected, $is_safe);
750 public function providerTestValueCallableIsSafe() {
752 $data['string_no_slash'] = [
753 'Drupal\Core\Render\Element\Token::valueCallback',
756 $data['string_with_slash'] = [
757 '\Drupal\Core\Render\Element\Token::valueCallback',
760 $data['array_no_slash'] = [
761 ['Drupal\Core\Render\Element\Token', 'valueCallback'],
764 $data['array_with_slash'] = [
765 ['\Drupal\Core\Render\Element\Token', 'valueCallback'],
776 * @covers ::doBuildForm
778 * @dataProvider providerTestInvalidToken
780 public function testInvalidToken($expected, $valid_token, $user_is_authenticated) {
781 $form_token = 'the_form_token';
782 $form_id = 'test_form_id';
784 if (is_bool($valid_token)) {
785 $this->csrfToken->expects($this->any())
787 ->willReturnArgument(0);
788 $this->csrfToken->expects($this->atLeastOnce())
790 ->willReturn($valid_token);
793 $current_user = $this->prophesize(AccountInterface::class);
794 $current_user->isAuthenticated()->willReturn($user_is_authenticated);
795 $property = new \ReflectionProperty(FormBuilder::class, 'currentUser');
796 $property->setAccessible(TRUE);
797 $property->setValue($this->formBuilder, $current_user->reveal());
799 $expected_form = $form_id();
800 $form_arg = $this->getMockForm($form_id, $expected_form);
802 $form_state = new FormState();
803 $input['form_id'] = $form_id;
804 $input['form_token'] = $form_token;
805 $form_state->setUserInput($input);
806 $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
807 $this->assertSame($expected, $form_state->hasInvalidToken());
810 public function providerTestInvalidToken() {
812 $data['authenticated_invalid'] = [TRUE, FALSE, TRUE];
813 $data['authenticated_valid'] = [FALSE, TRUE, TRUE];
814 // If the user is not authenticated, we will not have a token.
815 $data['anonymous'] = [FALSE, NULL, FALSE];
820 * @covers ::prepareForm
822 * @dataProvider providerTestFormTokenCacheability
824 public function testFormTokenCacheability($token, $is_authenticated, $expected_form_cacheability, $expected_token_cacheability, $method) {
825 $user = $this->prophesize(AccountProxyInterface::class);
826 $user->isAuthenticated()
827 ->willReturn($is_authenticated);
828 $this->container->set('current_user', $user->reveal());
829 \Drupal::setContainer($this->container);
831 $form_id = 'test_form_id';
833 $form['#method'] = $method;
836 $form['#token'] = $token;
839 $form_arg = $this->getMock('Drupal\Core\Form\FormInterface');
840 $form_arg->expects($this->once())
841 ->method('getFormId')
842 ->will($this->returnValue($form_id));
843 $form_arg->expects($this->once())
844 ->method('buildForm')
845 ->will($this->returnValue($form));
847 $form_state = new FormState();
848 $built_form = $this->formBuilder->buildForm($form_arg, $form_state);
849 if (!isset($expected_form_cacheability) || ($method == 'get' && !is_string($token))) {
850 $this->assertFalse(isset($built_form['#cache']));
853 $this->assertTrue(isset($built_form['#cache']));
854 $this->assertEquals($expected_form_cacheability, $built_form['#cache']);
856 if (!isset($expected_token_cacheability)) {
857 $this->assertFalse(isset($built_form['form_token']));
860 $this->assertTrue(isset($built_form['form_token']));
861 $this->assertEquals($expected_token_cacheability, $built_form['form_token']['#cache']);
866 * Data provider for testFormTokenCacheability.
870 public function providerTestFormTokenCacheability() {
872 'token:none,authenticated:true' => [NULL, TRUE, ['contexts' => ['user.roles:authenticated']], ['max-age' => 0], 'post'],
873 'token:none,authenticated:false' => [NULL, FALSE, ['contexts' => ['user.roles:authenticated']], NULL, 'post'],
874 'token:false,authenticated:false' => [FALSE, FALSE, NULL, NULL, 'post'],
875 'token:false,authenticated:true' => [FALSE, TRUE, NULL, NULL, 'post'],
876 'token:none,authenticated:false,method:get' => [NULL, FALSE, ['contexts' => ['user.roles:authenticated']], NULL, 'get'],
877 'token:test_form_id,authenticated:false,method:get' => ['test_form_id', TRUE, ['contexts' => ['user.roles:authenticated']], ['max-age' => 0], 'get'],
883 class TestForm implements FormInterface {
885 public function getFormId() {
889 public function buildForm(array $form, FormStateInterface $form_state) {
890 return test_form_id();
893 public function validateForm(array &$form, FormStateInterface $form_state) {}
895 public function submitForm(array &$form, FormStateInterface $form_state) {}
898 class TestFormInjected extends TestForm implements ContainerInjectionInterface {
900 public static function create(ContainerInterface $container) {
907 class TestFormWithPredefinedForm extends TestForm {
914 public function setForm($form) {
918 public function buildForm(array $form, FormStateInterface $form_state) {