class FormBuilderTest

Same name in other branches
  1. 9 core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php \Drupal\Tests\Core\Form\FormBuilderTest
  2. 10 core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php \Drupal\Tests\Core\Form\FormBuilderTest
  3. 11.x core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php \Drupal\Tests\Core\Form\FormBuilderTest

@coversDefaultClass \Drupal\Core\Form\FormBuilder @group Form

Hierarchy

Expanded class hierarchy of FormBuilderTest

File

core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php, line 33

Namespace

Drupal\Tests\Core\Form
View source
class FormBuilderTest extends FormTestBase {
    
    /**
     * The dependency injection container.
     *
     * @var \Symfony\Component\DependencyInjection\ContainerBuilder
     */
    protected $container;
    
    /**
     * {@inheritdoc}
     */
    protected function setUp() {
        parent::setUp();
        $this->container = new ContainerBuilder();
        $cache_contexts_manager = $this->prophesize(CacheContextsManager::class)
            ->reveal();
        $this->container
            ->set('cache_contexts_manager', $cache_contexts_manager);
        \Drupal::setContainer($this->container);
    }
    
    /**
     * Tests the getFormId() method with a string based form ID.
     */
    public function testGetFormIdWithString() {
        $form_arg = 'foo';
        $form_state = new FormState();
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionMessage('The form argument foo is not a valid form.');
        $this->formBuilder
            ->getFormId($form_arg, $form_state);
    }
    
    /**
     * Tests the getFormId() method with a class name form ID.
     */
    public function testGetFormIdWithClassName() {
        $form_arg = 'Drupal\\Tests\\Core\\Form\\TestForm';
        $form_state = new FormState();
        $form_id = $this->formBuilder
            ->getFormId($form_arg, $form_state);
        $this->assertSame('test_form', $form_id);
        $this->assertSame($form_arg, get_class($form_state->getFormObject()));
    }
    
    /**
     * Tests the getFormId() method with an injected class name form ID.
     */
    public function testGetFormIdWithInjectedClassName() {
        $container = $this->createMock('Symfony\\Component\\DependencyInjection\\ContainerInterface');
        \Drupal::setContainer($container);
        $form_arg = 'Drupal\\Tests\\Core\\Form\\TestFormInjected';
        $form_state = new FormState();
        $form_id = $this->formBuilder
            ->getFormId($form_arg, $form_state);
        $this->assertSame('test_form', $form_id);
        $this->assertSame($form_arg, get_class($form_state->getFormObject()));
    }
    
    /**
     * Tests the getFormId() method with a form object.
     */
    public function testGetFormIdWithObject() {
        $expected_form_id = 'my_module_form_id';
        $form_arg = $this->getMockForm($expected_form_id);
        $form_state = new FormState();
        $form_id = $this->formBuilder
            ->getFormId($form_arg, $form_state);
        $this->assertSame($expected_form_id, $form_id);
        $this->assertSame($form_arg, $form_state->getFormObject());
    }
    
    /**
     * Tests the getFormId() method with a base form object.
     */
    public function testGetFormIdWithBaseForm() {
        $expected_form_id = 'my_module_form_id';
        $base_form_id = 'my_module';
        $form_arg = $this->createMock('Drupal\\Core\\Form\\BaseFormIdInterface');
        $form_arg->expects($this->once())
            ->method('getFormId')
            ->will($this->returnValue($expected_form_id));
        $form_arg->expects($this->once())
            ->method('getBaseFormId')
            ->will($this->returnValue($base_form_id));
        $form_state = new FormState();
        $form_id = $this->formBuilder
            ->getFormId($form_arg, $form_state);
        $this->assertSame($expected_form_id, $form_id);
        $this->assertSame($form_arg, $form_state->getFormObject());
        $this->assertSame($base_form_id, $form_state->getBuildInfo()['base_form_id']);
    }
    
    /**
     * Tests the handling of FormStateInterface::$response.
     *
     * @dataProvider formStateResponseProvider
     */
    public function testHandleFormStateResponse($class, $form_state_key) {
        $form_id = 'test_form_id';
        $expected_form = $form_id();
        $response = $this->getMockBuilder($class)
            ->disableOriginalConstructor()
            ->getMock();
        $form_arg = $this->getMockForm($form_id, $expected_form);
        $form_arg->expects($this->any())
            ->method('submitForm')
            ->will($this->returnCallback(function ($form, FormStateInterface $form_state) use ($response, $form_state_key) {
            $form_state->setFormState([
                $form_state_key => $response,
            ]);
        }));
        $form_state = new FormState();
        try {
            $input['form_id'] = $form_id;
            $form_state->setUserInput($input);
            $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
            $this->fail('EnforcedResponseException was not thrown.');
        } catch (EnforcedResponseException $e) {
            $this->assertSame($response, $e->getResponse());
        }
        $this->assertSame($response, $form_state->getResponse());
    }
    
    /**
     * Provides test data for testHandleFormStateResponse().
     */
    public function formStateResponseProvider() {
        return [
            [
                'Symfony\\Component\\HttpFoundation\\Response',
                'response',
            ],
            [
                'Symfony\\Component\\HttpFoundation\\RedirectResponse',
                'redirect',
            ],
        ];
    }
    
    /**
     * Tests the handling of a redirect when FormStateInterface::$response exists.
     */
    public function testHandleRedirectWithResponse() {
        $form_id = 'test_form_id';
        $expected_form = $form_id();
        // Set up a response that will be used.
        $response = $this->getMockBuilder('Symfony\\Component\\HttpFoundation\\Response')
            ->disableOriginalConstructor()
            ->getMock();
        // Set up a redirect that will not be called.
        $redirect = $this->getMockBuilder('Symfony\\Component\\HttpFoundation\\RedirectResponse')
            ->disableOriginalConstructor()
            ->getMock();
        $form_arg = $this->getMockForm($form_id, $expected_form);
        $form_arg->expects($this->any())
            ->method('submitForm')
            ->will($this->returnCallback(function ($form, FormStateInterface $form_state) use ($response, $redirect) {
            // Set both the response and the redirect.
            $form_state->setResponse($response);
            $form_state->set('redirect', $redirect);
        }));
        $form_state = new FormState();
        try {
            $input['form_id'] = $form_id;
            $form_state->setUserInput($input);
            $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
            $this->fail('EnforcedResponseException was not thrown.');
        } catch (EnforcedResponseException $e) {
            $this->assertSame($response, $e->getResponse());
        }
        $this->assertSame($response, $form_state->getResponse());
    }
    
    /**
     * Tests the getForm() method with a string based form ID.
     */
    public function testGetFormWithString() {
        $form_id = 'test_form_id';
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionMessage('The form argument test_form_id is not a valid form.');
        $this->formBuilder
            ->getForm($form_id);
    }
    
    /**
     * Tests the getForm() method with a form object.
     */
    public function testGetFormWithObject() {
        $form_id = 'test_form_id';
        $expected_form = $form_id();
        $form_arg = $this->getMockForm($form_id, $expected_form);
        $form = $this->formBuilder
            ->getForm($form_arg);
        $this->assertFormElement($expected_form, $form, 'test');
        $this->assertArrayHasKey('#id', $form);
    }
    
    /**
     * Tests the getForm() method with a class name based form ID.
     */
    public function testGetFormWithClassString() {
        $form_id = '\\Drupal\\Tests\\Core\\Form\\TestForm';
        $object = new TestForm();
        $form = [];
        $form_state = new FormState();
        $expected_form = $object->buildForm($form, $form_state);
        $form = $this->formBuilder
            ->getForm($form_id);
        $this->assertFormElement($expected_form, $form, 'test');
        $this->assertSame('test-form', $form['#id']);
    }
    
    /**
     * Tests the buildForm() method with a string based form ID.
     */
    public function testBuildFormWithString() {
        $form_id = 'test_form_id';
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionMessage('The form argument test_form_id is not a valid form.');
        $this->formBuilder
            ->getForm($form_id);
    }
    
    /**
     * Tests the buildForm() method with a class name based form ID.
     */
    public function testBuildFormWithClassString() {
        $form_id = '\\Drupal\\Tests\\Core\\Form\\TestForm';
        $object = new TestForm();
        $form = [];
        $form_state = new FormState();
        $expected_form = $object->buildForm($form, $form_state);
        $form = $this->formBuilder
            ->buildForm($form_id, $form_state);
        $this->assertFormElement($expected_form, $form, 'test');
        $this->assertSame('test-form', $form['#id']);
    }
    
    /**
     * Tests the buildForm() method with a form object.
     */
    public function testBuildFormWithObject() {
        $form_id = 'test_form_id';
        $expected_form = $form_id();
        $form_arg = $this->getMockForm($form_id, $expected_form);
        $form_state = new FormState();
        $form = $this->formBuilder
            ->buildForm($form_arg, $form_state);
        $this->assertFormElement($expected_form, $form, 'test');
        $this->assertSame($form_id, $form_state->getBuildInfo()['form_id']);
        $this->assertArrayHasKey('#id', $form);
    }
    
    /**
     * Tests whether the triggering element is properly identified.
     *
     * @param string $element_value
     *   The input element "#value" value.
     * @param string $input_value
     *   The corresponding submitted input value.
     *
     * @covers ::buildForm
     *
     * @dataProvider providerTestBuildFormWithTriggeringElement
     */
    public function testBuildFormWithTriggeringElement($element_value, $input_value) {
        $form_id = 'test_form_id';
        $expected_form = $form_id();
        $expected_form['actions']['other_submit'] = [
            '#type' => 'submit',
            '#value' => $element_value,
        ];
        $form_arg = $this->getMockForm($form_id, $expected_form, 2);
        $form_state = new FormState();
        $form_state->setProcessInput();
        $form_state->setUserInput([
            'form_id' => $form_id,
            'op' => $input_value,
        ]);
        $this->request
            ->setMethod('POST');
        $this->formBuilder
            ->buildForm($form_arg, $form_state);
        $this->assertEquals($expected_form['actions']['other_submit']['#value'], $form_state->getTriggeringElement()['#value']);
    }
    
    /**
     * Data provider for ::testBuildFormWithTriggeringElement().
     */
    public function providerTestBuildFormWithTriggeringElement() {
        $plain_text = 'Other submit value';
        $markup = 'Other submit <input> value';
        return [
            'plain-text' => [
                $plain_text,
                $plain_text,
            ],
            'markup' => [
                $markup,
                $markup,
            ],
            // Note: The input is always decoded, see
            // \Drupal\Core\Form\FormBuilder::buttonWasClicked, so we do not need to
            // escape the input.
'escaped-markup' => [
                Html::escape($markup),
                $markup,
            ],
        ];
    }
    
    /**
     * Tests the rebuildForm() method for a POST submission.
     */
    public function testRebuildForm() {
        $form_id = 'test_form_id';
        $expected_form = $form_id();
        // The form will be built four times.
        $form_arg = $this->createMock('Drupal\\Core\\Form\\FormInterface');
        $form_arg->expects($this->exactly(2))
            ->method('getFormId')
            ->will($this->returnValue($form_id));
        $form_arg->expects($this->exactly(4))
            ->method('buildForm')
            ->will($this->returnValue($expected_form));
        // Do an initial build of the form and track the build ID.
        $form_state = new FormState();
        $form = $this->formBuilder
            ->buildForm($form_arg, $form_state);
        $original_build_id = $form['#build_id'];
        $this->request
            ->setMethod('POST');
        $form_state->setRequestMethod('POST');
        // Rebuild the form, and assert that the build ID has not changed.
        $form_state->setRebuild();
        $input['form_id'] = $form_id;
        $form_state->setUserInput($input);
        $form_state->addRebuildInfo('copy', [
            '#build_id' => TRUE,
        ]);
        $this->formBuilder
            ->processForm($form_id, $form, $form_state);
        $this->assertSame($original_build_id, $form['#build_id']);
        $this->assertTrue($form_state->isCached());
        // Rebuild the form again, and assert that there is a new build ID.
        $form_state->setRebuildInfo([]);
        $form = $this->formBuilder
            ->buildForm($form_arg, $form_state);
        $this->assertNotSame($original_build_id, $form['#build_id']);
        $this->assertTrue($form_state->isCached());
    }
    
    /**
     * Tests the rebuildForm() method for a GET submission.
     */
    public function testRebuildFormOnGetRequest() {
        $form_id = 'test_form_id';
        $expected_form = $form_id();
        // The form will be built four times.
        $form_arg = $this->createMock('Drupal\\Core\\Form\\FormInterface');
        $form_arg->expects($this->exactly(2))
            ->method('getFormId')
            ->will($this->returnValue($form_id));
        $form_arg->expects($this->exactly(4))
            ->method('buildForm')
            ->will($this->returnValue($expected_form));
        // Do an initial build of the form and track the build ID.
        $form_state = new FormState();
        $form_state->setMethod('GET');
        $form = $this->formBuilder
            ->buildForm($form_arg, $form_state);
        $original_build_id = $form['#build_id'];
        // Rebuild the form, and assert that the build ID has not changed.
        $form_state->setRebuild();
        $input['form_id'] = $form_id;
        $form_state->setUserInput($input);
        $form_state->addRebuildInfo('copy', [
            '#build_id' => TRUE,
        ]);
        $this->formBuilder
            ->processForm($form_id, $form, $form_state);
        $this->assertSame($original_build_id, $form['#build_id']);
        $this->assertFalse($form_state->isCached());
        // Rebuild the form again, and assert that there is a new build ID.
        $form_state->setRebuildInfo([]);
        $form = $this->formBuilder
            ->buildForm($form_arg, $form_state);
        $this->assertNotSame($original_build_id, $form['#build_id']);
        $this->assertFalse($form_state->isCached());
    }
    
    /**
     * Tests the getCache() method.
     */
    public function testGetCache() {
        $form_id = 'test_form_id';
        $expected_form = $form_id();
        $expected_form['#token'] = FALSE;
        // FormBuilder::buildForm() will be called twice, but the form object will
        // only be called once due to caching.
        $form_arg = $this->createMock('Drupal\\Core\\Form\\FormInterface');
        $form_arg->expects($this->exactly(2))
            ->method('getFormId')
            ->will($this->returnValue($form_id));
        $form_arg->expects($this->once())
            ->method('buildForm')
            ->will($this->returnValue($expected_form));
        // Do an initial build of the form and track the build ID.
        $form_state = (new FormState())->addBuildInfo('files', [
            [
                'module' => 'node',
                'type' => 'pages.inc',
            ],
        ])
            ->setRequestMethod('POST')
            ->setCached();
        $form = $this->formBuilder
            ->buildForm($form_arg, $form_state);
        $cached_form = $form;
        $cached_form['#cache_token'] = 'csrf_token';
        // The form cache, form_state cache, and CSRF token validation will only be
        // called on the cached form.
        $this->formCache
            ->expects($this->once())
            ->method('getCache')
            ->willReturn($form);
        // The final form build will not trigger any actual form building, but will
        // use the form cache.
        $form_state->setExecuted();
        $input['form_id'] = $form_id;
        $input['form_build_id'] = $form['#build_id'];
        $form_state->setUserInput($input);
        $this->formBuilder
            ->buildForm($form_arg, $form_state);
        $this->assertEmpty($form_state->getErrors());
    }
    
    /**
     * Tests that HTML IDs are unique when rebuilding a form with errors.
     */
    public function testUniqueHtmlId() {
        $form_id = 'test_form_id';
        $expected_form = $form_id();
        $expected_form['test']['#required'] = TRUE;
        // Mock a form object that will be built two times.
        $form_arg = $this->createMock('Drupal\\Core\\Form\\FormInterface');
        $form_arg->expects($this->exactly(2))
            ->method('getFormId')
            ->will($this->returnValue($form_id));
        $form_arg->expects($this->exactly(2))
            ->method('buildForm')
            ->will($this->returnValue($expected_form));
        $form_state = new FormState();
        $form = $this->simulateFormSubmission($form_id, $form_arg, $form_state);
        $this->assertSame('test-form-id', $form['#id']);
        $form_state = new FormState();
        $form = $this->simulateFormSubmission($form_id, $form_arg, $form_state);
        $this->assertSame('test-form-id--2', $form['#id']);
    }
    
    /**
     * Tests that HTML IDs are unique between 2 forms with the same element names.
     */
    public function testUniqueElementHtmlId() {
        $form_id_1 = 'test_form_id';
        $form_id_2 = 'test_form_id_2';
        $expected_form = $form_id_1();
        // Mock 2 form objects that will be built once each.
        $form_arg_1 = $this->createMock('Drupal\\Core\\Form\\FormInterface');
        $form_arg_1->expects($this->exactly(1))
            ->method('getFormId')
            ->will($this->returnValue($form_id_1));
        $form_arg_1->expects($this->exactly(1))
            ->method('buildForm')
            ->will($this->returnValue($expected_form));
        $form_arg_2 = $this->createMock('Drupal\\Core\\Form\\FormInterface');
        $form_arg_2->expects($this->exactly(1))
            ->method('getFormId')
            ->will($this->returnValue($form_id_2));
        $form_arg_2->expects($this->exactly(1))
            ->method('buildForm')
            ->will($this->returnValue($expected_form));
        $form_state = new FormState();
        $form_1 = $this->simulateFormSubmission($form_id_1, $form_arg_1, $form_state);
        $form_state = new FormState();
        $form_2 = $this->simulateFormSubmission($form_id_2, $form_arg_2, $form_state);
        $this->assertNotSame($form_1['actions']["#id"], $form_2['actions']["#id"]);
    }
    
    /**
     * Tests that a cached form is deleted after submit.
     */
    public function testFormCacheDeletionCached() {
        $form_id = 'test_form_id';
        $form_build_id = $this->randomMachineName();
        $expected_form = $form_id();
        $expected_form['#build_id'] = $form_build_id;
        $form_arg = $this->getMockForm($form_id, $expected_form);
        $form_arg->expects($this->once())
            ->method('submitForm')
            ->willReturnCallback(function (array &$form, FormStateInterface $form_state) {
            // Mimic EntityForm by cleaning the $form_state upon submit.
            $form_state->cleanValues();
        });
        $this->formCache
            ->expects($this->once())
            ->method('deleteCache')
            ->with($form_build_id);
        $form_state = new FormState();
        $form_state->setRequestMethod('POST');
        $form_state->setCached();
        $this->simulateFormSubmission($form_id, $form_arg, $form_state);
    }
    
    /**
     * Tests that an uncached form does not trigger cache set or delete.
     */
    public function testFormCacheDeletionUncached() {
        $form_id = 'test_form_id';
        $form_build_id = $this->randomMachineName();
        $expected_form = $form_id();
        $expected_form['#build_id'] = $form_build_id;
        $form_arg = $this->getMockForm($form_id, $expected_form);
        $this->formCache
            ->expects($this->never())
            ->method('deleteCache');
        $form_state = new FormState();
        $this->simulateFormSubmission($form_id, $form_arg, $form_state);
    }
    
    /**
     * @covers ::buildForm
     */
    public function testExceededFileSize() {
        $request = new Request([
            FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
        ]);
        $request_stack = new RequestStack();
        $request_stack->push($request);
        $this->formBuilder = $this->getMockBuilder('\\Drupal\\Core\\Form\\FormBuilder')
            ->setConstructorArgs([
            $this->formValidator,
            $this->formSubmitter,
            $this->formCache,
            $this->moduleHandler,
            $this->eventDispatcher,
            $request_stack,
            $this->classResolver,
            $this->elementInfo,
            $this->themeManager,
            $this->csrfToken,
        ])
            ->setMethods([
            'getFileUploadMaxSize',
        ])
            ->getMock();
        $this->formBuilder
            ->expects($this->once())
            ->method('getFileUploadMaxSize')
            ->willReturn(33554432);
        $form_arg = $this->getMockForm('test_form_id');
        $form_state = new FormState();
        $this->expectException(BrokenPostRequestException::class);
        $this->formBuilder
            ->buildForm($form_arg, $form_state);
    }
    
    /**
     * @covers ::buildForm
     */
    public function testGetPostAjaxRequest() {
        $request = new Request([
            FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
        ], [
            'form_id' => 'different_form_id',
        ]);
        $request->setMethod('POST');
        $this->requestStack
            ->push($request);
        $form_state = (new FormState())->setUserInput([
            FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
        ])
            ->setMethod('get')
            ->setAlwaysProcess()
            ->disableRedirect()
            ->set('ajax', TRUE);
        $form_id = '\\Drupal\\Tests\\Core\\Form\\TestForm';
        $expected_form = (new TestForm())->buildForm([], $form_state);
        $form = $this->formBuilder
            ->buildForm($form_id, $form_state);
        $this->assertFormElement($expected_form, $form, 'test');
        $this->assertSame('test-form', $form['#id']);
    }
    
    /**
     * @covers ::buildForm
     *
     * @dataProvider providerTestChildAccessInheritance
     */
    public function testChildAccessInheritance($element, $access_checks) {
        $form_arg = new TestFormWithPredefinedForm();
        $form_arg->setForm($element);
        $form_state = new FormState();
        $form = $this->formBuilder
            ->buildForm($form_arg, $form_state);
        $actual_access_structure = [];
        $expected_access_structure = [];
        // Ensure that the expected access checks are set.
        foreach ($access_checks as $access_check) {
            $parents = $access_check[0];
            $parents[] = '#access';
            $actual_access = NestedArray::getValue($form, $parents);
            $actual_access_structure[] = [
                $parents,
                $actual_access,
            ];
            $expected_access_structure[] = [
                $parents,
                $access_check[1],
            ];
        }
        $this->assertEquals($expected_access_structure, $actual_access_structure);
    }
    
    /**
     * Data provider for testChildAccessInheritance.
     *
     * @return array
     */
    public function providerTestChildAccessInheritance() {
        $data = [];
        $element = [
            'child0' => [
                '#type' => 'checkbox',
            ],
            'child1' => [
                '#type' => 'checkbox',
            ],
            'child2' => [
                '#type' => 'fieldset',
                'child2.0' => [
                    '#type' => 'checkbox',
                ],
                'child2.1' => [
                    '#type' => 'checkbox',
                ],
                'child2.2' => [
                    '#type' => 'checkbox',
                ],
            ],
        ];
        // Sets access FALSE on the root level, this should be inherited completely.
        $clone = $element;
        $clone['#access'] = FALSE;
        $expected_access = [];
        $expected_access[] = [
            [],
            FALSE,
        ];
        $expected_access[] = [
            [
                'child0',
            ],
            FALSE,
        ];
        $expected_access[] = [
            [
                'child1',
            ],
            FALSE,
        ];
        $expected_access[] = [
            [
                'child2',
            ],
            FALSE,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.0',
            ],
            FALSE,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.1',
            ],
            FALSE,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.2',
            ],
            FALSE,
        ];
        $data['access-false-root'] = [
            $clone,
            $expected_access,
        ];
        $clone = $element;
        $access_result = AccessResult::forbidden();
        $clone['#access'] = $access_result;
        $expected_access = [];
        $expected_access[] = [
            [],
            $access_result,
        ];
        $expected_access[] = [
            [
                'child0',
            ],
            $access_result,
        ];
        $expected_access[] = [
            [
                'child1',
            ],
            $access_result,
        ];
        $expected_access[] = [
            [
                'child2',
            ],
            $access_result,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.0',
            ],
            $access_result,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.1',
            ],
            $access_result,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.2',
            ],
            $access_result,
        ];
        $data['access-forbidden-root'] = [
            $clone,
            $expected_access,
        ];
        // Allow access on the most outer level but set FALSE otherwise.
        $clone = $element;
        $clone['#access'] = TRUE;
        $clone['child0']['#access'] = FALSE;
        $expected_access = [];
        $expected_access[] = [
            [],
            TRUE,
        ];
        $expected_access[] = [
            [
                'child0',
            ],
            FALSE,
        ];
        $expected_access[] = [
            [
                'child1',
            ],
            NULL,
        ];
        $expected_access[] = [
            [
                'child2',
            ],
            NULL,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.0',
            ],
            NULL,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.1',
            ],
            NULL,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.2',
            ],
            NULL,
        ];
        $data['access-true-root'] = [
            $clone,
            $expected_access,
        ];
        // Allow access on the most outer level but forbid otherwise.
        $clone = $element;
        $access_result_allowed = AccessResult::allowed();
        $clone['#access'] = $access_result_allowed;
        $access_result_forbidden = AccessResult::forbidden();
        $clone['child0']['#access'] = $access_result_forbidden;
        $expected_access = [];
        $expected_access[] = [
            [],
            $access_result_allowed,
        ];
        $expected_access[] = [
            [
                'child0',
            ],
            $access_result_forbidden,
        ];
        $expected_access[] = [
            [
                'child1',
            ],
            NULL,
        ];
        $expected_access[] = [
            [
                'child2',
            ],
            NULL,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.0',
            ],
            NULL,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.1',
            ],
            NULL,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.2',
            ],
            NULL,
        ];
        $data['access-allowed-root'] = [
            $clone,
            $expected_access,
        ];
        // Allow access on the most outer level, deny access on a parent, and allow
        // on a child. The denying should be inherited.
        $clone = $element;
        $clone['#access'] = TRUE;
        $clone['child2']['#access'] = FALSE;
        $clone['child2.0']['#access'] = TRUE;
        $clone['child2.1']['#access'] = TRUE;
        $clone['child2.2']['#access'] = TRUE;
        $expected_access = [];
        $expected_access[] = [
            [],
            TRUE,
        ];
        $expected_access[] = [
            [
                'child0',
            ],
            NULL,
        ];
        $expected_access[] = [
            [
                'child1',
            ],
            NULL,
        ];
        $expected_access[] = [
            [
                'child2',
            ],
            FALSE,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.0',
            ],
            FALSE,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.1',
            ],
            FALSE,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.2',
            ],
            FALSE,
        ];
        $data['access-mixed-parents'] = [
            $clone,
            $expected_access,
        ];
        $clone = $element;
        $clone['#access'] = $access_result_allowed;
        $clone['child2']['#access'] = $access_result_forbidden;
        $clone['child2.0']['#access'] = $access_result_allowed;
        $clone['child2.1']['#access'] = $access_result_allowed;
        $clone['child2.2']['#access'] = $access_result_allowed;
        $expected_access = [];
        $expected_access[] = [
            [],
            $access_result_allowed,
        ];
        $expected_access[] = [
            [
                'child0',
            ],
            NULL,
        ];
        $expected_access[] = [
            [
                'child1',
            ],
            NULL,
        ];
        $expected_access[] = [
            [
                'child2',
            ],
            $access_result_forbidden,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.0',
            ],
            $access_result_forbidden,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.1',
            ],
            $access_result_forbidden,
        ];
        $expected_access[] = [
            [
                'child2',
                'child2.2',
            ],
            $access_result_forbidden,
        ];
        $data['access-mixed-parents-object'] = [
            $clone,
            $expected_access,
        ];
        return $data;
    }
    
    /**
     * @covers ::valueCallableIsSafe
     *
     * @dataProvider providerTestValueCallableIsSafe
     */
    public function testValueCallableIsSafe($callback, $expected) {
        $method = new \ReflectionMethod(FormBuilder::class, 'valueCallableIsSafe');
        $method->setAccessible(TRUE);
        $is_safe = $method->invoke($this->formBuilder, $callback);
        $this->assertSame($expected, $is_safe);
    }
    public function providerTestValueCallableIsSafe() {
        $data = [];
        $data['string_no_slash'] = [
            'Drupal\\Core\\Render\\Element\\Token::valueCallback',
            TRUE,
        ];
        $data['string_with_slash'] = [
            '\\Drupal\\Core\\Render\\Element\\Token::valueCallback',
            TRUE,
        ];
        $data['array_no_slash'] = [
            [
                'Drupal\\Core\\Render\\Element\\Token',
                'valueCallback',
            ],
            TRUE,
        ];
        $data['array_with_slash'] = [
            [
                '\\Drupal\\Core\\Render\\Element\\Token',
                'valueCallback',
            ],
            TRUE,
        ];
        $data['closure'] = [
            function () {
            },
            FALSE,
        ];
        return $data;
    }
    
    /**
     * @covers ::doBuildForm
     *
     * @dataProvider providerTestInvalidToken
     */
    public function testInvalidToken($expected, $valid_token, $user_is_authenticated) {
        $form_token = 'the_form_token';
        $form_id = 'test_form_id';
        if (is_bool($valid_token)) {
            $this->csrfToken
                ->expects($this->any())
                ->method('get')
                ->willReturnArgument(0);
            $this->csrfToken
                ->expects($this->atLeastOnce())
                ->method('validate')
                ->willReturn($valid_token);
        }
        $current_user = $this->prophesize(AccountInterface::class);
        $current_user->isAuthenticated()
            ->willReturn($user_is_authenticated);
        $property = new \ReflectionProperty(FormBuilder::class, 'currentUser');
        $property->setAccessible(TRUE);
        $property->setValue($this->formBuilder, $current_user->reveal());
        $expected_form = $form_id();
        $form_arg = $this->getMockForm($form_id, $expected_form);
        // Set up some request data so we can be sure it is removed when a token is
        // invalid.
        $this->request->request
            ->set('foo', 'bar');
        $_POST['foo'] = 'bar';
        $form_state = new FormState();
        $input['form_id'] = $form_id;
        $input['form_token'] = $form_token;
        $input['test'] = 'example-value';
        $form_state->setUserInput($input);
        $form = $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
        $this->assertSame($expected, $form_state->hasInvalidToken());
        if ($expected) {
            $this->assertEmpty($form['test']['#value']);
            $this->assertEmpty($form_state->getValue('test'));
            $this->assertEmpty($_POST);
            $this->assertEmpty(iterator_to_array($this->request->request
                ->getIterator()));
        }
        else {
            $this->assertEquals('example-value', $form['test']['#value']);
            $this->assertEquals('example-value', $form_state->getValue('test'));
            $this->assertEquals('bar', $_POST['foo']);
            $this->assertEquals('bar', $this->request->request
                ->get('foo'));
        }
    }
    public function providerTestInvalidToken() {
        $data = [];
        $data['authenticated_invalid'] = [
            TRUE,
            FALSE,
            TRUE,
        ];
        $data['authenticated_valid'] = [
            FALSE,
            TRUE,
            TRUE,
        ];
        // If the user is not authenticated, we will not have a token.
        $data['anonymous'] = [
            FALSE,
            NULL,
            FALSE,
        ];
        return $data;
    }
    
    /**
     * @covers ::prepareForm
     *
     * @dataProvider providerTestFormTokenCacheability
     */
    public function testFormTokenCacheability($token, $is_authenticated, $expected_form_cacheability, $expected_token_cacheability, $method) {
        $user = $this->prophesize(AccountProxyInterface::class);
        $user->isAuthenticated()
            ->willReturn($is_authenticated);
        $this->container
            ->set('current_user', $user->reveal());
        \Drupal::setContainer($this->container);
        $form_id = 'test_form_id';
        $form = $form_id();
        $form['#method'] = $method;
        if (isset($token)) {
            $form['#token'] = $token;
        }
        $form_arg = $this->createMock('Drupal\\Core\\Form\\FormInterface');
        $form_arg->expects($this->once())
            ->method('getFormId')
            ->will($this->returnValue($form_id));
        $form_arg->expects($this->once())
            ->method('buildForm')
            ->will($this->returnValue($form));
        $form_state = new FormState();
        $built_form = $this->formBuilder
            ->buildForm($form_arg, $form_state);
        if (!isset($expected_form_cacheability) || $method == 'get' && !is_string($token)) {
            $this->assertFalse(isset($built_form['#cache']));
        }
        else {
            $this->assertTrue(isset($built_form['#cache']));
            $this->assertEquals($expected_form_cacheability, $built_form['#cache']);
        }
        if (!isset($expected_token_cacheability)) {
            $this->assertFalse(isset($built_form['form_token']));
        }
        else {
            $this->assertTrue(isset($built_form['form_token']));
            $this->assertEquals($expected_token_cacheability, $built_form['form_token']['#cache']);
        }
    }
    
    /**
     * Data provider for testFormTokenCacheability.
     *
     * @return array
     */
    public function providerTestFormTokenCacheability() {
        return [
            'token:none,authenticated:true' => [
                NULL,
                TRUE,
                [
                    'contexts' => [
                        'user.roles:authenticated',
                    ],
                ],
                [
                    'max-age' => 0,
                ],
                'post',
            ],
            'token:none,authenticated:false' => [
                NULL,
                FALSE,
                [
                    'contexts' => [
                        'user.roles:authenticated',
                    ],
                ],
                NULL,
                'post',
            ],
            'token:false,authenticated:false' => [
                FALSE,
                FALSE,
                NULL,
                NULL,
                'post',
            ],
            'token:false,authenticated:true' => [
                FALSE,
                TRUE,
                NULL,
                NULL,
                'post',
            ],
            'token:none,authenticated:false,method:get' => [
                NULL,
                FALSE,
                [
                    'contexts' => [
                        'user.roles:authenticated',
                    ],
                ],
                NULL,
                'get',
            ],
            'token:test_form_id,authenticated:false,method:get' => [
                'test_form_id',
                TRUE,
                [
                    'contexts' => [
                        'user.roles:authenticated',
                    ],
                ],
                [
                    'max-age' => 0,
                ],
                'get',
            ],
        ];
    }

}

Members

Title Sort descending Deprecated Modifiers Object type Summary Overriden Title Overrides
FormBuilderTest::$container protected property The dependency injection container.
FormBuilderTest::formStateResponseProvider public function Provides test data for testHandleFormStateResponse().
FormBuilderTest::providerTestBuildFormWithTriggeringElement public function Data provider for ::testBuildFormWithTriggeringElement().
FormBuilderTest::providerTestChildAccessInheritance public function Data provider for testChildAccessInheritance.
FormBuilderTest::providerTestFormTokenCacheability public function Data provider for testFormTokenCacheability.
FormBuilderTest::providerTestInvalidToken public function
FormBuilderTest::providerTestValueCallableIsSafe public function
FormBuilderTest::setUp protected function Overrides FormTestBase::setUp
FormBuilderTest::testBuildFormWithClassString public function Tests the buildForm() method with a class name based form ID.
FormBuilderTest::testBuildFormWithObject public function Tests the buildForm() method with a form object.
FormBuilderTest::testBuildFormWithString public function Tests the buildForm() method with a string based form ID.
FormBuilderTest::testBuildFormWithTriggeringElement public function Tests whether the triggering element is properly identified.
FormBuilderTest::testChildAccessInheritance public function @covers ::buildForm
FormBuilderTest::testExceededFileSize public function @covers ::buildForm
FormBuilderTest::testFormCacheDeletionCached public function Tests that a cached form is deleted after submit.
FormBuilderTest::testFormCacheDeletionUncached public function Tests that an uncached form does not trigger cache set or delete.
FormBuilderTest::testFormTokenCacheability public function @covers ::prepareForm
FormBuilderTest::testGetCache public function Tests the getCache() method.
FormBuilderTest::testGetFormIdWithBaseForm public function Tests the getFormId() method with a base form object.
FormBuilderTest::testGetFormIdWithClassName public function Tests the getFormId() method with a class name form ID.
FormBuilderTest::testGetFormIdWithInjectedClassName public function Tests the getFormId() method with an injected class name form ID.
FormBuilderTest::testGetFormIdWithObject public function Tests the getFormId() method with a form object.
FormBuilderTest::testGetFormIdWithString public function Tests the getFormId() method with a string based form ID.
FormBuilderTest::testGetFormWithClassString public function Tests the getForm() method with a class name based form ID.
FormBuilderTest::testGetFormWithObject public function Tests the getForm() method with a form object.
FormBuilderTest::testGetFormWithString public function Tests the getForm() method with a string based form ID.
FormBuilderTest::testGetPostAjaxRequest public function @covers ::buildForm
FormBuilderTest::testHandleFormStateResponse public function Tests the handling of FormStateInterface::$response.
FormBuilderTest::testHandleRedirectWithResponse public function Tests the handling of a redirect when FormStateInterface::$response exists.
FormBuilderTest::testInvalidToken public function @covers ::doBuildForm
FormBuilderTest::testRebuildForm public function Tests the rebuildForm() method for a POST submission.
FormBuilderTest::testRebuildFormOnGetRequest public function Tests the rebuildForm() method for a GET submission.
FormBuilderTest::testUniqueElementHtmlId public function Tests that HTML IDs are unique between 2 forms with the same element names.
FormBuilderTest::testUniqueHtmlId public function Tests that HTML IDs are unique when rebuilding a form with errors.
FormBuilderTest::testValueCallableIsSafe public function @covers ::valueCallableIsSafe
FormTestBase::$account protected property The current user.
FormTestBase::$cache protected property The cache backend to use.
FormTestBase::$classResolver protected property The class results.
FormTestBase::$controllerResolver protected property The controller resolver.
FormTestBase::$csrfToken protected property The CSRF token generator.
FormTestBase::$elementInfo protected property The element info manager.
FormTestBase::$eventDispatcher protected property The event dispatcher.
FormTestBase::$formBuilder protected property The form builder being tested.
FormTestBase::$formCache protected property The form cache.
FormTestBase::$formSubmitter protected property
FormTestBase::$formValidator protected property
FormTestBase::$kernel protected property
FormTestBase::$logger protected property
FormTestBase::$moduleHandler protected property The mocked module handler.
FormTestBase::$request protected property The request.
FormTestBase::$requestStack protected property The request stack.
FormTestBase::$themeManager protected property The mocked theme manager.
FormTestBase::$translationManager protected property
FormTestBase::$urlGenerator protected property The mocked URL generator.
FormTestBase::assertFormElement protected function Asserts that the expected form structure is found in a form for a given key.
FormTestBase::getInfo public function A stub method returning properties for the defined element type.
FormTestBase::getMockForm protected function Provides a mocked form object.
FormTestBase::simulateFormSubmission protected function Simulates a form submission within a request, bypassing submitForm().
FormTestBase::tearDown protected function
PhpunitCompatibilityTrait::getMock Deprecated public function Returns a mock object for the specified class using the available method.
PhpunitCompatibilityTrait::setExpectedException Deprecated public function Compatibility layer for PHPUnit 6 to support PHPUnit 4 code.
UnitTestCase::$randomGenerator protected property The random generator.
UnitTestCase::$root protected property The app root. 1
UnitTestCase::assertArrayEquals protected function Asserts if two arrays are equal by sorting them first.
UnitTestCase::getBlockMockWithMachineName Deprecated protected function Mocks a block with a block plugin. 1
UnitTestCase::getClassResolverStub protected function Returns a stub class resolver.
UnitTestCase::getConfigFactoryStub public function Returns a stub config factory that behaves according to the passed array.
UnitTestCase::getConfigStorageStub public function Returns a stub config storage that returns the supplied configuration.
UnitTestCase::getContainerWithCacheTagsInvalidator protected function Sets up a container with a cache tags invalidator.
UnitTestCase::getRandomGenerator protected function Gets the random generator for the utility methods.
UnitTestCase::getStringTranslationStub public function Returns a stub translation manager that just returns the passed string.
UnitTestCase::randomMachineName public function Generates a unique random string containing letters and numbers.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.